-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsrfi-251.html
250 lines (214 loc) · 10.6 KB
/
srfi-251.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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
<!DOCTYPE html>
<html lang="en">
<head>
<!--
SPDX-FileCopyrightText: 2023 Sergei Egorov
SPDX-License-Identifier: MIT
-->
<meta charset="utf-8">
<title>SRFI 251: Mixing groups of definitions with expressions within bodies</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>
small { font-size: 14px; vertical-align: 2px; }
body { line-height: 24px; }
pre { font-family: inherit; line-height: 20px; }
</style>
</head>
<body>
<h1><a href="https://srfi.schemers.org/"><img class="srfi-logo" src="https://srfi.schemers.org/srfi-logo.svg"
alt="SRFI surfboard logo" /></a>251: Mixing groups of definitions with expressions within bodies</h1>
<p>by Sergei Egorov</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+251+at+srfi+dotschemers+dot+org">srfi-251@<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-251/">archive</a>.</p>
<ul>
<li>Received: 2023-11-28</li>
<li>Draft #1 published: 2023-11-30</li>
<li>Draft #2 published: 2024-01-01</li>
<li>Draft #3 published: 2024-03-05</li>
<li>Finalized: 2024-05-06</li>
</ul>
<h2 id="abstract">Abstract</h2>
<p>
Scheme has traditionally required procedure bodies and the bodies of derived constructs
such as <code>let</code> to contain definitions followed by commands/expressions.
This SRFI proposes to allow mixing commands and groups of definitions in such bodies, so that each
command/expression is in the scope of all local definition groups preceding it, but not in scope of
the local definition groups following it. This approach is backwards compatible with R7RS and
upholds the intuitive rule that to find the definition of a lexical variable, one has
to look <em>up</em> the source code tree.
</p>
<h2 id="rationale">Rationale</h2>
<p>
This SRFI competes with Daphne Preston-Kendal's <a href="https://srfi.schemers.org/srfi-245/">SRFI-245</a>,
which also allows arbitrary mixing of expressions/commands with definitions within single ⟨body⟩.
The difference is in the <em>scope</em> of the internal definitions — while SRFI-245 proposes single recursive
scope for all definitions, this SRFI limits the scope of each group of immediately adjacent definitions to the
corresponding initializers and the subsequent body forms; the preceding body forms are not included. To make
sure that the extended ⟨body⟩ with more than one definition group behaves in a way consistent
with the top level, it is an error for a command or an initialization expression in a definition group to
contain mentions of identifiers defined by “downstream” definition groups in the same body (<i>identifier visibility constraint</i>).
This scope rule is in line with common expectations on identifier visibility: to find a definition of an identifier,
one has to go <em>up</em> the source tree, looking at groups of adjacent definitions, bindings, or formals; the
first definition found (or top-level one if none is found) is the definition for the identifier in question.
SRFI-245's rule of looking not only <em>up</em> the tree, but also <em>down</em>, stepping over the commands
until the whole body is inspected, goes against the usual expectations. The proposed approach to scopes within
body-like constructs with mixed expressions and definitions is in line with decisions made by designers of
modern functional languages such as ReasonML, Elixir, and others.
</p>
<p>
Note that differences in scope behavior of the proposed body-level definitions and top-level definitions
are made necessary by the fact that potentially there are a lot of lexically scoped identifiers to refer
to from bodies, while top level has just one scope, the program/library global scope. The “look up the tree”
rule for searching for variable definitions is not applicable on the top level, because there is no “up”;
replicating this functionality on a local level may lead to hard-to-follow code, with “up” and “down”
variable definition scans competing for programmer's attention (this is why bindings-first <code>let</code>-style
forms are usually preferred to bindings-at-end <code>where</code>-style forms).
</p>
<p>
The rationale for allowing commands before and between the definitions within a single ⟨body⟩
is the same as in SRFI-245, so it is copied verbatim below (except for the last paragraph). Please note that
all bodies satisfying the aforementioned identifier visibility constraint have the same meaning under this
proposal and SRFI-245.
</p>
<p>
It often makes sense to run type and other basic error checks on input forms before any other code runs
(including the right-hand sides of definitions):
</p>
<pre><code>(define (double-square x)
(unless (number? x)
(error "foo: not a number" x))
(define y (square x))
(* 2 y))
</code></pre>
<p>
It is likewise sometimes useful to insert logging code before the beginning of a procedure before any other code:
</p>
<pre><code>(define (dangerous-operation x)
(log-warn "Beginning dangerous operation on value" x)
(define prepared-x (prepare-for-dangerous-operation x))
...)
</code></pre>
<p>
When writing test suites it is often beneficial to build up values to be tested and run the tests on them incrementally:
</p>
<pre><code>(test-group "basic arithmetic"
(define one-plus-one (+ 1 1))
(test 2 one-plus-one)
(define two-plus-two (+ one-plus-one one-plus-one))
(test 4 two-plus-two))
</code></pre>
<h2 id="specification">Specification</h2>
<p>
Scheme's syntax for ⟨body⟩ must be changed to
allow a group of adjacent definitions at any top-level position but the last.
The grammar rules are:
</p>
<pre>
⟨body⟩ ⟶ ⟨command⟩ ⟨body⟩
| ⟨definition⟩<small>+</small> ⟨body⟩
| ⟨expression⟩
</pre>
<p>
Informally, each group of adjacent definitions starts a new mutually recursive scope
that incorporates the rest of the body. There is an additional visibility constraint on mentions
of identifiers defined by body-level definition groups: it is an error for a body-level
command or an initialization expression of a definition in a body-level definition group
to contain a free mention of an identifier defined in a subsequent (“downstream”) body-level definition
group in the same body.
</p>
<p>
More formally, we can represent the semantics of a new ⟨body⟩ satisfying
the above constraint in terms of R7RS ⟨body⟩ via translation function
T[<i>new body</i>] ⟶ <i>R7RS body</i> :
</p>
<pre>
T[⟨command⟩ ⟨body⟩] ⟹ ⟨command⟩ T[⟨body⟩]
T[⟨definition⟩<small>+</small> ⟨body⟩] ⟹ <code>((lambda</code> <code>()</code> ⟨definition⟩<small>+</small> T[⟨body⟩]<code>))</code>
T[⟨expression⟩] ⟹ ⟨expression⟩
</pre>
<p>
The actual transformation, run as a part of the macro expansion process, must ensure that
macro uses expanding into definitions are processed in the same way as forms they expand to.
In order to support this SRFI, the “definition discovery” process, normally limited to initial
body forms, should be repeated after each non-definition form, producing a new nested recursive
scope for each group of adjacent definitions. The constraint on the mentions of defined identifiers
may be enforced during macro processing.
</p>
<p>
Examples:
</p>
<p>
This is an error because <code>(define (foo) x)</code> contains a mention of <code>x</code>
defined via downstream definition group:
</p>
<pre><code>(let ((x 0))
(display "the result is")
(define (foo) x)
(display ": ")
(define x 42)
(display (foo)))
</code></pre>
<p>
The example below prints “the result is: 42”:
</p>
<pre><code>(let ((x 0))
(display "the result is")
(define (foo) x)
(define x 42)
(display ": ")
(display (foo)))
</code></pre>
<p>
These two examples print “the result is: 0”:
</p>
<pre><code>(let ((x 0))
(display "the result is")
(define (foo) x)
(display ": ")
(define xx 42)
(display (foo)))
(let ((x 0))
(define-syntax define-thunk
(syntax-rules ()
((_ i v) (define (i) v))))
(display "the result is")
(display ": ")
(define xx 42)
(define-thunk foo x)
(display (foo)))
</code></pre>
<h2 id="implementation">Implementation</h2>
<p>
The sample implementation is based on Alan Petrofsky's EIOD (“Eval In One Define”) v1.17.
Support for this proposal is implemented as a 5-line patch to the original code.
</p>
<a href="eiod+mde.scm">Source for the sample implementation.</a>
<h2 id="acknowledgements">Acknowledgements</h2>
<p>Daphne Preston-Kendal's <a href="https://srfi.schemers.org/srfi-245/">SRFI-245</a> served as an inspiration for this proposal.</p>
<h2 id="copyright">Copyright</h2>
<p>© 202? Sergei Egorov</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></body></html>