Is there a good way to make type parameters from the where keyword accessible in an if statement, the way we can with function signatures?
_
_ _ _(_)_ | Documentation: https://docs.julialang.org
(_) | (_) (_) |
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.
| | | | | | |/ _` | |
| | |_| | | | (_| | | Version 1.10.4 (2024-06-04)
_/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release
|__/ |
julia> struct Foo{K} end
julia> foo = Foo{3}()
Foo{3}()
julia> if foo isa Foo{K} where K
@show K
end
ERROR: UndefVarError: `K` not defined
Stacktrace:
[1] top-level scope
@ REPL[3]:1
It seems weird that if foo isa Foo{K} where K is valid syntax but leaves K undefined.
(I am aware that there are other ways of accomplishing this, such as defining get_k(::Foo{K}) where K = K. My question is about the syntactic inconsistency.)
This has nothing to do with if and everything to do with where.
julia> foo isa Foo{K} where K
true
julia> K
ERROR: UndefVarError: `K` not defined
The reason that Foo{K} where K does not work is that it does not actually assign K as a variable. It’s only usable as a typevar within the expression it belongs to. As far as I know (which may be wrong) the parameter is only made available when where is used in a method definition (e.g., with function). I don’t have an explanation as to why.
I’m not aware of any responsible way to recover K from the type except for the get_k pattern you already mentioned.
ONE SHOULD NOT but one can get this particular parameter with typeof(foo).parameters[1]. This is absolutely discouraged and may break in any Julia version. It’s also very brittle to changes in Foo.
(Foo{K} where K) is one type, all isa does is check if foo is an instance of said type. It reasonably does not assign variables in its scope. You just happened to make an if condition where a K value could exist in the branch, but what if the condition were if foo isa (Foo{K} where K) || foo isa Complex? Do you assign K when foo = 1im? if statements don’t make new local scopes anyway so it’s not possible to isolate any K assignment to a branch.
where in methods is different, the clause doesn’t belong to a type and it’s intended to get static parameters (not variables, you can’t reassign them) from the argument types after dispatch. Method syntax resembles other block structures but syntax often does different things in different places; let is probably the closest thing by taking inputs in the header, making a local scope, and returning an output, but you’re not dispatching to it and annotating the header variables restricts their further assignment unlike a method’s argument annotations used for dispatch.