Skip to content

Commit 1de4e51

Browse files
Merge pull request #75 from michalmuskala/multi-comp
EEP 78: Multi-valued comprehensions
2 parents 7c0dd6f + 21aed50 commit 1de4e51

File tree

1 file changed

+114
-0
lines changed

1 file changed

+114
-0
lines changed

eeps/eep-0078.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
[PR #9375](https://github.com/erlang/otp/pull/9375)
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

Comments
 (0)