I'm definitely sympathetic to that idea. I think part of what internal consistency buys us is predictability and therefore a quicker path to a good mental model about what the code is going to do. But the mental model is the more important thing. Which is just to say if we don't have internal consistency but we can get to a good mental model, then I think it might be okay.
I think given other functions that follow the same idea, seeing a `for!` would certainly communicate "right this is expected to raise under some condition" - at least to me. There's also not an obvious way to have `for` mimic `with` when I think about it because say you do this: ``` for [a, _] = [1, 2], do: ... ``` there is no way to distinguish it from a filter - where `=` should not raise a match error. I think you'd have to more clearly de-mark the difference between the generators and the filters, which feels like a big change. Best Adam > On 11 Jun 2021, at 00:13, Paul Schoenfelder <[email protected]> > wrote: > > I’m generally in favor of the option to have stricter semantics, but to me > the introduction of `for!` feels out of sync with other special forms, none > of which are bang-form. Furthermore, especially in contrast to `with`, you > end up with this weird dichotomy with the `<-` operator, where sometimes it > means a filtering match, and other times where it means strict match. That > kind of syntactical inconsistency in a language feels like a bad precedent to > set, despite what feels like a reasonable compromise. It’s also notable to me > that there are easy ways to program defensively to force match errors if you > want them, within the current syntax, but obviously that comes at the cost of > more verbosity. > > I’m not sure what the right answer is, but this feels to me like rushing to > solve a specific problem without spending enough time considering how it > meshes with the rest of the language in terms of cognitive complexity, > particularly for those new to the language. > > Anyway, that’s my two cents. I’m a fan of the concept for sure, but would > almost prefer to see the semantics changed in a major version bump, to match > `with`, even if that meant manually updating a bunch of my code, because at > least it keeps the language self consistent. I’ll admit I’m probably an > outlier on that though. > > Paul > > On Thu, Jun 10, 2021, at 6:16 PM, Christopher Keele wrote: >> That's fair enough! Though from my perspective both for! and strict: true >> would be about equally far from the <- where matches fail. But I can see the >> keyword format getting lost in the filters and other keywords. >> >> On Thu, Jun 10, 2021 at 3:14 PM José Valim <[email protected] >> <mailto:[email protected]>> wrote: >> 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] >> <mailto:[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 <christ...@ <>gmail.com >> <http://gmail.com/>> 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 tal...@ <>gmail.com >> <http://gmail.com/> 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 ad...@ <>a-corp.co.uk >> <http://a-corp.co.uk/>: >> 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 <christ...@ <>gmail.com >>> <http://gmail.com/>> 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 elixir-lang-co...@ <>googlegroups.com <http://googlegroups.com/>. >>> 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 elixir-lang-co...@ <>googlegroups.com <http://googlegroups.com/>. >> 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] >> <mailto:[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 a topic in the >> Google Groups "elixir-lang-core" group. >> To unsubscribe from this topic, visit >> https://groups.google.com/d/topic/elixir-lang-core/LEUD2alHPiE/unsubscribe >> <https://groups.google.com/d/topic/elixir-lang-core/LEUD2alHPiE/unsubscribe>. >> To unsubscribe from this group and all its topics, 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/CAGnRm4K01hBRkjLaRPj5ktViNNjYqdFbKdysvFcDVG%3DgBp78dA%40mail.gmail.com >> >> <https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4K01hBRkjLaRPj5ktViNNjYqdFbKdysvFcDVG%3DgBp78dA%40mail.gmail.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] >> <mailto:[email protected]>. >> To view this discussion on the web visit >> https://groups.google.com/d/msgid/elixir-lang-core/CAD9kT2QPn_prFiS%2BR9eemqA43DMvvOB8NrAweL2PgE_ZR2g6Cg%40mail.gmail.com >> >> <https://groups.google.com/d/msgid/elixir-lang-core/CAD9kT2QPn_prFiS%2BR9eemqA43DMvvOB8NrAweL2PgE_ZR2g6Cg%40mail.gmail.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] > <mailto:[email protected]>. > To view this discussion on the web visit > https://groups.google.com/d/msgid/elixir-lang-core/8a6cf634-cda5-4445-8230-4b7b69ed5ca8%40www.fastmail.com > > <https://groups.google.com/d/msgid/elixir-lang-core/8a6cf634-cda5-4445-8230-4b7b69ed5ca8%40www.fastmail.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/65609056-4A25-45FA-B91F-84D4DF292129%40a-corp.co.uk.
