```
Base.@pure which_key(tuple::NTuple{N, Symbol}, key::Symbol) where N =
map(x -> x == key, tuple)
```

# Is this pure?

**bramtayl**#1

**mohamed82008**#2

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!

**vchuravy**#3

I would generally discourage using the `@pure`

annotations. It’s primary usage is for inference and it is hard to use correctly.

**jameson**#5

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...)...)
```

**bramtayl**#6

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.

**bramtayl**#7

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)
```

**jameson**#8

No, it’s not pure without the restrictions.

It seems like argtail just needs an update to the inference code