Sorry, I meant to someone reading the code. The strict option is modifying the behavior of the operator <-, which may be quite before it in the text.
I prefer for! in this case as it is upfront. On Fri, Jun 11, 2021 at 00:09 Christopher Keele <[email protected]> wrote: > > My concern with :strict is that it changes the behavior considerably of > the generators but it may show up only quite later on, far from them, > especially if you have multiple filters. > > Could you elaborate? I don't quite think I understand, particularly *"[the > behaviour] may show up only quite later on"* > > Does "quite later" here refer to code distance (the MatchError's > stacktrace would point away from/bury the for location)? Or temporal > distance? > > On Thursday, June 10, 2021 at 2:58:03 PM UTC-7 José Valim wrote: > >> My concern with :strict is that it changes the behavior considerably of >> the generators but it may show up only quite later on, far from them, >> especially if you have multiple filters. >> >> On Thu, Jun 10, 2021 at 23:56 Christopher Keele <[email protected]> >> wrote: >> >>> > 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 >>> <https://groups.google.com/d/msgid/elixir-lang-core/f4d5c0be-567a-4a7d-9b39-68202226c788n%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/0ce03abc-61bb-4423-b6a8-704d1d62169fn%40googlegroups.com > <https://groups.google.com/d/msgid/elixir-lang-core/0ce03abc-61bb-4423-b6a8-704d1d62169fn%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/CAGnRm4K01hBRkjLaRPj5ktViNNjYqdFbKdysvFcDVG%3DgBp78dA%40mail.gmail.com.
