|
| 1 | + Author: Michał Muskała <micmus(at)whatsapp(dot)com> |
| 2 | + Status: Draft |
| 3 | + Type: ... |
| 4 | + Created: 16-12-2024 |
| 5 | + Erlang-Version: ... |
| 6 | + Post-History: ... |
| 7 | +**** |
| 8 | +EEP 78: Multi-valued comprehensions |
| 9 | +---- |
| 10 | + |
| 11 | +Abstract |
| 12 | +======== |
| 13 | + |
| 14 | +This EEP proposes enhancing the comprehension syntax to allow emitting multiple |
| 15 | +elements in a single iteration of the comprehension loop - effectively enhancing |
| 16 | +comprehensions to implement `flatmap` with a fixed number of elements. |
| 17 | + |
| 18 | +Rationale |
| 19 | +========= |
| 20 | + |
| 21 | +Comprehensions in Erlang are a very flexible and convinent way of implementing many |
| 22 | +iteration and looping patterns. However, there are some cases that end up unergonomic, |
| 23 | +notably, and where the scope of this EEP is, where we'd like to emit multiple elements |
| 24 | +from a single iteration. |
| 25 | + |
| 26 | +Existing forms of expressing this are awkward and introduce extra, unnecessary allocations. |
| 27 | +For example: |
| 28 | + |
| 29 | + lists:append([[X + 1, X + 2] || X <- Xs] |
| 30 | + [Tmp || X <- Xs, Tmp <- [X + 1, X + 2]] |
| 31 | + |
| 32 | +Both of those ways end up creating an extra allocation of a temporary 2-element list, introducing |
| 33 | +inefficiency, as well as are, arguably, harder to understand than necessary introducing extra function |
| 34 | +calls or variables. |
| 35 | + |
| 36 | +This EEP proposes enhancing comprehensions with the ability to do so using a very natural |
| 37 | +syntax extension of the existing comprehension syntax. In particular for list and map |
| 38 | +comprehensions: |
| 39 | + |
| 40 | + [X + 1, X + 2, ... || X <- Xs] |
| 41 | + |
| 42 | + #{K + 1 => V + 1, K + 2 => V + 2, ... || K := V <- Map} |
| 43 | + |
| 44 | +The semantics of map comprehensions where multiple keys in the same iteration would end up |
| 45 | +with the same value, should be the same as if the keys were emitted in subsequent iterations. |
| 46 | + |
| 47 | +Binary comprehensions already support this, and thus there's no enhancement to their syntax |
| 48 | +suggested in this EEP, for example: |
| 49 | + |
| 50 | + << <<(X + 1), (X + 2)>> || <<X>> <= Bin>>. |
| 51 | + |
| 52 | +Parsing & Abstract Forms |
| 53 | +============== |
| 54 | + |
| 55 | +Today, the [comprehension abstract forms](https://www.erlang.org/doc/apps/erts/absform.html#expressions) are defined as: |
| 56 | + |
| 57 | +- If `E` is a list comprehension `[E_0 || Q_1, ..., Q_k]`, where each `Q_i` is a |
| 58 | + qualifier, then `Rep(E) = {lc,ANNO,Rep(E_0),[Rep(Q_1), ..., Rep(Q_k)]}`. For |
| 59 | + `Rep(Q)`, see below. |
| 60 | +- If `E` is a map comprehension `#{E_0 || Q_1, ..., Q_k}`, where `E_0` is an |
| 61 | + association `K => V` and each `Q_i` is a qualifier, then `Rep(E) = |
| 62 | + {mc,ANNO,Rep(E_0),[Rep(Q_1), ..., Rep(Q_k)]}`. For `Rep(E_0)` and `Rep(Q)`, see |
| 63 | + below. |
| 64 | + |
| 65 | +This EEP proposes to change the representation, in a fairly backwards-compatible way |
| 66 | +to include the representation of `E_0` directly, if there's just one element emitted, |
| 67 | +or a list of elements, if there's more than one. This slightly complicates the implementation |
| 68 | +(vs always emitting a list), but retains backwards-compatibility of the AST for code that |
| 69 | +exists today. As such, the definition after the changes would read: |
| 70 | + |
| 71 | +- If `E` is a list comprehension `[E_0, ..., E_k || Q_1, ..., Q_k]`, where each `Q_i` is a |
| 72 | + qualifier, then `Rep(E) = {lc,ANNO,Rep(Es),[Rep(Q_1), ..., Rep(Q_k)]}`. `Rep(Es) = Rep(E_0)`, |
| 73 | + if there's just one expression or `Rep(Es) = [Rep(E_0), ..., Rep(E_k)]` if there's many. For |
| 74 | + `Rep(Q)`, see below. |
| 75 | +- If `E` is a map comprehension `#{E_0, ..., E_k || Q_1, ..., Q_k}`, where `E_i` is an |
| 76 | + association `K => V` and each `Q_i` is a qualifier, then `Rep(E) = |
| 77 | + {mc,ANNO,Rep(Es),[Rep(Q_1), ..., Rep(Q_k)]}`. `Rep(Es) = Rep(E_0)`, |
| 78 | + if there's just one expression or `Rep(Es) = [Rep(E_0), ..., Rep(E_k)]` if there's many. |
| 79 | + For `Rep(E_0)` and `Rep(Q)`, see below. |
| 80 | + |
| 81 | +For example the following expressions: |
| 82 | + |
| 83 | + [X || X <- Xs] |
| 84 | + [X, X || X <- Xs] |
| 85 | + #{K => V || K := V <- Map} |
| 86 | + #{K => V, K => V || K := V <- Map} |
| 87 | + |
| 88 | +Would have the following representations (where `_` is substituted for corresponding `Anno` values): |
| 89 | + |
| 90 | + {lc,_,{var,_,'X'},[{generate,_,{var,_,'X'},{var,_,'Xs'}}]} |
| 91 | + {lc,_,[{var,_,'X'},{var,_,'X'}],[{generate,_,{var,_,'X'},{var,_,'Xs'}}]} |
| 92 | + {mc,_,{map_field_assoc,_,{var,_,'K'},{var,_,'V'}},[ |
| 93 | + {m_generate,_,{map_field_exact,_,{var,_,'K'},{var,_,'V'}},{var,_,'Map'}}}} |
| 94 | + ]} |
| 95 | + {mc,_,[{map_field_assoc,_,{var,_,'K'},{var,_,'V'}},{map_field_assoc,_,{var,_,'K'},{var,_,'V'}}],[ |
| 96 | + {m_generate,_,{map_field_exact,_,{var,_,'K'},{var,_,'V'}},{var,_,'Map'}}}} |
| 97 | + ]} |
| 98 | + |
| 99 | +Backwards compatibility |
| 100 | +======================== |
| 101 | + |
| 102 | +For code that does not use this new feature, nothing changes. For code that uses this new feature |
| 103 | +parse transforms or any tools using abstract forms, would need to be updated. |
| 104 | + |
| 105 | +Reference Implementation |
| 106 | +======================== |
| 107 | + |
| 108 | +https://github.com/erlang/otp/pull/9374 |
| 109 | + |
| 110 | +Copyright |
| 111 | +========= |
| 112 | + |
| 113 | +This document is placed in the public domain or under the CC0-1.0-Universal |
| 114 | +license, whichever is more permissive. |
0 commit comments