Meaning and error message of type-parameters

Following this thread:

We have this example:

julia> struct A{T}
         i
       end

julia> A(1) # does not work
ERROR: MethodError: no method matching A(::Int64)
Stacktrace:
 [1] top-level scope at REPL[2]:1

julia> A{Int64}(1)  # explicitly defining the type parameter
A{Int64}(1)

julia> A(i::T) where T = A{T}(i) # deduces type from argument
A

julia> A(1) # now it works
A{Int64}(1)

julia>

The question is if the error message could be more clear, mentioning the need of the type parameter in the constructor.


The second question is what does the type parameter mean, and what are its implications, when the type is not used for the type of the variables?

julia> struct A{T}
         i
       end

julia> a = A{Float64}(1)
A{Float64}(1)

julia> typeof(a.i)
Int64

(I was not expecting that a.i was anything but an integer, I am only trying to explain the question). In that case, what the Float64 implies, if anything, for the behavior of that struct?

I think all the parameterizations of your struct would compile down to the same thing, the only difference is when you dispatch on it and do something with the contained type parameter. I don’t know why you’d do this in the case you showed, but it might be helpful to “tag” similar structs in that way for some purposes.

1 Like

It may imply something if you want. E.g., you can still dispatch on that type parameter, but it doesn’t help in inference of the type of i. In certain cases, I think, it may be even worse for performance than a non-parameterized type. E.g.

A(x::T) where T = A{T}(x)

Base.:+(a1::A, a2::A) = A(a1.i + a2.i)

Now + is type-unstable because the type of a1.i + a2.i cannot be inferred. So, it may be even slower than

struct B i end

Base.:+(b1::B, b2::B) = B(b1.i + b2.i)

(it is indeed ~20x slower on my machine with Julia 1.5.3)

1 Like

It seems to me that it should just mean that you have an infinite collection of types A{Float64}, A{Array{Int64}}, A{TCPSocket} and so on. All of these types have an untyped field i, which can hold values of any type.

1 Like

Yes, that is a consequence. The only one? I was particularly thinking about the possible implications for the way the variable is stored. But in that case it is only a label, apparently.

(If it was only for dispatch, it would make more sense to define abstract types for that, I suppose). Here I put an example just for clarity:

julia> struct A{T}
         i :: Int64
       end

julia> abstract type Dispatch1 end

julia> abstract type Dispatch2 end

julia> a = A{Dispatch1}(1)
A{Dispatch1}(1)

julia> b = A{Dispatch2}(1)
A{Dispatch2}(1)

julia> f(x::A{Dispatch1}) = x.i
f (generic function with 4 methods)

julia> f(x::A{Dispatch2}) = 2*x.i
f (generic function with 3 methods)

julia> f(a)
1

julia> f(b)
2