-
Notifications
You must be signed in to change notification settings - Fork 42
/
binding.lisp
358 lines (304 loc) · 12.4 KB
/
binding.lisp
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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
(in-package :serapeum)
;;; Helpers.
(defun simple-binding-p (binding)
(or (atom binding)
(= (length binding) 2)))
(defun canonicalize-bindings (bindings)
(loop for binding in bindings
if (atom binding)
collect (list binding nil)
else collect binding))
;;; `let1'
(defmacro let1 (var expr &body body)
"Bind VAR, immutably, to EXPR and evaluate BODY.
This may be pronounced with equal propriety as \"let-one\" or
\"let-once\"."
`(let ((,var ,expr))
(with-read-only-vars (,var)
,@body)))
(defpattern let1 (var expr &rest body)
`(and (trivia:<> ,var ,expr) ,@body))
;;; `lret'
(defmacro lret-aux (let (&rest bindings) &body body)
(if (null bindings)
`(,let ()
,@body)
(multiple-value-bind (body decls)
(parse-body body)
(let ((last-binding (ensure-car (lastcar bindings))))
`(,let ,bindings
,@decls
(with-read-only-vars (,last-binding)
(prog1 ,last-binding
,@body)))))))
(defmacro lret ((&rest bindings) &body body)
"Return the initial value of the last binding in BINDINGS. The idea
is to create something, initialize it, and then return it.
(lret ((x 1)
(y (make-array 1)))
(setf (aref y 0) x))
=> #(1)
Note that the value returned is the value initially bound. Subsequent
assignments are ignored.
(lret ((x 1))
(setf x 2))
=> 1
Furthermore, on Lisps that support it, the variable may be made
read-only, making assignment a compiler-time error.
`lret' may seem trivial, but it fufills the highest purpose a macro
can: it eliminates a whole class of bugs (initializing an object, but
forgetting to return it).
Cf. `aprog1' in Anaphora."
`(lret-aux let ,bindings
,@body))
(defmacro lret* ((&rest bindings) &body body)
"Cf. `lret'."
`(lret-aux let* ,bindings
,@body))
;;; `letrec'
;; Obviously `letrec' is less useful than in Scheme (where it is the
;; way to construct recursive functions) but still sometimes useful;
;; say, when initializing a timer whose function needs to refer to
;; the timer itself.
(defmacro letrec-with (setq (&rest bindings) &body body
&environment env)
(setf bindings (canonicalize-bindings bindings))
`(let (,@(loop for (var init) in bindings
if (constantp init env)
collect `(,var ,init)
else collect var))
(,setq
,@(loop for (var init) in bindings
unless (constantp init env)
append `(,var ,init)))
(locally ,@body)))
(defmacro letrec ((&rest bindings) &body body)
"Recursive LET.
The idea is that functions created in BINDINGS can close over one
another, and themselves.
Note that `letrec' only binds variables: it can define recursive
functions, but can't bind them as functions. (But see `fbindrec'.)"
`(letrec-with psetq ,bindings
,@body))
(defmacro letrec* ((&rest bindings) &body body)
"Like LETREC, but the bindings are evaluated in order.
See Waddell et al., *Fixing Letrec* for motivation.
Cf. `fbindrec*'."
`(letrec-with setq ,bindings
,@body))
(defmacro receive (formals expr &body body)
"Stricter version of `multiple-value-bind'.
Use `receive' when you want to enforce that EXPR should return a
certain number of values, or a minimum number of values.
If FORMALS is a proper list, then EXPR must return exactly as many
values -- no more and no less -- as there are variables in FORMALS.
If FORMALS is an improper list (VARS . REST), then EXPR must return at
least as many values as there are VARS, and any further values are
bound, as a list, to REST.
Lastly, if FORMALS is a symbol, bind that symbol to all the values
returned by EXPR, as if by `multiple-value-list'.
From Scheme (SRFI-8)."
;; It's probably not worth stack-allocating the thunk.
(cond ((null formals)
`(multiple-value-call
(lambda ()
,@body)
,expr))
((atom formals)
;; This could also be written:
#+(or) `(let ((,formals (multiple-value-list ,expr)))
,@body)
;; But I want it to be possible to declare FORMALS to have
;; dynamic extent, and most Lisps support that for &rest
;; lists.
`(multiple-value-call
(lambda (&rest ,formals)
,@body)
,expr))
((proper-list-p formals)
(when (intersection formals lambda-list-keywords)
(error "Lambda list keywords in formals: ~a" formals))
`(multiple-value-call
(lambda ,formals
,@body)
,expr))
(t (let* ((last (last formals))
(vars (append (butlast formals)
(list (car last))))
(rest (cdr last)))
(when (intersection (cons rest vars) lambda-list-keywords)
(error "Lambda list keywords in formals: ~a" formals))
`(multiple-value-call
(lambda (,@vars &rest ,rest)
,@body)
,expr)))))
;;; `mvlet'
;; TODO Should mvlet* allow bindings to be repeated in a single
;; binding form? It would be more consistent with let*.
(defmacro mvlet* ((&rest bindings) &body body &environment env)
"Expand a series of nested `multiple-value-bind' forms.
`mvlet*' is similar in intent to Scheme’s `let-values', but with a
different and less parenthesis-intensive syntax. Each binding is a
list of
(var var*... expr)
A simple example should suffice to show both the implementation and
the motivation:
(defun uptime (seconds)
(mvlet* ((minutes seconds (truncate seconds 60))
(hours minutes (truncate minutes 60))
(days hours (truncate hours 24)))
(declare ((integer 0 *) days hours minutes seconds))
(fmt \"~d day~:p, ~d hour~:p, ~d minute~:p, ~d second~:p\"
days hours minutes seconds)))
Note that declarations work just like `let*'."
(cond ((null bindings)
`(locally ,@body))
((every #'simple-binding-p bindings)
`(let* ,bindings ,@body))
(t (multiple-value-bind (body decls)
(parse-body body)
(let* ((bindings (canonicalize-bindings bindings))
(mvbinds (member-if-not #'simple-binding-p bindings))
(simple-binds (ldiff bindings mvbinds)))
(if simple-binds
(multiple-value-bind (local other)
(partition-declarations (mapcar #'car simple-binds) decls env)
`(let* ,simple-binds
,@local
(mvlet* ,mvbinds
,@other
,@body)))
(let* ((vars (butlast (car bindings)))
(expr (lastcar (car bindings))))
(multiple-value-bind (local other)
(partition-declarations vars decls env)
`(multiple-value-bind ,vars
,expr
,@local
(mvlet* ,(cdr bindings)
,@other
,@body))))))))))
(defmacro firstn-values (n expr)
(cond ((= n 0)
`(progn ,expr (values)))
((= n 1)
`(values ,expr))
(t (let ((temps (loop for i below n collect (string-gensym 'temp))))
`(multiple-value-bind ,temps
,expr
(values ,@temps))))))
(defmacro mvlet ((&rest bindings) &body body)
"Parallel (`let'-like) version of `mvlet*'."
(cond ((null bindings)
`(locally ,@body))
((null (rest bindings))
(let* ((bindings (canonicalize-bindings bindings))
(b (first bindings)))
`(multiple-value-bind ,(butlast b) ,(lastcar b)
,@body)))
((every #'simple-binding-p bindings)
`(let ,bindings
,@body))
(t (let* ((bindings (canonicalize-bindings bindings))
(binds
(mapcar #'butlast bindings))
(exprs
(mapcar #'lastcar bindings))
(temp-binds
(loop for vars in binds
collect (mapcar #'string-gensym vars)))
(temp-bindings
(loop for temp-bind in temp-binds
for expr in exprs
collect (append temp-bind (list expr))))
(rebindings
(loop for vars in binds
for temp-vars in temp-binds
append (loop for var in vars
for temp-var in temp-vars
collect (list var temp-var)))))
`(mvlet* ,temp-bindings
(let ,rebindings
,@body))))))
;;; `and-let*'
(defmacro and-let* ((&rest clauses) &body body &environment env)
"Scheme's guarded LET* (SRFI-2).
Each clause should have one of the following forms:
- `identifier', in which case IDENTIFIER's value is tested.
- `(expression)', in which case the value of EXPRESSION is tested.
- `(identifier expression)' in which case EXPRESSION is evaluated,
and, if its value is not false, IDENTIFIER is bound to that value
for the remainder of the clauses and the optional body.
Note that, of course, the semantics are slightly different in Common
Lisp than in Scheme, because our AND short-circuits on null, not
false.
Also, this version makes the bindings immutable."
(multiple-value-bind (body decls)
(parse-body body)
(labels ((expand (clauses body)
(unsplice
(ematch clauses
(() (if body `(progn ,@body) nil))
((list* (and var (type symbol)) clauses)
`(and ,var ,@(expand clauses body)))
((list* (list var expr) clauses)
(let ((temp (gensym (string var))))
(multiple-value-bind (local-decls other-decls)
(partition-declarations (list var) decls env)
;; The use of the temporary here is so we still
;; get style warnings if the variable is
;; unused.
`(let* ((,temp ,expr)
(,var ,temp))
,@local-decls
(and ,temp
,@(expand clauses
(append other-decls body)))))))
((list* (list expr) clauses)
`(and ,expr ,@(expand clauses body)))))))
(car (expand clauses body)))))
;;; Etc
;; These might be worth exporting if we can teach Slime to indent
;; them.
(defmacro flet* (bindings &body body)
(if (null bindings)
`(locally ,@body)
`(flet (,(car bindings))
(flet* ,(cdr bindings)
,@body))))
(defmacro stack-flet (bindings &body body)
`(flet ,bindings
(declare (dynamic-extent ,@(mapcar (lambda (binding)
`(function ,(car binding)))
bindings)))
,@body))
(defmacro if-not (test then &optional else)
"If TEST evaluates to NIL, evaluate THEN and return its values,
otherwise evaluate ELSE and return its values. ELSE defaults to NIL."
`(if (not ,test)
,then
,else))
(defmacro if-not-let (bindings &body (then-form &optional else-form))
"Creates new variable bindings, and conditionally executes either
THEN-FORM or ELSE-FORM. ELSE-FORM defaults to NIL.
BINDINGS must be either single binding of the form:
(variable initial-form)
or a list of bindings of the form:
((variable-1 initial-form-1)
(variable-2 initial-form-2)
...
(variable-n initial-form-n))
All initial-forms are executed sequentially in the specified order. Then all
the variables are bound to the corresponding values.
If one of the variables was bound to NIL, the THEN-FORM is executed with the
bindings in effect, otherwise the ELSE-FORM is executed with the bindings in
effect.
Adapted from Alexandria if-let."
(let* ((binding-list (if (and (consp bindings) (symbolp (car bindings)))
(list bindings)
bindings))
(variables (mapcar #'car binding-list)))
`(let ,binding-list
(if-not (and ,@variables)
,then-form
,else-form))))