ERROR: LoadError: UndefVarError: A not defined

Hey all, I have the following code:

function POMDPs.action(pol::MyCustomType,s::S)::A where {S,A}
   # ignore
end

This code works on Julia 1.1.1 but errors on Julia 1.3 + and says “ERROR: LoadError: UndefVarError: A not defined”. Is this a syntax change I missed in one of the new Julia versions (I looked and could not find this syntax mentioned in the release notes anywhere) ?

Simpler reproducible example:

julia> VERSION
v"1.1.0"

julia> foo(x::T)::T where {T} = x
ERROR: UndefVarError: T not defined
Stacktrace:
 [1] top-level scope at none:0

Are you sure this works on Julia 1.1.1? Maybe you just defined A somewhere?

julia> const T = Float32
Float32

julia> foo(x::T)::T where {T} = x
foo (generic function with 1 method)

julia> foo(2.3)
ERROR: MethodError: no method matching foo(::Float64)
Closest candidates are:
  foo(::Float32) at REPL[7]:1
Stacktrace:
 [1] top-level scope at none:0

julia> foo(2.3f0)
2.3f0
2 Likes

Yeah, it works on 1.1.1.

But it doesn’t work on 1.1.0, as I showed above.
EDIT, doesn’t work on 1.1.2-pre either:

julia> VERSION
v"1.1.2-pre.0"

julia> foo(x::T)::T where {T} = x
ERROR: UndefVarError: T not defined
Stacktrace:
 [1] top-level scope at none:0
1 Like

There are some pretty weird behaviors here, at least in julia 1.2 and 1.4, that maybe shouldn’t be allowed syntactically at all. Basically, what I’m saying below is that the OP should continue to be an error, because A is indeed not defined, but also that other things shouldn’t be allowed either…

The following doesn’t really make sense if T isn’t defined at parse time. So it should error at that stage, rather than when called.

julia> foo(x)::T = x    # should error here, T not defined

julia> foo(1)
ERROR: UndefVarError: T not defined

Even crazier, if you define T after defining the function but before calling it, it will work, and then changing T later will change the behavior of the function!

julia> foo(x)::T = x
foo (generic function with 1 method)

julia> T = Float32
Float32

julia> foo(1)
1.0f0

julia> T = Float64
Float64

julia> foo(1)    # holy cow!
1.0

I’m not sure if there’s any value to having this be possible, and maybe it’s important in some way I don’t realize, but if not it seems like it should be a parse time error to use types in a definition that aren’t associated with the arguments (in a where context) or previously defined.

1 Like

These are just closure which capture the variable binding (and not the value at the time of the closure creation). Here a recent tidbit from Jeff, musing whether value-capture would have been better:

1 Like

Interesting. Thanks for that!