source: project/wiki/eggref/5/srfi-197 @ 39964

Last change on this file since 39964 was 39964, checked in by anonymous, 5 months ago

eggref/5: add "Repository" section to the documentation of egggs missing it

File size: 15.5 KB
Line 
1== srfi-197: Pipeline Operators
2=== Abstract
3Many functional languages provide pipeline operators, like Clojure's -> or OCaml's |>. Pipelines are a simple, terse, and readable way to write deeply-nested expressions. This SRFI defines a family of chain and nest pipeline operators, which can rewrite nested expressions like {{(a b (c d (e f g)))}} as a sequence of operations: {{(chain g (e f _) (c d _) (a b _))}}.
4
5For more information see: [[https://srfi.schemers.org/srfi-197/|197: Pipeline Operators]]
6=== Rationale
7Deeply-nested expressions are a common problem in all functional languages, especially Lisps. Excessive nesting can result in deep indentation and parenthesis-matching
8errors.
9
10<enscript highlight="scheme">
11; Quick, how many close parentheses are there?
12(eta (zeta (epsilon (delta (gamma (beta (alpha)))))))
13</enscript>
14
15Additionally, some expressions sound more natural when written inside out, as a sequence of steps from start to finish.
16
17<enscript highlight="scheme">
18; This recipe looks
 backwards.
19(bake (pour (mix (add eggs (add sugar (add flour bowl))))) (fahrenheit 350))
20</enscript>
21
22Many functional languages solve this by introducing pipeline operators. This SRFI defines a chain operator inspired by Clojure's threading macros, but with _ as an
23argument placeholder, a notation also used in SRFI 156.
24
25<enscript highlight="scheme">
26(chain (alpha) (beta _) (gamma _) (delta _) (epsilon _) (zeta _) (eta _))
27
28(chain bowl
29       (add flour _)
30       (add sugar _)
31       (add eggs _)
32       (mix _)
33       (pour _)
34       (bake _ (fahrenheit 350)))
35</enscript>
36
37Pipelines are especially useful for nested list and vector operations.
38
39<enscript highlight="scheme">
40(chain xs
41       (map (lambda (x) (+ x 1) _)
42       (filter odd? _)
43       (fold * 1 _))
44</enscript>
45
46Scheme already provides an idiomatic way to chain expressions in let* and SRFI 2 {{and-let*}}, but the primary advantage of chain is terseness and the accompanying
47readability. This focus on readability and reduced nesting is similar in spirit to SRFI 156 and SRFI 26.
48
49Compared to an equivalent {{let*}} expression, chain removes two levels of parenthesis nesting, does not define any intermediate variables, and allows mixing single and
50multiple return values.
51
52To demonstrate the difference in verbosity, here is the let* equivalent of the recipe expression:
53
54<enscript highlight="scheme">
55(let* ((x bowl)
56       (x (add flour x))
57       (x (add sugar x))
58       (x (add eggs x))
59       (x (mix x))
60       (x (pour x)))
61  (bake x (fahrenheit 350)))
62</enscript>
63
64Like let*, chain guarantees evaluation order. In fact, {{(chain a (b _) (c _))}} expands to something like {{(let* ((x (b a)) (x (c x))) x)}}, not {{(c (b a))}}, and so chain is not suitable for pipelines containing syntax like if or let.
65
66For pipelines containing complex syntax, the nest and nest-reverse operators look like chain but are guaranteed to expand to nested forms, not let* forms. nest nests in the opposite direction of chain, so {{(nest (a _) (b _) c)}} expands to {{(a (b c))}}.
67=== Specification
68==== chain
69
70<procedure>(chain <initial-value> [<placeholder> [<ellipsis>]] <step> ...)</procedure>
71
72==== Syntax
73
74<parameter><initial-value></parameter>
75
76{{<initial-value>}} is an expression.
77
78
79<parameter><placeholder></parameter>
80
81
82<parameter><ellipsis></parameter>
83
84
85{{<placeholder>}} and {{<ellipsis>}} are literal symbols; these are the placeholder symbol and ellipsis symbol. If {{<placeholder>}} or {{<ellipsis>}} are not present, they default to _ and ..., respectively.
86
87<parameter><step></parameter>
88
89The syntax of {{<step>}} is (<datum> ...), where each {{<datum>}} is either the placeholder symbol, the ellipsis symbol, or an expression. A {{<step>}} must contain at least one {{<datum>}}. The ellipsis symbol is only allowed at the end of a {{<step>}}, and it must immediately follow a placeholder symbol.
90==== Semantics
91chain evaluates each {{<step>}} in order from left to right, passing the result of each step to the next.
92
93Each {{<step>}} is evaluated as an application, and the return value(s) of that application are passed to the next step as its pipeline values. {{<initial-value>}} is the pipeline value of the first step. The return value(s) of chain are the return value(s) of the last step.
94
95The placeholder symbols in each {{<step>}} are replaced with that step's pipeline values, in the order they appear. It is an error if the number of placeholders for a step does not equal the number of pipeline values for that step, unless the step contains no placeholders, in which case it will ignore its pipeline values.
96
97<enscript highlight="scheme">
98(chain x (a b _)) ; => (a b x)
99(chain (a b) (c _ d) (e f _)) ; => (let* ((x (a b)) (x (c x d))) (e f x))
100(chain (a) (b _ _) (c _)) ; => (let*-values (((x1 x2) (a)) ((x) (b x1 x2))) (c x))
101</enscript>
102
103If a {{<step>}} ends with a placeholder symbol followed by an ellipsis symbol, that placeholder sequence is replaced with all remaining pipeline values that do not have a matching placeholder.
104
105<enscript highlight="scheme">
106(chain (a) (b _ c _ ...) (d _))
107; => (let*-values (((x1 . x2) (a)) ((x) (apply b x1 c x2))) (d x))
108</enscript>
109
110chain and all other SRFI 197 macros support custom placeholder symbols, which can help to preserve hygiene when used in the body of a syntax definition that may insert a {{_}} or {{...}}.
111
112<enscript highlight="scheme">
113(chain (a b) <> (c <> d) (e f <>))
114 ; => (let* ((x (a b)) (x (c x d))) (e f x))
115(chain (a) - --- (b - c - ---) (d -))
116; => (let*-values (((x1 . x2) (a)) ((x) (apply b x1 c x2))) (d x))
117</enscript>
118==== chain-and
119
120<procedure>(chain-and <initial-value> [<placeholder>] <step> ...)</procedure>
121
122===== Syntax
123
124<parameter><initial-value></parameter>
125
126{{<initial-value>}} is an expression.
127
128
129<parameter><placeholder></parameter>
130
131{{<placeholder>}} is a literal symbol; this is the placeholder symbol. If {{<placeholder>}} is not present, the placeholder symbol is {{_}}.
132
133
134<parameter><step></parameter>
135
136The syntax of {{<step>}} is (<datum> ... [<_> <datum> ...]), where {{<_>}} is the placeholder symbol.
137===== Semantics
138A variant of chain that short-circuits and returns {{#f}} if any step returns {{#f}}. chain-and is to chain as SRFI 2 {{and-let*}} is to {{let*}}.
139
140Each {{<step>}} is evaluated as an application. If the step evaluates to {{#f}}, the remaining steps are not evaluated, and chain-and returns {{#f}}. Otherwise, the return value of the step is passed to the next step as its pipeline value. {{<initial-value>}} is the pipeline value of the first step. If no step evaluates to {{#f}}, the return value of chain-and is the return value of the last step.
141
142The {{<_>}} placeholder in each {{<step>}} is replaced with that step's pipeline value. If a {{<step>}} does not contain {{<_>}}, it will ignore its pipeline value, but chain-and will still check whether that pipeline value is {{#f}}.
143
144Because chain-and checks the return value of each step, it does not support steps with multiple return values. It is an error if a step returns more than one value.
145==== chain-when
146
147<procedure>(chain-when <initial-value> [<placeholder>] ([<guard>] <step>) ...)</procedure>
148
149===== Syntax
150{{<initial-value>}} and {{<guard>}} are expressions. {{<placeholder>}} is a literal symbol; this is the placeholder symbol. If {{<placeholder>}} is not present, the placeholder symbol is _. The syntax of {{<step>}} is (<datum> ... [<_> <datum> ...]), where {{<_>}} is the placeholder symbol.
151===== Semantics
152A variant of chain in which each step has a guard expression and will be skipped if the guard expression evaluates to {{#f}}.
153===== Example
154<enscript highlight="scheme">
155(define (describe-number n)
156  (chain-when '()
157    ((odd? n) (cons "odd" _))
158    ((even? n) (cons "even" _))
159    ((zero? n) (cons "zero" _))
160    ((positive? n) (cons "positive" _))))
161
162(describe-number 3) ; => '("positive" "odd")
163(describe-number 4) ; => '("positive" "even")
164</enscript>
165===== Description
166Each {{<step>}} is evaluated as an application. The return value of the step is passed to the next step as its pipeline value. {{<initial-value>}} is the pipeline value of the first step.
167
168The {{<_>}} placeholder in each {{<step>}} is replaced with that step's pipeline value. If a {{<step>}} does not contain {{<_>}}, it will ignore its pipeline value
169
170If a step's {{<guard>}} is present and evaluates to {{#f}}, that step will be skipped, and its pipeline value will be reused as the pipeline value of the next step. The return value of chain-when is the return value of the last non-skipped step, or {{<initial-value>}} if all steps are skipped.
171
172Because chain-when may skip steps, it does not support steps with multiple return values. It is an error if a step returns more than one value.
173==== chain-lambda
174
175<procedure>(chain-lambda [<placeholder> [<ellipsis>]] <step> ...)</procedure>
176
177===== Syntax
178
179<parameter><placeholder></parameter>
180
181
182<parameter><ellipsis></parameter>
183
184{{<placeholder>}} and {{<ellipsis>}} are literal symbols these are the placeholder symbol and ellipsis symbol. If {{<placeholder>}} or {{<ellipsis>}} are not present, they default to _ and ..., respectively.
185
186
187<parameter><step></parameter>
188
189The syntax of {{<step>}} is (<datum> ...), where each {{<datum>}} is either the placeholder symbol, the ellipsis symbol, or an expression. A {{<step>}} must contain at least one {{<datum>}}. The ellipsis symbol is only allowed at the end of a {{<step>}}, and it must immediately follow a placeholder symbol.
190===== Semantics
191Creates a procedure from a sequence of chain steps. When called, a {{chain-lambda}} procedure evaluates each {{<step>}} in order from left to right, passing the result of each step to the next.
192
193<enscript highlight="scheme">
194(chain-lambda (a _) (b _)) ; => (lambda (x) (let* ((x (a x))) (b x)))
195(chain-lambda (a _ _) (b c _)) ; => (lambda (x1 x2) (let* ((x (a x1 x2))) (b c x)))
196</enscript>
197
198Each {{<step>}} is evaluated as an application, and the return value(s) of that application are passed to the next step as its pipeline values. The procedure's arguments are the pipeline values of the first step. The return value(s) of the procedure are the return value(s) of the last step.
199
200The placeholder symbols in each {{<step>}} are replaced with that step's pipeline values, in the order they appear. It is an error if the number of placeholders for a step does not equal the number of pipeline values for that step, unless the step contains no placeholders, in which case it will ignore its pipeline values.
201
202If a {{<step>}} ends with a placeholder symbol followed by an ellipsis symbol, that placeholder sequence is replaced with all remaining pipeline values that do not have a matching placeholder.
203
204The number of placeholders in the first {{<step>}} determines the arity of the procedure. If the first step ends with an ellipsis symbol, the procedure is variadic.
205==== nest
206
207<procedure>(nest [<placeholder>] <step> ... <initial-value>)</procedure>
208
209===== Syntax
210
211<parameter><placeholder></parameter>
212
213{{<placeholder>}} is a literal symbol; this is the placeholder symbol. If {{<placeholder>}} is not present, the placeholder symbol is _. The syntax of {{<step>}} is {{(<datum> ... <_> <datum> ...)}}, where {{<_>}} is the placeholder symbol. {{<initial-value>}} is expression.
214===== Semantics
215nest is similar to chain, but sequences its steps in the opposite order. Unlike chain, nest literally nests expressions; as a result, it does not provide the same strict evaluation order guarantees as chain.
216
217<enscript highlight="scheme">
218(nest (a b _) (c d _) e) ; => (a b (c d e))
219</enscript>
220
221A nest expression is evaluated by lexically replacing the {{<_>}} in the last {{<step>}} with {{<initial-value>}}, then replacing the {{<_>}} in the next-to-last {{<step>}} with that replacement, and so on until the {{<_>}} in the first {{<step>}} has been replaced. It is an error if the resulting final replacement is not an expression, which is then evaluated and its values are returned.
222
223Because it produces an actual nested form, nest can build expressions that chain cannot. For example, nest can build a quoted data structure:
224
225<enscript highlight="scheme">
226(nest '_ (1 2 _) (3 _ 5) (_) 4) ; => '(1 2 (3 (4) 5))
227</enscript>
228
229nest can also safely include special forms like if, let, lambda, or parameterize in a pipeline.
230
231A custom placeholder can be used to safely nest nest expressions.
232
233<enscript highlight="scheme">
234(nest (nest _2 '_2 (1 2 3 _2) _ 6)
235      (_ 5 _2)
236      4)
237; => '(1 2 3 (4 5 6))
238</enscript>
239==== nest-reverse
240
241<procedure>(nest-reverse <initial-value> [<placeholder>] <step> ...)</procedure>
242
243===== Syntax
244
245<parameter><initial-value></parameter>
246
247{{<initial-value>}} is an expression. {{<placeholder>}} is a literal symbol; this is the placeholder symbol. If {{<placeholder>}} is not present, the placeholder symbol is _.
248
249The syntax of {{<step>}} is (<datum> ... <_> <datum> ...), where {{<_>}} is the placeholder symbol.
250===== Semantics
251nest-reverse is variant of nest that nests in reverse order, which is the same order as chain.
252
253<enscript highlight="scheme">
254(nest-reverse e (c d _) (a b _)) ; => (a b (c d e))
255</enscript>
256
257A nest-reverse expression is evaluated by lexically replacing the {{<_>}} in the first {{<step>}} with {{<initial-value>}}, then replacing the {{<_>}} in the second {{<step>}} with that replacement, and so on until the {{<_>}} in the last {{<step>}} has been replaced. It is an error if the resulting final replacement is not an expression, which is then evaluated and its values are returned.
258=== Implementation
259A sample implementation is available on GitHub. This repository contains two portable SRFI 197 implementations, one in R7RS-small and syntax-rules, the other in R6RS and syntax-case. The only dependency of either implementation is SRFI 2. It includes an R7RS library wrapper and a test script.
260=== Acknowledgements
261Thanks to the participants in the SRFI 197 mailing list who helped me refine this SRFI, including Marc Nieper-Wißkirchen, Linus Björnstam, Shiro Kawai, Lassi Kortela, and John Cowan.
262
263Marc provided a paragraph that has been included (with only minor changes) in the Semantics section of the nest and nest-reverse macros.
264
265Thanks to Rich Hickey for Clojure and the original implementation of Clojure threading macros, and to Paulus Esterhazy for the (EPL licensed) threading macros
266documentation page, which was a source of inspiration and some of the examples in this document.
267=== Author
268Adam Nelson. Ported to Chicken Scheme 5 by Sergey Goldgaber.  Maintained by Diego A. Mundo.
269
270=== Repository
271
272[[https://code.dieggsy.com/srfi-197|https://code.dieggsy.com/srfi-197]]
273
274
275=== Copyright
276© 2020 Adam Nelson.
277
278Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
279
280The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
281
282THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
283=== Version history
284* [[https://github.com/diamond-lizard/srfi-197/releases/tag/0.1|0.1]] - Ported to Chicken Scheme 5
Note: See TracBrowser for help on using the repository browser.