UndefKeywordError with multiple (overloaded) constructors

I’d prefer to write multiple outer constructors for structs, so that the user can provide what they have and let the constructor make whatever conversions are needed. Running the mwe:

module ConstructorTest
    struct Fraction
        numerator::Integer
        denominator::Integer
        value::Number
        name::String
    end
    Fraction(; numerator::Integer, denominator::Integer, name="") = Fraction(numerator, denominator, numerator/denominator, name)
    Fraction(; value::Number, denominator::Integer, name="") = Fraction(value*denominator, denominator, value, name)

    function test()
        fa = Fraction(3,4,0.75,"three-quarters")
        println(fa)
        fb = Fraction(numerator=3,denominator=4,name="three-quarters")
        println(fb)
        fc = Fraction(value=0.75,denominator=4,name="three-quarters")
        println(fc)
    end
end
ConstructorTest.test()

I receive:

Main.ConstructorTest.Fraction(3, 4, 0.75, "three-quarters")
ERROR: LoadError: UndefKeywordError: keyword argument value not assigned

from fb’s creation. Despite using named arguments and unique argument types, the wrong constructor is called.

Have I made an error in defining the outer constructors or is this usage not intended/implemented?

Unfortunately keyword arguments do not participate in method dispatch, so you can’t have two different methods which differ only in what keywords they accept. In practice, whichever one you define last will just overwrite the one defined before it. There’s a note about this in the docs here:

Keyword arguments behave quite differently from ordinary positional arguments. In particular, they do not participate in method dispatch. Methods are dispatched based only on positional arguments, with keyword arguments processed after the matching method is identified.

One possible workaround is to create a single function with default values for all the keyword arguments and then check which ones are set inside that function:

function Fraction(; numerator=nothing, value=nothing, denominator, name="")
  if numerator !== nothing
    @assert value === nothing # can't set both
    Fraction(numerator, denominator, numerator / denominator, name)
 else if value !== nothing
    Fraction(value * denominator, denominator, value, name)
  else
    @error "must set either numerator or value"
  end
end
5 Likes

You may also be interested in Base.@kwdef and/or Parameters.jl, two tools that automate keyword argument constructors.

1 Like