purrr has_element() in R: Test If a List Contains a Value
purrr's has_element() tests whether a list or vector contains a specific value and returns a single TRUE or FALSE. It compares with identical(), so the match is exact in both value and type.
has_element(list(1, 2, 3), 2) # value in a list -> TRUE has_element(letters, "z") # value in a vector -> TRUE has_element(list(1L, 2L), 2) # type-strict: 2 is not 2L -> FALSE has_element(list("a", "b"), "c") # absent value -> FALSE has_element(list(1:3, 4:6), 4:6) # a whole vector as element -> TRUE 2 %in% c(1, 2, 3) # %in%: coerces types, vectorized some(x, ~ .x > 10) # some(): a predicate, not equality
Need explanation? Read on for examples and pitfalls.
What has_element() does in one sentence
has_element() answers one question: does this list contain that object? You pass a list or atomic vector as the first argument and the value to look for as the second, and you get back a single logical. It is the purrr way to write a membership check that stays predictable on lists, where base R %in% can behave in surprising ways.
The function exists because lists are not vectors. A list can hold numbers, strings, other lists, even functions, all mixed together. has_element() walks that list element by element and asks whether any element is exactly equal to your target. That makes it a natural fit for checking configuration objects, scanning nested results, or guarding a pipeline on the presence of a flag.
Syntax
has_element() takes exactly two arguments and has no options to tune. The signature is has_element(.x, .y), where .x is the list or vector to search and .y is the value to find.
The return value is always length one. That is the key difference from %in%, which returns one logical per element of its left-hand side. Because the result is a scalar, you can drop has_element() straight into an if () condition without wrapping it in any().
Internally, has_element(.x, .y) is equivalent to some(.x, identical, .y). It uses identical() for the comparison, which means the match must agree on value, type, and attributes. There is no type coercion and no partial matching.
Examples
has_element() works on atomic vectors just as well as lists. When the input is an atomic vector, the function compares against each scalar element in turn.
Searching a list whose elements are themselves vectors is where has_element() earns its place. 4:6 is an integer vector and matches the second element exactly. c(4, 5, 6) looks the same but is a double vector, so identical() rejects it.
A common real use is guarding a workflow on a setting, or scanning many lists at once with a map function.
has_element() with map_lgl() to scan a list of lists. Passing has_element as the function and the target as an extra argument tests every sub-list in one vectorized call, returning a clean logical vector you can index or sum.has_element() vs %in% vs some()
Pick has_element() for exact-value checks, %in% for atomic membership, and some() for conditions. All three answer "is something in here?", but they compare in different ways and return different shapes.
| Function | Comparison | Returns | Use when |
|---|---|---|---|
has_element() |
identical(), strict |
Single TRUE/FALSE |
A list might contain an exact value |
%in% |
coerces types, vectorized | Logical vector | Membership test on atomic vectors |
some() |
a custom predicate | Single TRUE/FALSE |
Any element satisfies a condition |
The decision rule is simple. If you need to know whether any element meets a condition rather than equals a value, reach for some() and pass a predicate. If you are working with plain atomic vectors and want the speed and coercion of base R, %in% is fine. When the input is a list and you want a strict, scalar answer, has_element() is the clearest choice.
has_element() is some() frozen to one predicate: equality. Once you see that, the whole purrr predicate family clicks. some() is the general tool, has_element() is the equality shortcut, and detect() returns the matching element instead of a logical.Common pitfalls
The type-strict comparison is the mistake that bites everyone. Because has_element() uses identical(), a numeric literal will not match an integer stored in your list.
has_element() does not coerce types, but %in% does. If a check that should pass returns FALSE, suspect a double-versus-integer mismatch. Print str() of both values, or convert the target with as.integer() before searching.Two other traps are worth knowing. First, has_element() does not recurse into nested lists; an element buried two levels deep is invisible unless you flatten the list first. Second, the argument order is (.x, .y), list first and target second, which is the opposite of the value %in% collection order, so a swapped call fails quietly.
Try it yourself
Try it: Use has_element() to check whether the list pantry contains the string "yeast". Save the result to ex_has_yeast.
Click to reveal solution
Explanation: has_element() walks the list and compares each element to "yeast" with identical(). The third element matches exactly, so the call returns a single TRUE.
Related purrr functions
These purrr predicate functions pair naturally with has_element():
some()tests whether any element satisfies a predicate function.every()tests whether all elements satisfy a predicate.none()tests whether no element satisfies a predicate.detect()returns the first element that matches a predicate.detect_index()returns the position of the first matching element.
For the broader picture, see the guide to functional programming in R, which covers the map and reduce families that these predicates build on.
has_element(x, v) is the equivalent of v in x for a Python list, except the comparison is strict on type. The official reference lives at purrr.tidyverse.org.FAQ
What is the difference between has_element() and %in% in R?
has_element() compares with identical() and returns a single TRUE or FALSE, so the match must agree on value and type. %in% coerces types before comparing and returns one logical per element of its left-hand side. Use has_element() for strict, scalar checks against a list, and %in% for fast membership tests on atomic vectors where type coercion is acceptable.
Does has_element() work on atomic vectors?
Yes. has_element() accepts both lists and atomic vectors as its first argument. With an atomic vector it compares the target against each scalar element in turn, for example has_element(letters, "q") returns TRUE. The behavior is identical to using it on a list; only the input structure differs.
Why does has_element() return FALSE when the value looks present?
The most common cause is a type mismatch. has_element() uses identical(), which treats the double 2 and the integer 2L as different. If a list holds integers and you search for a plain numeric literal, the call returns FALSE. Match the type with 2L, or use %in%, which coerces types automatically.
Can has_element() search nested lists?
No. has_element() only inspects the top level of the list and does not recurse into nested elements. To search a deeply nested structure, flatten it first with purrr::flatten() or unlist(), then call has_element() on the flattened result.
What does has_element() return?
It always returns a single logical value, either TRUE or FALSE, regardless of how long the input list or vector is. This scalar return is what makes it safe to use directly inside an if () condition without wrapping it in any().