Why the inner constructor of a template structure returns "too few type parameters" error?

Why the following code:

mutable struct Test{T <:Number}
    μ::Union{T,Nothing}
    σ²::Union{T,Nothing}
    Test(μ=nothing,σ²=nothing) = new(μ,σ²)
end

returns the error ERROR: syntax: too few type parameters specified in "new{...}"

I think I got an “intuition” on the why, that calling the constructor like that, the compiler has then no clue on what is T.

I am however left with the next question: which can be a workaround for such cases, when I want to have the freedom to initialise my struct for something specific but also have a non-initialised default…

mutable struct Test{T <:Number}
    μ::Union{T,Nothing}
    σ²::Union{T,Nothing}
    Test(μ=nothing,σ²=nothing) = new{T}(μ,σ²)
end

Notice the {T}.

That still gives me an error when I go to call the constructor:

julia> a = Test(1,2)
ERROR: UndefVarError: T not defined
julia> a = Test()
ERROR: UndefVarError: T not defined

Right. Sorry. This works.

mutable struct Test{T <:Number}
    μ::Union{T,Nothing}
    σ²::Union{T,Nothing}
    Test(μ::Union{T,Nothing}=nothing,σ²::Union{T,Nothing}=nothing) where {T} = new{T}(μ,σ²)
end
Test(1, 2)

Will fail for Test() but then the compiler really has no way to know the value of T, unfortunately, you cannot call Test{Int}() also, so there is no way to build the object with two nothings and a Type for T.

Yes, but not with Test(). I think it is not possible to have a (nothing,nothing) initialisation, because that would not contain any information on T.

I think I need to create an other field containing this information and then add its initialisation with an actual type to default (e.g. Float64) so that I can let callers call the function Test() to default to Float64, but allow the flexibility to call it with Test(type=Int64) if I want my nothing values to then contain Int64 values…

1 Like

You can do this, however:

mutable struct Test{T <:Number}
        μ  ::Union{T,Nothing}
        σ² ::Union{T,Nothing}
        Test(μ::Union{T,Nothing}=nothing,σ²::Union{T,Nothing}=nothing) where {T} = new{T}(μ,σ²)
        Test(::Type{T}) where {T} = new{T}(nothing, nothing)
end

> Test(Int)
Test{Int64}(nothing, nothing)

It is not ideal because if you have two nothings you need to call the initialization in a different-than-usual fashion, but it works. You could also always take the type as first parameter, so the call is always the same format (but then, it does not deduce the type from the other parameters, just check if they are match).

4 Likes

@Henrique_Becker

Thank you!

This seems to work in all circumstances:


mutable struct Test{T <:Number}
        μ  ::Union{T,Nothing}
        σ² ::Union{T,Nothing}
        Test(μ::Union{T,Nothing}=nothing,σ²::Union{T,Nothing}=nothing) where {T} = new{T}(μ,σ²)
        Test(type::Type{T}=Float64) where {T} = new{T}(nothing, nothing)
end

f = Test()
f.μ = 1.2

i = Test(Int64)
i.μ = 1
i.μ = 1.5 # error, as it should be 

f2 = Test(1.1,2.2)
i2 = Test(1,2)

Just two things:

  1. You do not need the type in Test(type::Type{T}=Float64) ... just Test(::Type{T}=Float64) ... is fine. You can see type is not needed anywhere.
  2. Yes, you can have a default. The only problem is forgetting this and ending up with the default Float64 everywhere, XD.

Happy to help.

2 Likes

While this solution works in my user case, the problem is that when I put it in a module I have a “Method definition overwritten” warning together with a message “** incremental compilation may be fatally broken for this module **”.

Should I worry ? (well, there are more important things to be worried now, but is it really disabling pre-compilation for my module or doing something nasty ?)

Ah, I was wrong, I should have tested. You have an ambiguity problem, if both method definitions have all parameters as optional, then Julia is not sure about what should be called with no parameters. I think that what you want is something like:

julia> mutable struct Test{T <:Number}
               μ  ::Union{T,Nothing}
               σ² ::Union{T,Nothing}
               Test(μ::Union{T,Nothing},σ²::Union{T,Nothing}=nothing) where {T} = new{T}(μ,σ²)
               Test(type::Type{T}=Float64) where {T} = new{T}(nothing, nothing)
       end

julia> Test()
Test{Float64}(nothing, nothing)

julia> Test(Int)
Test{Int64}(nothing, nothing)

julia> Test(nothing)
ERROR: UndefVarError: T not defined
Stacktrace:
 [1] Test(::Nothing, ::Nothing) at ./REPL[1]:4 (repeats 2 times)
 [2] top-level scope at REPL[4]:1

julia> Test(1)
Test{Int64}(1, nothing)

So there is no ambiguity.

1 Like