> for {:ok, num} <- list, strict: true, do: num

Agreed, this is more or less exactly what I was pitching.

On Wednesday, June 9, 2021 at 10:16:25 PM UTC-7 [email protected] wrote:

> I would like to add a solution within the existing language:
>
>
> ```elixir
> > list = [{:ok, 1}, {:ok, 2}, {:error, :fail}, {:ok, 4}]
> > for el <- list, do: ({:ok, num} = el; num)
> ** (MatchError) no match of right hand side value: {:error, :fail}
> ```
> I think this is reasonable.
>
> Acctually the built in filtering in `for` caught me off guard, I was 
> expecting for to fail unless all elements matched. So for me the better 
> solution would be to always make matching in `for` strict. But I guess this 
> is too late now for backwards compatibility. Another alternative to `for!` 
> would be:
>
> ```elixir
> > list = [{:ok, 1}, {:ok, 2}, {:error, :fail}, {:ok, 4}]
> > for {:ok, num} <- list, strict: true, do: num
> ** (MatchError) no match of right hand side value: {:error, :fail}
> ```
>
> I don't like the use of the exclamation mark in `for!` because it has 
> little meaning relative to the existing use of the exclamation mark in 
> Elixir.
>
> onsdag 9. juni 2021 kl. 13:17:04 UTC+2 skrev [email protected]:
>
>> 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] 
>> 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].
>> 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/f4d5c0be-567a-4a7d-9b39-68202226c788n%40googlegroups.com.

Reply via email to