diff --git a/eeps/eep-0077.md b/eeps/eep-0077.md new file mode 100644 index 0000000..ccd6c9f --- /dev/null +++ b/eeps/eep-0077.md @@ -0,0 +1,121 @@ + Author: Richard Carlsson + Status: Draft + Type: Standards Track + Created: 10-Jan-2025 + Erlang-Version: OTP-28.0 + Post-History: +**** +EEP 77: Assignment in Comprehensions +---- + +Abstract +======== + +This EEP adds assignments to comprehension qualifier lists, providing +a convenient and readable alternative to other syntactical tricks. + +Rationale +========= + +It would often be useful to be able to easily bind variables in the +qualifier sequence of a comprehension, for example: + +```erlang +[Z || X <- Ls, + {foo, Y} = g(X), + Z <- f(Y, h(Y))]. +``` + +using plain `Pattern = ...,` entries between qualifiers. + +You can do this today by writing a singleton generator: + +```erlang +[Z || X <- Ls, + {foo, Y} <- [g(X)], % produce single element + Z <- f(Y, h(Y))]. +``` + +but this has some drawbacks: + +- The intent is not clear to the reader +- You have to remember to write a single element list around the + right hand side argument (which itself could be a list) +- You probably want it to be a strict generator so typos don't just + silently yield no elements +- Someone editing the code later may accidentally add extra + elements to the right hand side list, causing unintended Cartesian + combinations + +Another trick is to piggy-back on a boolean test, using export of +bindings from within a subexpression to propagate to the subsequent +qualifiers: + +```erlang +[Z || X <- Ls, + is_tuple(Y = g(X)), + Z <- f(Y, h(element(2,Y)))]. +``` + +which is very much not a recommended style in Erlang, making it hard +to see where the bindings come from. It also relies on having a +suitable test as part of the qualifiers for the value you want to +bind. If you don't, it is possible to invent a dummy always-true test: + +```erlang +[Z || X <- Ls, + foobar =/= (Y = g(X)), + Z <- f(Y, h(Y))]. +``` + +Such tricks are very bad for readability and maintenance of the code, +making the logic hard to follow. + +Specification +======================== + +It is in fact already allowed syntactically to have a `Pattern = ...` +match expression in the qualifiers. This however gets interpreted as +any other expression - thus expected to produce a boolean value - and +if `false`, the current element will be skipped. + +Hence, any qualifier following after a match `Var = Expr` will only be +executed if `Var` has the value `true`. We can therefore expect that +no such uses exist in practice, because `Var` would be fully +redundant. (The OTP code base has been checked and does not contain +any.) To avoid incompatibilities with any possible existing code that +relies on the current behaviour, the new semantics can be implemented +as an optional feature at first, and an error be reported for such +assignments when the feature flag is not enabled. + +The main change needed is then for the compiler to detect a match +expression `Pattern = Expr` in the qualifier list, and treat it as a +strict singleton generator `Pattern <-:- [Expr]`. + +Reference Implementation +------------------------ + +A [reference implementation][GitHub branch] exists in the +`lc-match-operator` branch of the author's GitHub account, together with a +[GitHub pull request][GitHub PR] to the Erlang/OTP repository. + +[GitHub branch]: https://github.com/richcarl/otp/tree/lc-match-operator + "Reference implementation branch on GitHub" + +[GitHub PR]: https://github.com/erlang/otp/pull/9153 + "GitHub Pull Request" + +Copyright +========= + +This document is placed in the public domain or under the CC0-1.0-Universal +license, whichever is more permissive. + +[EmacsVar]: <> "Local Variables:" +[EmacsVar]: <> "mode: indented-text" +[EmacsVar]: <> "indent-tabs-mode: nil" +[EmacsVar]: <> "sentence-end-double-space: t" +[EmacsVar]: <> "fill-column: 70" +[EmacsVar]: <> "coding: utf-8" +[EmacsVar]: <> "End:" +[VimVar]: <> " vim: set fileencoding=utf-8 expandtab shiftwidth=4 softtabstop=4: "