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

Last change on this file since 40030 was 40030, checked in by Diego, 5 months ago

srfi-197: clean up wiki formatting

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