Base.@pure which_key(tuple::NTuple{N, Symbol}, key::Symbol) where N =
map(x -> x == key, tuple)
According to the definition in #414, further discussed in #14324, it seems so:
julia> which_key(tuple::NTuple{N, Symbol}, key::Symbol) where N =
map(x -> x == key, tuple)
which_key (generic function with 1 method)
julia> a1 = (:x,:y,:z); a2 = (:x,:y,:z);
julia> k1 = :x; k2 = :x;
julia> a1 === a2
true
julia> k1 === k2
true
julia> which_key(a1, k1) === which_key(a2, k2)
true
But I see you commented on that second issue, so I doubt this comment is of any use to you!
I would generally discourage using the @pure
annotations. It’s primary usage is for inference and it is hard to use correctly.
You need the @pure
annotation here to get constant propagation.
It’s true that it is computing a pure result, since we can inline the map
call to create an equivalent function that makes it more apparent that we’re only using pure functions:
which_key(tuple::Tuple{}, key::Symbol) = ()
which_key(tuple::Tuple{Vararg{Symbol}}, key::Symbol) =
(first(tuple) === key, which_key(Base.tail(tuple), key)...)
Although, if you try code_typed
on this, you’ll soon discover that IPO constant-propagation gets disabled by recursion, so I’d guess that probably doesn’t help as much as you might have liked. (and after several minutes of playing with this, I can say that it’s surprisingly hard to bypass that heuristic – which is nice since it means that at least it’s fairly robust to style changes.)
In the end, I decided the most feasible way to bypass it was just to give in and use a generated function after moving everything into the type-domain, so, um, yeah, this:
which_key4(tuple::NTuple{N, Symbol}, key::Symbol) =
_which_key4(Val(tuple), Val(key))
_which_key4(::Val{tuple}, ::Val{key}) where {tuple, key} =
ntuple(i -> tuple[i] === key, Val(length(tuple)))
addendum:
An even more minimal implementation (e.g. the same as above after more manual inlining):
which_key(tuple::Tuple{Vararg{Symbol}}, key::Symbol) =
_which_key(key, tuple...)
_which_key(key::Symbol) = ()
Base.@pure _which_key(key::Symbol, first::Symbol, tail...) =
(first === key, _which_key(key, tail...)...)
Ok, great! Since I have an expert here, to get constant propagation to work recursively, I need to define my own tail function:
Base.@pure argtail(x, rest...) = rest
tail(x) = argtail(x...)
Again, a @pure
annotation is necessary here, even though argtail
seems very pure.
Also, would it be kosher to take off the type restrictions?
Base.@pure which_key(tuple::Tuple, key) where N =
map(x -> x == key, tuple)
No, it’s not pure without the restrictions.
It seems like argtail just needs an update to the inference code