-
Notifications
You must be signed in to change notification settings - Fork 0
/
lambda.lisp
315 lines (266 loc) · 12.1 KB
/
lambda.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
(in-package #:ski)
(defclass lambda-term (term) ()
(:documentation "The base class for a lambda calculus term."))
(defun lambda-term-p (object)
"Return true if OBJECT is a LAMBDA-TERM, and NIL otherwise."
(typep object 'lambda-term))
(defclass lambda-abstraction (lambda-term)
((variable :initarg :variable :accessor variable)
(body :initarg :body :accessor body))
(:documentation "A lambda abstraction in lambda calculus."))
(defun make-lambda-abstraction (variable body)
"Construct and return a LAMBDA-ABSTRACTION with VARIABLE and BODY."
(make-instance 'lambda-abstraction :variable variable :body body))
(defun lambda-abstraction-p (object)
"Return true if OBJECT is a LAMBDA-ABSTRACTION, and NIL otherwise."
(typep object 'lambda-abstraction))
(defmethod print-object ((object lambda-abstraction) stream)
(with-slots (variable body) object
(print-unreadable-object (object stream :type t :identity t)
(format stream "(~a ~a)" variable body))))
(defmethod print-term ((term lambda-abstraction) &optional (stream *standard-output*))
(write-char #\λ stream)
(print-term (variable term) stream)
(write-char #\. stream)
(print-term (body term) stream)
term)
(defclass lambda-application (application lambda-term) ()
(:documentation "An application in lambda calculus."))
(defun make-lambda-application (left right)
"Construct and return a LAMBDA-APPLICATION that represents the
application of the LEFT term to the RIGHT term."
(make-instance 'lambda-application :left left :right right))
(defun lambda-application-p (object)
"Return true if OBJECT is a LAMBDA-APPLICATION, and NIL otherwise."
(typep object 'lambda-application))
(defmethod print-term ((term lambda-application) &optional (stream *standard-output*))
(with-accessors ((left left) (right right)) term
(if (lambda-abstraction-p left)
(progn
(write-char #\( stream)
(print-term left stream)
(write-char #\) stream))
(print-term left stream))
(if (lambda-variable-p right)
(print-term right stream)
(progn
(write-char #\( stream)
(print-term right stream)
(write-char #\) stream))))
term)
(defclass lambda-variable (variable lambda-term) ()
(:documentation "A variable in the lambda calculus."))
(defun make-lambda-variable (name)
"Construct and return a LAMBDA-VARIABLE."
(make-instance 'lambda-variable :name name))
(defun lambda-variable-p (object)
"Return true if OBJECT is a LAMBDA-VARIABLE, and NIL otherwise."
(typep object 'lambda-variable))
(defgeneric alpha-equivalent-p (term1 term2 env)
(:documentation "Return true if TERM1 and TERM2 are alpha-equivalent lambda calculus
terms, and NIL otherwise. Use the environment ENV to resolve the
bindings of variables."))
(defmethod alpha-equivalent-p ((term1 lambda-term) (term2 lambda-term) env)
nil)
(defmethod alpha-equivalent-p ((term1 lambda-variable) (term2 lambda-variable) env)
(let ((assoc1 (cdr (assoc term1 env :test #'same-variable-p))))
(cond (assoc1 (same-variable-p assoc1 term2))
((rassoc term2 env :test #'same-variable-p) nil)
(t (same-variable-p term1 term2)))))
(defmethod alpha-equivalent-p ((term1 lambda-application) (term2 lambda-application) env)
(and
(alpha-equivalent-p (left term1) (left term2) env)
(alpha-equivalent-p (right term1) (right term2) env)))
(defmethod alpha-equivalent-p ((term1 lambda-abstraction) (term2 lambda-abstraction) env)
(alpha-equivalent-p
(body term1)
(body term2)
(acons (variable term1) (variable term2) env)))
(defmethod term-equal ((term1 lambda-term) (term2 lambda-term))
(alpha-equivalent-p term1 term2 nil))
(defmethod occurs-free-p ((variable variable) (term lambda-abstraction))
(unless (same-variable-p variable (variable term))
(occurs-free-p variable (body term))))
(defgeneric free-variables (term)
(:documentation "Return the free variables in the lambda calculus TERM."))
(defmethod free-variables ((term lambda-variable))
(list term))
(defmethod free-variables ((term lambda-application))
(with-accessors ((left left) (right right)) term
(union (free-variables left)
(free-variables right)
:test #'same-variable-p)))
(defmethod free-variables ((term lambda-abstraction))
(with-accessors ((variable variable) (body body)) term
(delete-if (lambda (fv) (same-variable-p fv variable))
(free-variables body))))
(defgeneric bound-variables (term)
(:documentation "Return the bound variables in the lambda calculus TERM."))
(defmethod bound-variables ((term lambda-variable))
nil)
(defmethod bound-variables ((term lambda-application))
(with-accessors ((left left) (right right)) term
(union (bound-variables left)
(bound-variables right)
:test #'same-variable-p)))
(defmethod bound-variables ((term lambda-abstraction))
(with-accessors ((variable variable) (body body)) term
(union (list variable)
(bound-variables body)
:test #'same-variable-p)))
(defgeneric lambda-combinator-p (term)
(:documentation "Return true if TERM is a lambda calculus combinator, and NIL
otherwise."))
(defmethod lambda-combinator-p ((term lambda-term))
nil)
(defmethod lambda-combinator-p ((term lambda-abstraction))
(null (free-variables term)))
(defgeneric substitute-avoiding-capture (term target replacement)
(:documentation "Return a new term where the free occurrences of the variable TARGET in
TERM have been replaced with REPLACEMENT without capturing free
variables while doing so."))
(defmethod substitute-avoiding-capture ((term lambda-variable)
(target lambda-variable)
(replacement lambda-term))
(if (same-variable-p term target) replacement term))
(defmethod substitute-avoiding-capture ((term lambda-application)
(target lambda-variable)
(replacement lambda-term))
(with-accessors ((left left) (right right)) term
(make-lambda-application
(substitute-avoiding-capture left target replacement)
(substitute-avoiding-capture right target replacement))))
(defmethod substitute-avoiding-capture ((term lambda-abstraction)
(target lambda-variable)
(replacement lambda-term))
(flet ((fresh-variable (t1 t2)
(let ((forbidden (union (free-variables t1)
(free-variables t2)
:test #'same-variable-p))
(generator (make-variable-name-generator)))
(loop
(let ((var (make-lambda-variable (generate-name generator))))
(unless (member var forbidden :test #'same-variable-p)
(return var)))))))
(with-accessors ((variable variable) (body body)) term
(cond ((same-variable-p variable target)
term)
((not (occurs-free-p target body))
term)
((not (occurs-free-p variable replacement))
(make-lambda-abstraction
variable
(substitute-avoiding-capture body target replacement)))
(t
(let ((fresh-variable (fresh-variable body replacement)))
(make-lambda-abstraction
fresh-variable
(substitute-avoiding-capture
(substitute-avoiding-capture
body
variable
fresh-variable)
target
replacement))))))))
(defvar *lambda-reduction-strategy* :normal-order
"The reduction strategy to use when reducing lambda calculus terms.
Must be a one of the following keywords: :NORMAL-ORDER,
:APPLICATIVE-ORDER, :CALL-BY-NAME, :CALL-BY-VALUE.")
(defgeneric beta-reduce (term strategy)
(:documentation "Perform a beta reduction on TERM using the reduction STRATEGY. Return,
as multiple values, the new term and a generalized boolean that is
true if a step took place and NIL otherwise."))
(defmethod beta-reduce ((term lambda-variable) strategy)
(values term nil))
(defmethod beta-reduce ((term lambda-abstraction) (strategy (eql :normal-order)))
(multiple-value-bind (new-body stepped)
(beta-reduce (body term) strategy)
(values (make-lambda-abstraction (variable term) new-body)
stepped)))
(defmethod beta-reduce ((term lambda-application) (strategy (eql :normal-order)))
(with-accessors ((left left) (right right)) term
(if (lambda-abstraction-p left)
(values (substitute-avoiding-capture
(body left)
(variable left)
right)
t)
(multiple-value-bind (new-left stepped-left)
(beta-reduce left strategy)
(multiple-value-bind (new-right stepped-right)
(beta-reduce right strategy)
(values (make-lambda-application new-left new-right)
(or stepped-left stepped-right)))))))
(defmethod beta-reduce ((term lambda-abstraction) (strategy (eql :applicative-order)))
(multiple-value-bind (new-body stepped)
(beta-reduce (body term) strategy)
(values (make-lambda-abstraction (variable term) new-body)
stepped)))
(defmethod beta-reduce ((term lambda-application) (strategy (eql :applicative-order)))
(with-accessors ((left left) (right right)) term
(if (lambda-abstraction-p left)
(values (substitute-avoiding-capture
(reduce-term (body left))
(variable left)
(reduce-term right))
t)
(multiple-value-bind (new-left stepped-left)
(beta-reduce left strategy)
(multiple-value-bind (new-right stepped-right)
(beta-reduce right strategy)
(values (make-lambda-application new-left new-right)
(or stepped-left stepped-right)))))))
(defmethod beta-reduce ((term lambda-abstraction) (strategy (eql :call-by-name)))
(values term nil))
(defmethod beta-reduce ((term lambda-application) (strategy (eql :call-by-name)))
(with-accessors ((left left) (right right)) term
(if (lambda-abstraction-p left)
(values (substitute-avoiding-capture
(body left)
(variable left)
right)
t)
(multiple-value-bind (new-left stepped-left)
(beta-reduce left strategy)
(multiple-value-bind (new-right stepped-right)
(beta-reduce right strategy)
(values (make-lambda-application new-left new-right)
(or stepped-left stepped-right)))))))
(defmethod beta-reduce ((term lambda-abstraction) (strategy (eql :call-by-value)))
(values term nil))
(defmethod beta-reduce ((term lambda-application) (strategy (eql :call-by-value)))
(with-accessors ((left left) (right right)) term
(if (lambda-abstraction-p left)
(values (substitute-avoiding-capture
(body left)
(variable left)
(reduce-term right))
t)
(multiple-value-bind (new-left stepped-left)
(beta-reduce left strategy)
(multiple-value-bind (new-right stepped-right)
(beta-reduce right strategy)
(values (make-lambda-application new-left new-right)
(or stepped-left stepped-right)))))))
(defmethod reduce-term ((term lambda-term))
(loop
(multiple-value-bind (new-term stepped)
(beta-reduce term *lambda-reduction-strategy*)
(unless stepped
(return term))
(setf term new-term))))
(defun lambda-driver-loop ()
"A REPL for lambda calculus."
(flet ((prompt-for-input ()
(format t "~&%%% ")
(finish-output)
(read-line t nil))
(drive (input)
(print-term (reduce-term (parse-lambda-term input)))))
(loop
(let ((input (prompt-for-input)))
(cond ((null input) (return))
((zerop (length input)))
(t (handler-case (drive input)
(esrap:esrap-parse-error ()
(format t "~&Parse error")))))))))