Making new a bit more automatic

Consider a definition with the outer-only constructor

Base.@kwdef struct Foo{T,S}
    a::T
    b::S
    function Foo(a::T, b::S) where {T,S}
        @assert a > 0 && b > 0
        new{T,S}(a, b)
    end
end

where T and S are not used for anything, just parametrizing new. An alternative would be

Base.@kwdef struct Foo2{T,S}
    a::T
    b::S
    function Foo2(a, b)
        @assert a > 0 && b > 0
        new{typeof(a),typeof(b)}(a, b)
    end
end

Is there a way to get rid of the boilerplate? I tried

Base.@kwdef struct Foo3{T,S}
    a::T
    b::S
    function Foo3(a, b)
        @assert a > 0 && b > 0
        args = (a, b)
        new{map(typeof, args)...}(args...)
    end
end

but got

ERROR: syntax: too few type parameters specified in "new{...}"
1 Like

I encountered this limitation before and could not figure out a way to automate the process. I think something along the lines of what you proposed would be a useful addition to the language.

I don’t think one can make it entirely automatic, consider eg

Base.@kwdef struct Foo{S,T} # NOTE ORDER
    a::T
    b::S
    function Foo(a::T, b::S) where {T,S}
        @assert a > 0 && b > 0
        new{S,T}(a, b) # NOTE ORDER
    end
end

What is missing is a nice way to compose

  1. the outermost layer of keyword arguments, with defaults,

  2. the default outer constructor which nicely promotes,

  3. an inner constructor which can call an arbitrary block of code that has at least access to the fields (not necessarily transformations, generation, etc, even though that would be nice).

Currently Base provides an all-or-nothing approach. Which may be fine, this is a job for a package. Currently none of the options available provide a full solution. But the problem is quite complex and I need to think more about what I want exactly, and whether it can be generalized enough to be packaged.

The immediate question I don’t get though is why the Foo3 inner constructor code doesn’t work. Is it a bug?

Unrelated (and most likely just a slip): @assert in your code should be replaced by throwing an exception: see the warning box in the doc.

I don’t know if it’s a bug, but it seems to be unsupported. Without @kwdef, I get this:

Julia-1.1.0> struct Foo3{T,S}
                 a::T
                 b::S
                 function Foo3(a, b)
                     @assert a > 0 && b > 0
                     args = (a, b)
                     new{map(typeof, args)...}(args...)
                 end
             end
ERROR: syntax: ... is not supported inside "new"
1 Like

Thanks, there was even an issue:

1 Like