-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathsrfi-197.html
120 lines (116 loc) · 35.3 KB
/
srfi-197.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
<!--
SPDX-FileCopyrightText: 2020 Adam R. Nelson <[email protected]>
SPDX-License-Identifier: MIT
-->
<!DOCTYPE html><html lang="en"><head>
<meta charset="utf-8">
<title>SRFI 197: Pipeline Operators</title>
<link href="/favicon.png" rel="icon" sizes="192x192" type="image/png">
<link rel="stylesheet" href="https://srfi.schemers.org/srfi.css" type="text/css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>*{border-color:inherit}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box}*,::after,::before{flex-shrink:1}a,area,button,input,label,select,summary,textarea{touch-action:manipulation}html{font-family:sans-serif;line-height:1.15;text-size-adjust:100%}body{margin:0px}h1{font-size:2em;margin:0.67em 0px}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}a:active,a:hover{outline-width:0px}code,kbd,samp{font-family:monospace,monospace;font-size:1em}html{font-size:16px;-webkit-tap-highlight-color:transparent}body,html{height:100%}body{font-size:1rem;line-height:1.375;user-select:none !important}body,input,textarea{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Arial,sans-serif;font-size:16px}.button,.card,.card-block:not(.multiple),.card-footer:not(.multiple),.card-header:not(.multiple),.centerer,.container,.infobar,.label,.select,.spacer,.tagbox,.widget,address,dl,fieldset,footer,form,h1,h2,h3,h4,h5,h6,header,i.icon,img,input,label,ol,p,pre,textarea,ul{position:relative;margin:0px 0px 0.625rem}.button:last-child,.card-block:last-child:not(.multiple),.card-footer:last-child:not(.multiple),.card-header:last-child:not(.multiple),.card:last-child,.centerer:last-child,.container:last-child,.infobar:last-child,.label:last-child,.select:last-child,.spacer:last-child,.tagbox:last-child,.widget:last-child,address:last-child,dl:last-child,fieldset:last-child,footer:last-child,form:last-child,h1:last-child,h2:last-child,h3:last-child,h4:last-child,h5:last-child,h6:last-child,header:last-child,i.icon:last-child,img:last-child,input:last-child,label:last-child,ol:last-child,p:last-child,pre:last-child,textarea:last-child,ul:last-child{margin-bottom:0px}*{transition:opacity 0.15s ease 0s}.small,h5{font-size:0.840901em}.infobar-title:not(.xxxxsmall):not(.xxxsmall):not(.xxsmall):not(.xsmall):not(.small):not(.medium):not(.large):not(.xlarge):not(.xxlarge):not(.xxxlarge):not(.xxxxlarge),.large,h3{font-size:1.1892em}.xlarge,h2{font-size:1.5em}.xxlarge,h1{font-size:1.68176em}.button:target,.card:target,.container:target,.infobar:target,.label:target,.target,input:target,pre:target,textarea:target{animation:0.9s ease-in-out 0s 1 normal none running}.select select + label::after,code{font-family:Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;background-color:rgba(0,0,0,0.075);padding:0.09em 0.18em;border-radius:0.375em}pre > code{padding:0px;border:0px;background:transparent}h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500}a{touch-action:manipulation;cursor:pointer;overflow-wrap:break-word;word-break:break-word;color:inherit;text-decoration:none}address a:hover,li a:hover,p a:hover,td a:hover,th a:hover{text-decoration:underline}p{word-break:break-word;max-width:100%;overflow-wrap:break-word}p > *{margin-bottom:0px !important}pre{display:flex;padding:0.625rem}span > *{margin-bottom:0px !important}.theme-light pre{background-color:rgb(246,248,250);color:rgb(31,31,31)}.theme-light .preview{background-color:rgb(255,255,255);border-color:rgb(230,229,229);color:rgb(31,31,31)}.theme-light .preview h1,.theme-light .preview h2,.theme-light .preview h3,.theme-light .preview h4,.theme-light .preview h5,.theme-light .preview h6{border-color:rgb(234,236,239)}.theme-light .preview h1,.theme-light .preview h2,.theme-light .preview h3,.theme-light .preview h4,.theme-light .preview h5{color:inherit}.theme-light .preview code,.theme-light .preview li code,.theme-light .preview p code,.theme-light .preview table code{background-color:rgba(27,31,35,0.05) !important;color:inherit !important}.theme-light .preview pre code{background-color:transparent !important}.theme-light .preview address a,.theme-light .preview dd a,.theme-light .preview dt a,.theme-light .preview h1 a,.theme-light .preview h2 a,.theme-light .preview h3 a,.theme-light .preview h4 a,.theme-light .preview h5 a,.theme-light .preview h6 a,.theme-light .preview li a,.theme-light .preview p a,.theme-light .preview td a,.theme-light .preview th a{color:rgb(3,102,214)}.theme-light .preview address a:hover,.theme-light .preview dd a:hover,.theme-light .preview dt a:hover,.theme-light .preview h1 a:hover,.theme-light .preview h2 a:hover,.theme-light .preview h3 a:hover,.theme-light .preview h4 a:hover,.theme-light .preview h5 a:hover,.theme-light .preview h6 a:hover,.theme-light .preview li a:hover,.theme-light .preview p a:hover,.theme-light .preview td a:hover,.theme-light .preview th a:hover{color:rgb(3,95,199)}.theme-light .preview address a:active,.theme-light .preview dd a:active,.theme-light .preview dt a:active,.theme-light .preview h1 a:active,.theme-light .preview h2 a:active,.theme-light .preview h3 a:active,.theme-light .preview h4 a:active,.theme-light .preview h5 a:active,.theme-light .preview h6 a:active,.theme-light .preview li a:active,.theme-light .preview p a:active,.theme-light .preview td a:active,.theme-light .preview th a:active{color:rgb(3,88,184)}.preview .copy-wrapper{position:relative;margin-bottom:16px}.preview .copy-wrapper:last-child{margin-bottom:0px}.preview .copy-wrapper .copy{position:absolute;top:0.625rem;right:0.625rem;cursor:pointer;opacity:0;transition:opacity 0.15s ease 0s;z-index:1}.preview .copy-wrapper:hover .copy{opacity:0.5}.preview .copy-wrapper:hover .copy:hover{opacity:0.75}.preview .copy-wrapper:hover .copy:active{opacity:1}.preview .copy-wrapper > pre{margin-bottom:0px !important}.preview code,.preview pre{direction:ltr;font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace;hyphens:none;line-height:1.4;margin:1em 0px;overflow:auto;padding:1.2em;tab-size:2;text-align:left;white-space:pre;word-spacing:normal}.preview li code,.preview p code,.preview table code{font-size:85%}.preview{font-size:16px;line-height:inherit;user-select:text !important;padding:1.25rem !important}.preview > :first-child{margin-top:0px !important}.preview > :last-child{margin-bottom:0px !important}.preview blockquote,.preview dl,.preview ol,.preview p,.preview pre,.preview table,.preview ul{margin-top:0px;margin-bottom:16px}.preview blockquote:last-child,.preview dl:last-child,.preview ol:last-child,.preview p:last-child,.preview pre:last-child,.preview table:last-child,.preview ul:last-child{margin-bottom:0px}.preview h1,.preview h2,.preview h3,.preview h4,.preview h5,.preview h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}.preview h1:last-child,.preview h2:last-child,.preview h3:last-child,.preview h4:last-child,.preview h5:last-child,.preview h6:last-child{margin-bottom:0px}.preview h1 code,.preview h2 code,.preview h3 code,.preview h4 code,.preview h5 code,.preview h6 code{font-size:inherit}.preview h1{font-size:2em}.preview h1,.preview h2{padding-bottom:0.3em;border-bottom-width:1px;border-bottom-style:solid}.preview h2{font-size:1.5em}.preview h3{font-size:1.25em}.preview a:not([href]){color:inherit;text-decoration:none}.preview pre{display:block;overflow-wrap:normal;padding:16px;overflow:auto;line-height:1.45}.preview code,.preview pre{font-size:85%;border-radius:3px}.preview code{padding:0.2em 0.4em;margin:0px}.preview pre > code{padding:0px;margin:0px;font-size:100%;word-break:normal;white-space:pre;border:0px;background:transparent !important}.preview pre code{display:inline;padding:0px;margin:0px;overflow:visible;line-height:inherit;overflow-wrap:normal;background-color:transparent;border:0px}.mtk1{color:rgb(31,31,31)}.mtk4{color:rgb(106,115,125)}.mtk12{color:rgb(215,58,73)}.mtk21{color:rgb(224,0,0)}</style>
</head>
<body class="theme-light">
<div class="preview">
<h1 id="srfi-197-pipeline-operators"><a href="https://srfi.schemers.org/"><img class="srfi-logo" src="https://srfi.schemers.org/srfi-logo.svg" alt="SRFI logo" /></a>197: Pipeline Operators</h1>
<p id="author">by Adam Nelson</p>
<h2 id="status">Status</h2>
<p>This SRFI is currently in <em>final</em> status. Here is <a href="https://srfi.schemers.org/srfi-process.html">an explanation</a> of each status that a SRFI can hold. To provide input on this SRFI, please send email to <code><a href="mailto:srfi+minus+197+at+srfi+dotschemers+dot+org">srfi-197@<span class="antispam">nospam</span>srfi.schemers.org</a></code>. To subscribe to the list, follow <a href="https://srfi.schemers.org/srfi-list-subscribe.html">these instructions</a>. You can access previous messages via the mailing list <a href="https://srfi-email.schemers.org/srfi-197">archive</a>.</p>
<ul>
<li>Received: 2020-06-07</li>
<li>Draft #1 published: 2020-06-08</li>
<li>Draft #2 published: 2020-06-10</li>
<li>Draft #3 published: 2020-08-23</li>
<li>Draft #4 published: 2020-08-31</li>
<li>Draft #5 published: 2020-09-06</li>
<li>Finalized: 2020-10-12</li>
</ul>
<h2 id="abstract">Abstract</h2>
<p>Many functional languages provide pipeline operators, like Clojure's <code>-></code> or OCaml's <code>|></code>. Pipelines are a simple, terse, and readable way to write deeply-nested expressions. This SRFI defines a family of <code>chain</code> and <code>nest</code> pipeline operators, which can rewrite nested expressions like <code>(a b (c d (e f g)))</code> as a sequence of operations: <code>(chain g (e f _) (c d _) (a b _))</code>.</p>
<h1 id="rationale">Rationale</h1>
<p>Deeply-nested expressions are a common problem in all functional languages, especially Lisps. Excessive nesting can result in deep indentation and parenthesis-matching errors.</p>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk4">; Quick, how many close parentheses are there? </span></span><br><span><span class="mtk1">(eta (zeta (epsilon (delta (gamma (beta (alpha))))</span><span class="mtk1">))) </span></span><br></code></pre></div>
<p>Additionally, some expressions sound more natural when written inside out, as a sequence of steps from start to finish.</p>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk4">; This recipe looks… backwards.</span></span><br><span><span class="mtk1">(bake (pour (mix (add eggs (add sugar (add flour b</span><span class="mtk1">owl))))) (fahrenheit </span><span class="mtk21">350</span><span class="mtk1">))</span></span><br></code></pre></div>
<p>Many functional languages solve this by introducing pipeline operators. This SRFI defines a <code>chain</code> operator inspired by <a target="_blank" href="https://clojure.org/guides/threading_macros">Clojure's threading macros</a>, but with <code>_</code> as an argument placeholder, a notation also used in <a target="_blank" href="https://srfi.schemers.org/srfi-156/srfi-156.html">SRFI 156</a>.</p>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(chain (alpha) (beta _) (gamma _) (delta _) (epsil</span><span class="mtk1">on _) (zeta _) (eta _))</span></span><br><span><span> </span></span><br><span><span class="mtk1">(chain bowl</span></span><br><span><span class="mtk1"> (add flour _)</span></span><br><span><span class="mtk1"> (add sugar _)</span></span><br><span><span class="mtk1"> (add eggs _)</span></span><br><span><span class="mtk1"> (mix _)</span></span><br><span><span class="mtk1"> (pour _)</span></span><br><span><span class="mtk1"> (bake _ (fahrenheit </span><span class="mtk21">350</span><span class="mtk1">)))</span></span><br></code></pre></div>
<p>Pipelines are especially useful for nested list and vector operations.</p>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(chain xs</span></span><br><span><span class="mtk1"> (map (</span><span class="mtk12">lambda</span><span class="mtk1"> (x) (+ x </span><span class="mtk21">1</span><span class="mtk1">)) _)</span></span><br><span><span class="mtk1"> (filter odd? _)</span></span><br><span><span class="mtk1"> (fold * </span><span class="mtk21">1</span><span class="mtk1"> _))</span></span><br></code></pre></div>
<p>Scheme already provides an idiomatic way to chain expressions in <code>let*</code> and <a target="_blank" href="https://srfi.schemers.org/srfi-2/srfi-2.html">SRFI 2</a> <code>and-let*</code>, but the primary advantage of <code>chain</code> is terseness and the accompanying readability. This focus on readability and reduced nesting is similar in spirit to SRFI 156 and <a target="_blank" href="https://srfi.schemers.org/srfi-26/srfi-26.html">SRFI 26</a>.</p>
<p>Compared to an equivalent <code>let*</code> expression, <code>chain</code> removes two levels of parenthesis nesting, does not define any intermediate variables, and allows mixing single and multiple return values.</p>
<p>To demonstrate the difference in verbosity, here is the <code>let*</code> equivalent of the recipe expression:</p>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(let* ((x bowl)</span></span><br><span><span class="mtk1"> (x (add flour x))</span></span><br><span><span class="mtk1"> (x (add sugar x))</span></span><br><span><span class="mtk1"> (x (add eggs x))</span></span><br><span><span class="mtk1"> (x (mix x))</span></span><br><span><span class="mtk1"> (x (pour x)))</span></span><br><span><span class="mtk1"> (bake x (fahrenheit </span><span class="mtk21">350</span><span class="mtk1">)))</span></span><br></code></pre></div>
<p>Like <code>let*</code>, <code>chain</code> guarantees evaluation order. In fact, <code>(chain a (b _) (c _))</code> expands to something like <code>(let* ((x (b a)) (x (c x))) x)</code>, not <code>(c (b a))</code>, and so <code>chain</code> is not suitable for pipelines containing syntax like <code>if</code> or <code>let</code>.</p>
<p>For pipelines containing complex syntax, the <code>nest</code> and <code>nest-reverse</code> operators look like <code>chain</code> but are guaranteed to expand to nested forms, not <code>let*</code> forms. <code>nest</code> nests in the opposite direction of <code>chain</code>, so <code>(nest (a _) (b _) c)</code> expands to <code>(a (b c))</code>.</p>
<h2 id="specification">Specification</h2>
<h3 id="chain"><code>chain</code></h3>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(chain <initial-value> [<placeholder> [<ellipsis>]</span><span class="mtk1">] <step> ...)</span></span><br></code></pre></div>
<p><em>Syntax:</em> <code><initial-value></code> is an expression.</p>
<p><code><placeholder></code> and <code><ellipsis></code> are literal symbols; these are the <em>placeholder symbol</em> and <em>ellipsis symbol</em>. If <code><placeholder></code> or <code><ellipsis></code> are not present, they default to <code>_</code> and <code>...</code>, respectively.</p>
<p>The syntax of <code><step></code> is <code>(<datum> ...)</code>, where each <code><datum></code> is either the placeholder symbol, the ellipsis symbol, or an expression. A <code><step></code> must contain at least one <code><datum></code>. The ellipsis symbol is only allowed at the end of a <code><step></code>, and it must immediately follow a placeholder symbol.</p>
<p><em>Semantics:</em> <code>chain</code> evaluates each <code><step></code> in order from left to right, passing the result of each step to the next.</p>
<p>Each <code><step></code> is evaluated as an application, and the return value(s) of that application are passed to the next step as its <em>pipeline values</em>. <code><initial-value></code> is the pipeline value of the first step. The return value(s) of <code>chain</code> are the return value(s) of the last step.</p>
<p>The placeholder symbols in each <code><step></code> 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.</p>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(chain x (a b _)) </span><span class="mtk4">; => (a b x)</span></span><br><span><span class="mtk1">(chain (a b) (c _ d) (e f _)) </span><span class="mtk4">; => (let* ((x (a b)) (x (c x d))) (e f x))</span></span><br><span><span class="mtk1">(chain (a) (b _ _) (c _)) </span><span class="mtk4">; => (let*-values (((x1 x2) (a)) ((x) (b x1 x2))) </span><span class="mtk4">(c x))</span></span><br></code></pre></div>
<p>If a <code><step></code> 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.</p>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(chain (a) (b _ c _ ...) (d _))</span></span><br><span><span class="mtk4">; => (let*-values (((x1 . x2) (a)) ((x) (apply b x</span><span class="mtk4">1 c x2))) (d x))</span></span><br></code></pre></div>
<p><code>chain</code> 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 <code>_</code> or <code>...</code>.</p>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(chain (a b) <> (c <> d) (e f <>))</span></span><br><span><span class="mtk1"> </span><span class="mtk4">; => (let* ((x (a b)) (x (c x d))) (e f x))</span></span><br><span><span class="mtk1">(chain (a) - --- (b - c - ---) (d -))</span></span><br><span><span class="mtk4">; => (let*-values (((x1 . x2) (a)) ((x) (apply b x</span><span class="mtk4">1 c x2))) (d x))</span></span><br></code></pre></div>
<h3 id="chain-and"><code>chain-and</code></h3>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(chain-and <initial-value> [<placeholder>] <step> </span><span class="mtk1">...)</span></span><br></code></pre></div>
<p><em>Syntax:</em> <code><initial-value></code> is an expression. <code><placeholder></code> is a literal symbol; this is the <em>placeholder symbol</em>. If <code><placeholder></code> is not present, the placeholder symbol is <code>_</code>. The syntax of <code><step></code> is <code>(<datum> ... [<_> <datum> ...])</code>, where <code><_></code> is the placeholder symbol.</p>
<p><em>Semantics:</em> A variant of <code>chain</code> that short-circuits and returns <code>#f</code> if any step returns <code>#f</code>. <code>chain-and</code> is to <code>chain</code> as <a target="_blank" href="https://srfi.schemers.org/srfi-2/srfi-2.html">SRFI 2</a> <code>and-let*</code> is to <code>let*</code>.</p>
<p>Each <code><step></code> is evaluated as an application. If the step evaluates to <code>#f</code>, the remaining steps are not evaluated, and <code>chain-and</code> returns <code>#f</code>. Otherwise, the return value of the step is passed to the next step as its <em>pipeline value</em>. <code><initial-value></code> is the pipeline value of the first step. If no step evaluates to <code>#f</code>, the return value of <code>chain-and</code> is the return value of the last step.</p>
<p>The <code><_></code> placeholder in each <code><step></code> is replaced with that step's pipeline value. If a <code><step></code> does not contain <code><_></code>, it will ignore its pipeline value, but <code>chain-and</code> will still check whether that pipeline value is <code>#f</code>.</p>
<p>Because <code>chain-and</code> 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.</p>
<h3 id="chain-when"><code>chain-when</code></h3>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(chain-when <initial-value> [<placeholder>] ([<gua</span><span class="mtk1">rd>] <step>) ...)</span></span><br></code></pre></div>
<p><em>Syntax:</em> <code><initial-value></code> and <code><guard></code> are expressions. <code><placeholder></code> is a literal symbol; this is the <em>placeholder symbol</em>. If <code><placeholder></code> is not present, the placeholder symbol is <code>_</code>. The syntax of <code><step></code> is <code>(<datum> ... [<_> <datum> ...])</code>, where <code><_></code> is the placeholder symbol.</p>
<p><em>Semantics:</em> A variant of <code>chain</code> in which each step has a guard expression and will be skipped if the guard expression evaluates to <code>#f</code>.</p>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(</span><span class="mtk12">define</span><span class="mtk1"> (describe-number n)</span></span><br><span><span class="mtk1"> (chain-when '()</span></span><br><span><span class="mtk1"> ((odd? n) (</span><span class="mtk12">cons</span><span class="mtk1"> </span><span class="mtk1">"odd"</span><span class="mtk1"> _))</span></span><br><span><span class="mtk1"> ((even? n) (</span><span class="mtk12">cons</span><span class="mtk1"> </span><span class="mtk1">"even"</span><span class="mtk1"> _))</span></span><br><span><span class="mtk1"> ((zero? n) (</span><span class="mtk12">cons</span><span class="mtk1"> </span><span class="mtk1">"zero"</span><span class="mtk1"> _))</span></span><br><span><span class="mtk1"> ((positive? n) (</span><span class="mtk12">cons</span><span class="mtk1"> </span><span class="mtk1">"positive"</span><span class="mtk1"> _))))</span></span><br><span><span> </span></span><br><span><span class="mtk1">(describe-number </span><span class="mtk21">3</span><span class="mtk1">) </span><span class="mtk4">; => '("positive" "odd")</span></span><br><span><span class="mtk1">(describe-number </span><span class="mtk21">4</span><span class="mtk1">) </span><span class="mtk4">; => '("positive" "even")</span></span><br></code></pre></div>
<p>Each <code><step></code> is evaluated as an application. The return value of the step is passed to the next step as its <em>pipeline value</em>. <code><initial-value></code> is the pipeline value of the first step.</p>
<p>The <code><_></code> placeholder in each <code><step></code> is replaced with that step's pipeline value. If a <code><step></code> does not contain <code><_></code>, it will ignore its pipeline value</p>
<p>If a step's <code><guard></code> is present and evaluates to <code>#f</code>, that step will be skipped, and its pipeline value will be reused as the pipeline value of the next step. The return value of <code>chain-when</code> is the return value of the last non-skipped step, or <code><initial-value></code> if all steps are skipped.</p>
<p>Because <code>chain-when</code> may skip steps, it does not support steps with multiple return values. It is an error if a step returns more than one value.</p>
<h3 id="chain-lambda"><code>chain-lambda</code></h3>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(chain-lambda [<placeholder> [<ellipsis>]] <step> </span><span class="mtk1">...)</span></span><br></code></pre></div>
<p><em>Syntax:</em> <code><placeholder></code> and <code><ellipsis></code> are literal symbols; these are the <em>placeholder symbol</em> and <em>ellipsis symbol</em>. If <code><placeholder></code> or <code><ellipsis></code> are not present, they default to <code>_</code> and <code>...</code>, respectively.</p>
<p>The syntax of <code><step></code> is <code>(<datum> ...)</code>, where each <code><datum></code> is either the placeholder symbol, the ellipsis symbol, or an expression. A <code><step></code> must contain at least one <code><datum></code>. The ellipsis symbol is only allowed at the end of a <code><step></code>, and it must immediately follow a placeholder symbol.</p>
<p><em>Semantics:</em> Creates a procedure from a sequence of <code>chain</code> steps. When called, a <code>chain-lambda</code> procedure evaluates each <code><step></code> in order from left to right, passing the result of each step to the next.</p>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(chain-lambda (a _) (b _)) </span><span class="mtk4">; => (lambda (x) (let* ((x (a x))) (b x)))</span></span><br><span><span class="mtk1">(chain-lambda (a _ _) (b c _)) </span><span class="mtk4">; => (lambda (x1 x2) (let* ((x (a x1 x2))) (b c x)</span><span class="mtk4">))</span></span><br></code></pre></div>
<p>Each <code><step></code> is evaluated as an application, and the return value(s) of that application are passed to the next step as its <em>pipeline values</em>. 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.</p>
<p>The placeholder symbols in each <code><step></code> 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.</p>
<p>If a <code><step></code> 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.</p>
<p>The number of placeholders in the first <code><step></code> determines the arity of the procedure. If the first step ends with an ellipsis symbol, the procedure is variadic.</p>
<h3 id="nest"><code>nest</code></h3>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(nest [<placeholder>] <step> ... <initial-value>)</span></span><br></code></pre></div>
<p><em>Syntax:</em> <code><placeholder></code> is a literal symbol; this is the <em>placeholder symbol</em>. If <code><placeholder></code> is not present, the placeholder symbol is <code>_</code>. The syntax of <code><step></code> is <code>(<datum> ... <_> <datum> ...)</code>, where <code><_></code> is the placeholder symbol. <code><initial-value></code> is expression.</p>
<p><em>Semantics:</em> <code>nest</code> is similar to <code>chain</code>, but sequences its steps in the opposite order. Unlike <code>chain</code>, <code>nest</code> literally nests expressions; as a result, it does not provide the same strict evaluation order guarantees as <code>chain</code>.</p>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(nest (a b _) (c d _) e) </span><span class="mtk4">; => (a b (c d e))</span></span><br></code></pre></div>
<p>A <code>nest</code> expression is evaluated by lexically replacing the <code><_></code> in the last <code><step></code> with <code><initial-value></code>, then replacing the <code><_></code> in the next-to-last <code><step></code> with that replacement, and so on until the <code><_></code> in the first <code><step></code> 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.</p>
<p>Because it produces an actual nested form, <code>nest</code> can build expressions that <code>chain</code> cannot. For example, <code>nest</code> can build a quoted data structure:</p>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(nest '_ (</span><span class="mtk21">1</span><span class="mtk1"> </span><span class="mtk21">2</span><span class="mtk1"> _) (</span><span class="mtk21">3</span><span class="mtk1"> _ </span><span class="mtk21">5</span><span class="mtk1">) (_) </span><span class="mtk21">4</span><span class="mtk1">) </span><span class="mtk4">; => '(1 2 (3 (4) 5))</span></span><br></code></pre></div>
<p><code>nest</code> can also safely include special forms like <code>if</code>, <code>let</code>, <code>lambda</code>, or <code>parameterize</code> in a pipeline.</p>
<p>A custom placeholder can be used to safely nest <code>nest</code> expressions.</p>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(nest (nest _2 '_2 (</span><span class="mtk21">1</span><span class="mtk1"> </span><span class="mtk21">2</span><span class="mtk1"> </span><span class="mtk21">3</span><span class="mtk1"> _2) _ </span><span class="mtk21">6</span><span class="mtk1">)</span></span><br><span><span class="mtk1"> (_ </span><span class="mtk21">5</span><span class="mtk1"> _2)</span></span><br><span><span class="mtk1"> </span><span class="mtk21">4</span><span class="mtk1">)</span></span><br><span><span class="mtk4">; => '(1 2 3 (4 5 6))</span></span><br></code></pre></div>
<h3 id="nest-reverse"><code>nest-reverse</code></h3>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(nest-reverse <initial-value> [<placeholder>] <ste</span><span class="mtk1">p> ...)</span></span><br></code></pre></div>
<p><em>Syntax:</em> <code><initial-value></code> is an expression. <code><placeholder></code> is a literal symbol; this is the <em>placeholder symbol</em>. If <code><placeholder></code> is not present, the placeholder symbol is <code>_</code>. The syntax of <code><step></code> is <code>(<datum> ... <_> <datum> ...)</code>, where <code><_></code> is the placeholder symbol.</p>
<p><em>Semantics:</em> <code>nest-reverse</code> is variant of <code>nest</code> that nests in reverse order, which is the same order as <code>chain</code>.</p>
<div class="copy-wrapper"><pre><code class="language-scheme"><span><span class="mtk1">(nest-reverse e (c d _) (a b _)) </span><span class="mtk4">; => (a b (c d e))</span></span><br></code></pre></div>
<p>A <code>nest-reverse</code> expression is evaluated by lexically replacing the <code><_></code> in the first <code><step></code> with <code><initial-value></code>, then replacing the <code><_></code> in the second <code><step></code> with that replacement, and so on until the <code><_></code> in the last <code><step></code> 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.</p>
<h2 id="implementation">Implementation</h2>
<p><a target="_blank" href="https://github.com/scheme-requests-for-implementation/srfi-197/">A sample implementation is available on GitHub.</a> This repository contains two portable SRFI 197 implementations, one in R7RS-small and <code>syntax-rules</code>, the other in R6RS and <code>syntax-case</code>. The only dependency of either implementation is SRFI 2. It includes an R7RS library wrapper and a test script.</p>
<h2 id="acknowledgements">Acknowledgements</h2>
<p>Thanks 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.</p>
<p>Marc provided a paragraph that has been included (with only minor changes) in the Semantics section of the <code>nest</code> and <code>nest-reverse</code> macros.</p>
<p>Thanks to Rich Hickey for Clojure and the original implementation of Clojure threading macros, and to Paulus Esterhazy for the (EPL licensed) <a target="_blank" href="https://clojure.org/guides/threading_macros">threading macros documentation page</a>, which was a source of inspiration and some of the examples in this document.</p>
<h2 id="copyright">Copyright</h2>
<p>© 2020 Adam Nelson.</p>
<p>Permission 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:</p>
<p>The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.</p>
<p>THE 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.</p>
<hr>
<address>Editor: <a href="mailto:srfi-editors+at+srfi+dot+schemers+dot+org">Arthur A. Gleckler</a></address>
</div>
</body></html>