Stream: helpdesk (published)

Topic: Why does `new` not infer its argument types?


view this post on Zulip Colin Caine (Mar 13 2021 at 18:30):

Why does this not work:

struct Y{T<:Integer}
    y::T
    Y(a) = new(a * 2)
end

Instead, you have to do this, which seems like a lot of ceremony:

struct X{T<:Integer}
    x::T
    X(a::T) where {T} = new{T}(a * 2)
end

Edit: search terms: ERROR: syntax: too few type parameters specified in "new{...}"

view this post on Zulip Colin Caine (Mar 13 2021 at 18:47):

Looking through the manual, the expected form is something like this

struct Z{T}
    z::T
    Z{T}(a) where {T} = new(a * 2)
end

Z(a::T) where {T} = Z{T}(a)

Which I guess is nice because it is very explicit? But I think it's adequately implied by Y above. It's nicer than X, tho

view this post on Zulip Kwaku Oskin (Mar 13 2021 at 19:05):

Maybe it's because in general case type of 2*a can differ from the type of a. For example, what if a is of the (non existent) type OddInteger? In this case 2*a is of the type EvenInteger and how compiler should solve this case? Is it Y{OddInteger}(1) or Y{EvenInteger}(1)? Only you know, what you really want to see in this case.

view this post on Zulip Colin Caine (Mar 13 2021 at 19:19):

I guess the issue is that I expect new to act like a generic method or a type with a default constructor. With those, we don't specify the types in the code, we just provide values and Julia decides what the types will be.

With your example, I feel like I'd be more likely to mess it up if I'm forced to be explicit compared to if new behaved more like a function.

(To be explicit, I expect a behaviour like:

struct A{T}
    x::T
    function A(x)
        x1 = x * 2
        new{typeof(x1)}(x1)
    end
end

)

view this post on Zulip Kwaku Oskin (Mar 13 2021 at 19:55):

But this is not the behavior of Z or X structure, right? Because they more or less explicitly say new{typeof(x)}(x1)

view this post on Zulip Kwaku Oskin (Mar 13 2021 at 19:56):

Hmm, I guess Z will just error in this case.

view this post on Zulip Kwaku Oskin (Mar 13 2021 at 19:57):

Maybe they both error.

view this post on Zulip Kwaku Oskin (Mar 13 2021 at 19:58):

Should put it in REPL :-)

view this post on Zulip Kwaku Oskin (Mar 13 2021 at 20:28):

Ah, after some consideration, I think I get the real reason, why you should be explicit.

I think that newis really can infer types on its own, but default inner constructor is more complicated than that.
I think it's closer to A{T}(x) where T = new{T}(convert(T, f(x)), and this gives you possibility to write expressions like Vector{Float64}(1). In your syntax A(x) = new(f(x)) everything will be fine till the moment, when you would need to call constructor with explicit type (like in Vector{Float64} example). Since types infer from the result of calculation, construction of the form A{T}(x) will be forbidden. And it would be nice until the moment when you need it.

In your last example:

julia> A{Int}(1)
ERROR: MethodError: no method matching A{Int64}(::Int64)

And it looks unexpected. So you can shoot yourself in a foot, but at least you have to make extra efforts for it.


Last updated: Dec 28 2024 at 04:38 UTC