Hi there, can anyone explain why the different behavior in the following two definitions? Note that in the first case all arguments are positional, whereas in the second case b
is a keyword argument:
julia> f(a::C, b::R=R(1)) where {R <: Real, C <: Union{R, Complex{R}}} = b*a
f (generic function with 2 methods)
julia> f(2.0, 3.0)
6.0
vs
julia> f(a::C; b::R=R(1)) where {R <: Real, C <: Union{R, Complex{R}}} = b*a
ERROR: UndefVarError: R not defined
Stacktrace:
[1] top-level scope at none:0
Thank you!
Keyword arguments don’t participate in dispatch. So what you’ve done here is the equivalent of this:
f(a::C; b=R(1)) where {R <: Real, C <: Union{R, Complex{R}}} = b*a
No value for R
can be determined by dispatch, so it’s undefined which means R(1)
will fail.
2 Likes
That makes a lot of sense, thank you. Note to self: this is explained here.
Wait a sec: R
is determined by dispatch in this other example however
julia> f(a::C) where {R <: Real, C <: Union{R, Complex{R}}} = println(R)
f (generic function with 1 method)
julia> f(Float64(3))
Float64
julia> f(Float32(3)+Float32(2)im)
Float32
How come having an additional keyword argument breaks this? From what I understand from here, dispatch occurs, R
and C
are matched against concrete types, and then keyword arguments are processed. By then, R
should have a concrete type associated.
I’m probably missing something.
It seems that you hit an interesting corner case.
Consider the following
julia> f(a::C; b::R) where {R <: Real, C <: Union{R, Complex{R}}} = b*a
ERROR: UndefVarError: R not defined
It seems that the where
clause is only applied to the positional arguments. Therefore the type R
is not known when the keyword arguments are being processed.
At the other hand
julia> g(a::C, x::R=2; b::R=R(3)) where {R <: Real, C <: Union{R, Complex{R}}} = b*a
g (generic function with 2 methods)
julia> g(5)
15
Type R
is now directly referenced by one of the positional arguments and that seems to make it OK to also use type R
for one of the keyword arguments.
By the way, the following works too. No need to assign a default value to the last positional argument.
julia> h(a::C, x::R; b::R=R(3)) where {R <: Real, C <: Union{R, Complex{R}}} = b*a
g (generic function with 2 methods)
julia> h(5, 2)
15
Yes, although that doesn’t explain why the example in my last post works: R
there does not appear explicitly in any positional arguments, it does only implicitly in the where
clause for C
.
To update this discussion with an even more minimal example, since I’m still not convinced by this:
Why does the following work
julia> f(a::C) where {R <: Real, C <: Union{R, Complex{R}}} = println(R)
f (generic function with 1 method)
julia> f(Float64(3))
Float64
julia> f(Float32(3)+Float32(2)im)
Float32
but the following doesn’t?
julia> g(a::C; b=R(1)) where {R <: Real, C <: Union{R, Complex{R}}} = println(R)
ERROR: UndefVarError: R not defined
Stacktrace:
[1] top-level scope at none:0
1 Like
This may be a bug; I’ve invited @jeff.bezanson to chime in. It has to do with the order of evaluation and scoping of type parameters.
3 Likes
Yes, I think this can be fixed. This is a holdover from pre-0.7, when the dispatch rules said that all static parameters must have well-determined values for a method to match. If you have that rule, but some static parameters are only referenced by keyword argument types, then it would be impossible to call the method without keyword arguments, which is not what you usually want!
To work around that, the front-end tries to separate static parameters used by positional arguments from those used by keyword arguments. That allows simple cases like this to work:
function f(x::X; y::Y) where {X, Y}
but clearly doesn’t work if one variable occurs in the other’s bounds. So this can now be fixed by simply letting both positional and keyword methods use the same set of static parameters.
5 Likes