struct Bar
x::Float64
y::Float64
end
Bar(missing, 2)
then the error message doesn’t seem to indicate whether x or y was the problem:
ERROR: LoadError: MethodError: Cannot `convert` an object of type Missing to an object of type Float64
Similarly, if I’m using Parameters.jl for keyword constructors and/or assertions, the error is slightly worse (still doesn’t tell me the field name, and also talks about boolean stuff that confuses the caller):
using Parameters
@with_kw struct Foo
x::Float64
@assert 0 <= x <= 1
end
Foo(; x = missing)
ERROR: LoadError: TypeError: non-boolean (Missing) used in boolean context
I can get back to the convert-style error by using an inner constructor to delay the assertion:
@with_kw struct Foo
x::Float64
function Foo(args...)
f = new(args...)
@assert 0 <= f.x <= 1
end
end
Foo(; x = missing)
ERROR: LoadError: MethodError: Cannot `convert` an object of type Missing to an object of type Float64
but that’s still not ideal.
What best practices have people come up with for giving helpful error messages when constructing composite types? Anything that plays especially well with Parameters.jl?
I don’t normally find this to be a problem from other clues, but the issue is that the error isn’t coming from the context of the type constructor that can know what its fields are. convert(Float64, missing) and missing && missing do not know. So on some level, you need a constructor to check the inputs before new and throw errors specifically naming the positions and/or names. I’m not sure if that can generally be done in one place because of type parameters and various constructors changing how data is passed; for example, an inner constructor shouldn’t tell me that the 4th internal field had a wrong type after unrecognizable processing, the immediate outer constructor should tell me that my call provided a public keyword argument with a (different) wrong type. At least the errors thrown in callers would stop execution before callees can error, and @with_kw’s consistently named keyword arguments helps a lot in determining what the errors should say.
struct Bar
x::Float64
y::Float64
function Bar(x::Number, y::Number)
new(x, y)
end
end
In the REPL:
julia> Bar(false, 7 + 0im) # conversion still works fine
Bar(0.0, 7.0)
julia> Bar(missing, 7 + 0im) # the error message is now friendlier
ERROR: MethodError: no method matching Bar(::Missing, ::Complex{Int64})
The type `Bar` exists, but no method is defined for this combination of argument types when trying to construct it.
Closest candidates are:
Bar(::Number, ::Number)
@ Main REPL[1]:4