[Proposal] Add strict comprehensions

42 views
Skip to first unread message

Hannes Steffenhagen

unread,
Jan 19, 2026, 8:30:20 AM (8 days ago) Jan 19
to elixir-lang-core
Right now, when you write a list comprehension with a pattern match, if the match fails the result is "discarded", so effectively the pattern match acts as a filter. This is convenient in some cases, but doesn't leave an obvious option to use when you wanted to use the pattern match only for destructuring, not filtering. E.g: in

for {x,y} <- pairs do...

We expect 'pairs' to to only contain pairs. It'd be unexpected for there to be anything else in there, but if by mistake (e.g. during refactoring) something else manages to sneak in there we may silently discard it which is a source of easy to miss bugs. This happened to us in the 'real world' during a refactor where we started to return a list of %{id:, name:} structs instead of a list of pairs from a function. Of course cases like that can often be caught with test coverage, but it'd be nice to have some way to explicitly say that we expect the pattern match to succeed, and raise if not, so cases that slip through the cracks don't lead to hard to trace bugs.

In Erlang, with https://www.erlang.org/eeps/eep-0070 we have strict comprehensions now with the syntax <:-.. I don't know if that syntax would be appropriate for Elixir, but if not I am sure we could think of some other syntax for it.

Apologies if this had already been discussed, I couldn't find a discussion on it other than a comment by Jose on this github issue that it would have to be something to be discussed separately: https://github.com/elixir-lang/elixir/issues/14148#issuecomment-3126519887

Bruce Tate

unread,
Jan 19, 2026, 8:40:30 AM (8 days ago) Jan 19
to elixir-l...@googlegroups.com
Maybe an option like strict: true, or mode: strict | match, or mode: match | destrcuture would work?

-bt

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/elixir-lang-core/34bd1126-03aa-4859-a23b-5c69bf351d3dn%40googlegroups.com.


--

Regards,
Bruce Tate
CEO

Amos King

unread,
Jan 19, 2026, 9:25:35 AM (8 days ago) Jan 19
to elixir-l...@googlegroups.com
An option would change the entire comprehension and not a single match which I think would be less than ideal.

I think of ‘for’ when I want to filter and combine permutations. If I want to match I generally use Enum.map and when the fun doesn’t match you get an error.

I personally like the strong distinction between when I want to use a ‘go’ vs Enum functions. A slightly different arrow can be easy to overlook when reading code or even when writing it.


Amos

On Jan 19, 2026, at 07:40, Bruce Tate <br...@grox.io> wrote:



Bruce Tate

unread,
Jan 19, 2026, 3:33:15 PM (7 days ago) Jan 19
to elixir-l...@googlegroups.com

Jean Klingler

unread,
Jan 19, 2026, 5:48:19 PM (7 days ago) Jan 19
to elixir-l...@googlegroups.com
The previous proposal on this topic: https://groups.google.com/g/elixir-lang-core/c/LEUD2alHPiE.

This is something I've been wanting for a while personally to avoid the silently filtering out issue, but I think it's worth mentioning as a counterpoint as well:
Hopefully the argument about being able to catch typos etc will be less and less of an issue as the type system makes progress, and can point out that the "clause will never match". It wouldn't catch a type that could match though, e.g. a {x, y} | nil union, so there's still an argument to be made for strict filtering.

Also, not a very satisfying solution, but strict filtering can be achieved today using this workaround:

for tuple <- tuples, {x, y} = tuple, do: ...

Benjamin Philip

unread,
Jan 20, 2026, 12:11:42 AM (7 days ago) Jan 20
to Jean Klingler, elixir-l...@googlegroups.com

Most of alternatives described here have been addressed in the Erlang EEP for strict comprehensions.

I think of ‘for’ when I want to filter and combine permutations. If I want to match I generally use Enum.map and when the fun doesn’t match you get an error.

Replacing the module with an Enum (or any stdlib) call would work for most datatypes, but not for bitstrings.

Also, not a very satisfying solution, but strict filtering can be achieved today using this workaround:

for tuple <- tuples, {x, y} = tuple, do: …

In the Erlang EEP, the main rationale for rejecting destructuring in filter was that it was quite awkward looking: . This doesn’t quite apply in Elixir.

This is also quite similar to destructuring in the comprehension body. In the Erlang EEP, the main reason for rejecting this was that you can’t re-use the variable name you use to match on each item has a whole (here tuple, and in the EEP, Usr) in following comprehensions. This obviously doesn’t apply in the context of Elixir as well.

However, the EEP also notes that the intent of strict generation is not conveyed by destructuring later. If I received something like this in a PR, my first thought would be that it was bad style, and request for destructuring within the match until I thought it through.

In Erlang, with https://www.erlang.org/eeps/eep-0070 we have strict comprehensions now with the syntax <:-.. I don’t know if that syntax would be appropriate for Elixir, but if not I am sure we could think of some other syntax for it.

Like Amos mentioned, <:- can be difficult to distinguish from <-. Generally, adding new syntax is a last resort since that adds complexity (not only for the compiler, but more importantly for users). Erlang had to introduce new syntax since its comprehension didn’t support being passed options. This is why I like Bruce’s suggestion:

Maybe an option like strict: true, or mode: strict | match, or mode: match | **destrcuture would work?

Of these, I like strict: true the best, since we will not have more than 2 “modes”.

An option would change the entire comprehension and not a single match which I think would be less than ideal.

Isn’t changing the behaviour of the iteration the goal? I see don’t how passing options in any different from switching to Enum?

– bp

Amos King

unread,
Jan 20, 2026, 8:40:43 AM (7 days ago) Jan 20
to elixir-l...@googlegroups.com
Benjamin,

I was thinking that I only want a line of the comprehension to change and not the whole thing to be strict.

with {x, y} <- positions,
  %Pawn{} <- white_pieces do
#
end

I might want positions to be strict but white pieces includes multiple types so I don’t want it strict.

If we can learn the pattern suggested with the ‘=‘ on a separate line then we begin to understand that pattern. We can also use

# this is gross but just tossing it out there.
pair <-  Enum.each(pairs, fn {_, _} -> nil end)

How do you intend on remembering that you want it strict when first writing the code and the match doesn’t fail? Would you always use strict as the default?

Have you attempted adding an implementation of this into your project and seeing how it affects behavior of the developers and if it reduces bugs? I’m curious how it has gone.

I also think about this a lot because I have seen where there is a ‘for’ used as a simple loop around some asserts in a test. Since ‘for’ filters if the type changes or there is something unexpected then the asserts are not run and the test passes happily. I’ve found that realizing that ‘for’ is a filter and pointing that out has changed behavior for the whole team. I’ve tried a few things and none of them have worked as well as pointing out what a ‘for’ does.

Amos

On Jan 19, 2026, at 23:11, Benjamin Philip <benjamin....@gmail.com> wrote:


Reply all
Reply to author
Forward
0 new messages