I also love the proposal.

It's a shame we can't re-use the `with` semantics of `=` raising a match error 
in the for.

My two cents is `for!` makes the most sense, and follows the conventions of 
other functions.

Best

Adam

> On 8 Jun 2021, at 18:18, Christopher Keele <[email protected]> wrote:
> 
> This feature would be very useful, I've experience this signature-change pain 
> point before too (and kind of have been avoiding `for` ever since, TBH).
> 
> I'm reluctant to increase the surface area of the language itself, what do 
> you think about adding a `:strict` option to `for` instead of a new special 
> form/kernel macro/operator?
> 
> On Monday, June 7, 2021 at 9:50:45 AM UTC-7 [email protected] 
> <http://gmail.com/> wrote:
> ## Background
> 
> `for` comprehensions are one of the most powerful features in Elixir. It 
> supports both enumerable and bitstring generators, filters through boolean 
> expressions and pattern matching, collectibles with `:into` and folding with 
> `:reduce`.
> 
> One of the features are automatic filtering by patterns in generators:
> 
> ```elixir
> list = [{:ok, 1}, {:ok, 2}, {:error, :fail}, {:ok, 4}]
> for {:ok, num} <- list, do: num
> #=> [1, 2, 4]
> ```
> 
> Generator filtering is very powerful because it allows you to succinctly 
> filter out data that is not relevant to the comprehension in the same 
> expression that you are generating elements out of your 
> enumerable/bitstrings. But the implicit filtering can be dangerous because 
> changes in the shape of the data will silently be removed which can cause 
> hard to catch bugs.
> 
> The following example can show how this can be an issue when testing 
> `Posts.create/0`. If a change causes the function to start returning `{:ok, 
> %Post{}}` instead of the expected `%Post{}` the test will pass even though we 
> have a bug.
> 
> ```elixir
> test "create posts" do
>   posts = Posts.create()
>   for %Post{id: id} <- posts, do: assert is_integer(id)
> end
> ```
> 
> The example uses a test to highlight the issue but it can just as well happen 
> in production code, specially when refactoring in other parts of the code 
> base than the comprehension.
> 
> Elixir is a dynamically typed language but dynamic typing errors are less of 
> an issue compared to many other dynamic languages because we are usual strict 
> in the data we accept by using pattern matching and guard functions. `for` is 
> by design not strict on the shape of data it accepts and therefor loses the 
> nice property of early failure on incorrect data.
> 
> ## Proposal
> 
> I propose an alternative comprehension macro called `for!` that has the same 
> functionality as `for` but instead of filtering on patterns in generators it 
> will raise a `MatchError`.
> 
> ```elixir
> posts = [{:ok, %Post{}}]
> for! %Post{id: id} <- posts, do: assert is_integer(id)
> #=> ** (MatchError) no match of right hand side value: {:ok, %Post{}}
> ```
> 
> Pattern matching when not generating values with `=` remains unchanged.
> 
> `for!` gives the developer an option to be strict on the data it accepts 
> instead of silently ignoring data that does not match.
> 
> ## Other considerations
> 
> You can get strict matching with `for` today by first assigning to a 
> variable. This way you can also mix filtering and strict matching patterns.
> 
> ```elixir
> posts = [{:ok, %Post{}}]
> for post <- posts,
>     %Post{id: id} = post,
>     do: assert is_integer(id)
> #=> ** (MatchError) no match of right hand side value: {:ok, %Post{}}
> ```
> 
> Another alternative is to introduce a new operator such as `<<-` (the actual 
> token can be anything, `<<-` is only used as an example) for raising pattern 
> matches instead of introducing a completely new macro.
> 
> ```elixir
> posts = [{:ok, %Post{}}]
> for %Post{id: id} <<- posts, do: assert is_integer(id)
> #=> ** (MatchError) no match of right hand side value: {:ok, %Post{}}
> ```
> 
> A downside of adding new functions or macros is that it doesn't compose as 
> well compared to adding options (or operators) to existing functions. If we 
> want to add another variant of comprehensions in the future we might be in 
> the position that we need 4 macros, and then 8 and so on.
> 
> Another benefit of adding an operator is that you can mix both `<-` and `<<-` 
> in a single comprehension.
> 
> The downside of an operator is that it adds more complexity for the language 
> user. We would also need an operator that is visually close to `<-` but still 
> distinctive enough that they are easy to separate since their behavior are 
> very difference.
> 
> -- 
> 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 [email protected] 
> <mailto:[email protected]>.
> To view this discussion on the web visit 
> https://groups.google.com/d/msgid/elixir-lang-core/42adcfba-12d8-4469-a156-f412b0d290a9n%40googlegroups.com
>  
> <https://groups.google.com/d/msgid/elixir-lang-core/42adcfba-12d8-4469-a156-f412b0d290a9n%40googlegroups.com?utm_medium=email&utm_source=footer>.

-- 
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 [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/5E4E868D-88E9-4B5E-9A3B-25F330530687%40a-corp.co.uk.

Reply via email to