-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathinfer.rkt
405 lines (348 loc) · 12.5 KB
/
infer.rkt
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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
#lang racket
(require "ast.rkt")
(define (check-type expr)
(reset-var)
(let ([t (second (infer (gamma empty) expr))])
(displayln (string-append "inferred type: " (pretty-type t)))
(unify-ind t (fun-type (list) (list (fresh-seq))))
expr))
(provide check-type)
(module+ test
(require rackunit))
;; type Expr = Word*
#|
type Type
= Int
| Bool
| IVar String
| SVar String
| Fun [Type] [Type]
type Scheme = Scheme [SVar | IVar] Type
One of the invariants we maintain throughout the
type system is that sequence variables can only
occur as the last element in a list of types.
|#
(struct ivar (n) #:transparent)
(struct svar (n) #:transparent)
(struct fun-type (in out) #:transparent)
(struct prim-type (name) #:transparent)
(struct scheme (vars mono) #:transparent)
(define (pretty-type t)
(match t
[(ivar n) n]
[(svar n) (string-append n "...")]
[(fun-type is os) (string-append "(" (pretty-type is) " -> " (pretty-type os) ")")]
[(list ts ...)
(string-join (map pretty-type ts) " ")]
[(prim-type p) (string-titlecase p)]
[(scheme vs t) (string-append "forall "
(string-join (map pretty-type vs) ",")
"."
(pretty-type t))]))
;; get-name : SVar | IVar -> String
(define (get-name v)
(match v
[(ivar n) n]
[(svar n) n]))
;; get-scheme-vnames : Scheme -> [String]
(define (get-scheme-vnames s)
(map get-name (scheme-vars s)))
;; type TypeEnvironment = [(Var,Scheme)]
;; Another invariant we maintain is that all variables
;; have a function type in the environment.
(struct gamma (vs) #:transparent)
(define (extend-env n t env)
(gamma (list* (list n t) (gamma-vs env))))
;; ftv :: Type | Scheme | Gamma -> [String]
(define (ftv t)
(match t
[(list ts ...) (append* (map ftv ts))]
[(ivar n) (list (ivar n))]
[(svar n) (list (svar n))]
[(fun-type in out) (append (ftv in) (ftv out))]
[(scheme vs mono) (filter (lambda (v) (not (member v vs))) (ftv mono))]
[(gamma vs) (append* (map (lambda (s) (ftv (second s))) vs))]
[_ empty]))
(module+ test
(check-equal? (list) (ftv (list (prim-type "int"))))
(check-equal? (list (ivar "n") (svar "a"))
(ftv (list (ivar "n") (svar "a"))))
(check-equal? (list (ivar "n") (svar "a"))
(ftv (fun-type (list (ivar "n")) (list (svar "a")))))
(check-equal? (list (svar "a"))
(ftv (scheme (list (ivar "n"))
(fun-type (list (ivar "n"))
(list (svar "a"))))))
(check-equal? (list (svar "a"))
(ftv (gamma (list (list "x"
(scheme (list (ivar "n"))
(fun-type (list (ivar "n"))
(list (svar "a"))))))))))
;; ===================================================
;; ===================================================
;; SUBSTITUTIONS
;; ===================================================
;; ===================================================
;; type Subst = [(String,Seq)]
;; subst : Subst, (Type | Seq | Gamma | Scheme) -> (Type | Seq | Gamma | Scheme)
(define (subst s t)
(define (flatten ls)
(match ls
[(list) (list)]
[(list (list e1 ...) e2 ...) (append e1 (flatten e2))]
[(list t e2 ...) (list* t (flatten e2))]))
;; in-scheme : [String] -> (String, Seq) -> Bool
(define (not-in-scheme vs)
(lambda (vp)
(not (member (car vp) vs))))
(cond
[(list? t)
(flatten (map (lambda (x) (subst s x)) t))]
[(fun-type? t)
(fun-type (subst s (fun-type-in t))
(subst s (fun-type-out t)))]
[(ivar? t)
(match (assoc (ivar-n t) s)
[#f t]
[other (second other)])]
[(svar? t)
(match (assoc (svar-n t) s)
[#f t]
[other (second other)])]
[(scheme? t)
(scheme
(scheme-vars t)
(subst (filter (not-in-scheme (get-scheme-vnames t)) s) (scheme-mono t)))]
[(gamma? t)
(gamma (map (lambda (x)
(list (first x) (subst s (second x))))
(gamma-vs t)))]
[#t t]))
(module+ test
(check-equal?
(subst (list (list "a" (list (svar "b")))) (list (svar "a")))
(list (svar "b"))))
;; compose-subst : Subst, Subst -> Subst
(define (compose-subst s1 s2)
(define (combine-subs s1 s2)
(match s2
[(list) s1]
[(list (list v t) s2s ...)
(if (assoc v s1)
(combine-subs s1 s2s)
(list* (list v t) (combine-subs s1 s2s)))]))
(define (sub-in-type s)
(lambda (pair)
(list (first pair) (subst s (second pair)))))
(combine-subs (map (sub-in-type s1) s2) s1))
;; ===================================================
;; ===================================================
;; TYPE INFERENCE
;; ===================================================
;; ===================================================
(define ind 0)
(define (reset-var) (set! ind 0))
;; fresh-var : -> String
(define (fresh-var)
(let ([x (string-append "a" (number->string ind))])
(set! ind (add1 ind))
x))
(define (fresh-ind) (ivar (fresh-var)))
(define (fresh-seq) (svar (fresh-var)))
;; infer : TypeEnvironment, Expr -> (Subst, Type)
(define (infer env expr)
(match expr
[(list)
(let ([a (fresh-seq)])
(list empty (fun-type (list a) (list a))))]
[(list e ... w)
(let* ([r1 (infer env e)]
[s1 (first r1)]
[t1 (second r1)]
[r2 (infer-word (subst s1 env) w)]
[s2 (first r2)]
[t2 (second r2)]
[phi (unify (fun-type-out t1) (fun-type-in t2))])
(list (compose-subst (compose-subst phi s2) s1)
(subst phi (fun-type (fun-type-in t1) (fun-type-out t2)))))]))
(module+ test
(check-equal?
(second (infer (gamma (list)) (list (ast-prim "call"))))
(fun-type (list (svar "a1") (fun-type (list (svar "a1")) (list (svar "a2"))))
(list (svar "a2"))))
(reset-var)
(check-equal?
(second (infer (gamma (list)) (list (ast-bind "x" (list "x")))))
(fun-type (list (svar "a4") (ivar "a2"))
(list (svar "a4") (ivar "a2"))))
(reset-var)
(check-equal?
(second (infer (gamma (list)) (list (ast-let "x" (list (ast-prim "add")) (list "x")))))
(fun-type (list (svar "a4") (prim-type "int") (prim-type "int"))
(list (svar "a4") (prim-type "int")))))
;; infer-word : TypeEnvironment, Word -> (Subst, Type)
(define (infer-word env word)
(match word
;; numeric constant
[c #:when (number? c)
(let ([a (fresh-seq)])
(list empty (fun-type (list a) (list a (prim-type "int")))))]
;; boolean constant
[c #:when (boolean? c)
(let ([a (fresh-seq)])
(list empty (fun-type (list a) (list a (prim-type "bool")))))]
;; block constant
[(ast-block e)
(let* ([r1 (infer env e)]
[s1 (first r1)]
[t1 (second r1)]
[a (fresh-seq)])
(list s1 (fun-type (list a) (list a t1))))]
;; primitive word
[(ast-prim n)
(list empty (infer-prim n))]
;; variable reference
[c #:when (string? c)
(list empty (inst (second (assoc c (gamma-vs env)))))]
;; value binding
[(ast-bind n e)
(let* ([a (fresh-seq)]
[b (fresh-ind)]
[r1 (infer (extend-env n
(scheme (list a) (fun-type (list a) (list a b)))
env)
e)]
[s1 (first r1)]
[t1 (second r1)])
(list s1 (fun-type (append (fun-type-in t1) (flatten (list (subst s1 b))))
(fun-type-out t1))))]
;; expression binding
[(ast-let n e1 e2)
(let* ([r1 (infer env e1)]
[s1 (first r1)]
[t1 (second r1)]
[envp (subst s1 env)]
[r2 (infer (extend-env n (gen envp t1) envp) e2)]
[s2 (first r2)]
[t2 (second r2)])
(list (compose-subst s2 s1) t2))]))
;; infer-prim : String -> Type
(define (infer-prim name)
(match name
["add"
(let ([a (fresh-seq)])
(fun-type (list a (prim-type "int") (prim-type "int"))
(list a (prim-type "int"))))]
["call"
(let ([a (fresh-seq)]
[b (fresh-seq)])
(fun-type (list a (fun-type (list a) (list b))) (list b)))]
["fix"
(let ([a (fresh-seq)]
[b (fresh-seq)])
(fun-type (list a (fun-type (list a (fun-type (list a) (list b))) (list b)))
(list b)))]
["if"
(let ([a (fresh-seq)]
[b (fresh-ind)])
(fun-type (list a b b (prim-type "bool")) (list a b)))]
["eq"
(let ([a (fresh-seq)])
(fun-type (list a (prim-type "int") (prim-type "int"))
(list a (prim-type "bool"))))]
["less"
(let ([a (fresh-seq)])
(fun-type (list a (prim-type "int") (prim-type "int"))
(list a (prim-type "bool"))))]))
;; inst : Scheme | Type -> Type
(define (inst sch)
(define (freshen v)
(match v
[(svar s) (list s (list (fresh-seq)))]
[(ivar s) (list s (list (fresh-ind)))]))
(match sch
[(scheme vs t)
(let ([fresh (map (lambda (v) (freshen v)) vs)])
(subst fresh t))]
[t t]))
(module+ test
(reset-var)
(check-equal? (inst (scheme (list (ivar "a")) (list (ivar "a"))))
(list (ivar "a0")))
(reset-var)
(check-equal? (inst (scheme (list) (list (ivar "a"))))
(list (ivar "a"))))
;; gen : TypeEnvironment, Type -> Scheme
(define (gen env ty)
(let ([env-ftv (ftv env)])
(scheme (filter (lambda (x) (not (member x env-ftv))) (ftv ty)) ty)))
(module+ test
(check-equal? (gen (gamma (list)) (list (ivar "a")))
(scheme (list (ivar "a")) (list (ivar "a"))))
(check-equal? (gen (gamma (list (list "x" (fun-type (list) (list (ivar "a"))))))
(list (ivar "a")))
(scheme (list) (list (ivar "a")))))
;; ===================================================
;; ===================================================
;; UNIFICATION
;; ===================================================
;; ===================================================
;; unify : Seq, Seq -> Subst | Error
(define (unify s1 s2)
(match* (s1 s2)
;; both sequences empty
[((list) (list))
(list)]
;; if two individual types are equal, their unifier
;; is empty, so just unify the rest of the sequences
[((list t1s ... t1) (list t2s ... t2))
#:when (equal? t1 t2)
(unify t1s t2s)]
;; sequence variable unifies with any sequence that
;; doesn't contain that variable
[((list (svar v1)) t2s)
#:when (not (member (svar v1) (ftv t2s)))
(list (list v1 t2s))]
;; same as above, but sequence variable is on the other side
[(t1s (list (svar v2)))
#:when (not (member (svar v2) (ftv t1s)))
(list (list v2 t1s))]
;; two individual types aren't unifiable, so unify them
[((list t1s ... t1) (list t2s ... t2))
#:when (and (not (svar? t1)) (not (svar? t2)))
(let* ([phi (unify-ind t1 t2)]
[phi2 (unify (subst phi t1s) (subst phi t2s))])
(compose-subst phi2 phi))]
[(l r) (displayln "inference failed: sequence unification error")
(displayln (string-append "left seq: " (pretty-type l)))
(displayln (string-append "right seq: " (pretty-type r)))
(error "will not run due to inference failure")]))
;; unify-ind : Type, Type -> Subst | Error
(define (unify-ind t1 t2)
(if (equal? t1 t2)
(list)
(match* (t1 t2)
[((ivar v1) t2p)
#:when (not (member (ivar v1) (ftv t2p)))
(list (list v1 (list t2p)))]
[(t1p (ivar v2))
#:when (not (member (ivar v2) (ftv t1p)))
(list (list v2 (list t1p)))]
[((fun-type i1 o1) (fun-type i2 o2))
(let* ([phi (unify i1 i2)]
[phi2 (unify (subst phi o1) (subst phi o2))])
(compose-subst phi2 phi))]
[(l r) (displayln "inference failed: individual unification error")
(displayln (string-append "left: " (pretty-type l)))
(displayln l)
(displayln (string-append "right: " (pretty-type r)))
(displayln r)
(error "type checking failure")])))
(module+ test
(check-equal? (unify-ind (fun-type (list) (list)) (fun-type (list) (list)))
(list)))
(module+ test
(reset-var)
(check-equal?
(second (infer (gamma (list)) (list (ast-bind "x" (list (ast-bind "x" (list "x")))))))
(fun-type (list (svar "a7") (ivar "a5") (ivar "a2")) (list (svar "a7") (ivar "a5")))))