- add folding compuate+instantiate-or-inferral of args in app
- start guard support in match (doesnt work yet)
This commit is contained in:
parent
87cf55e7ae
commit
f0aba48497
156
tapl/mlish.rkt
156
tapl/mlish.rkt
|
@ -1,7 +1,7 @@
|
|||
#lang s-exp "typecheck.rkt"
|
||||
(require (for-syntax syntax/id-set))
|
||||
|
||||
(extends "ext-stlc.rkt" #:except #%app λ → + - void = zero? sub1 add1 not let
|
||||
(extends "ext-stlc.rkt" #:except #%app λ → + - void = zero? sub1 add1 not let and
|
||||
#:rename [~→ ~ext-stlc:→])
|
||||
(reuse inst ~∀ ∀ ∀? Λ #:from "sysf.rkt")
|
||||
(require (only-in "stlc+rec-iso.rkt" case fld unfld μ × ∨ var tup define-type-alias)
|
||||
|
@ -12,9 +12,13 @@
|
|||
;(provide hd tl nil?)
|
||||
(provide →)
|
||||
(provide define-type match)
|
||||
(provide (rename-out [ext-stlc:let let]))
|
||||
(provide (rename-out [ext-stlc:let let] [ext-stlc:and and]))
|
||||
|
||||
;; ML-like language with no type inference
|
||||
;; ML-like language
|
||||
;; - top level recursive functions
|
||||
;; - user-definable algebraic datatypes
|
||||
;; - pattern matching
|
||||
;; - (local) type inference
|
||||
|
||||
;; type inference constraint solving
|
||||
(begin-for-syntax
|
||||
|
@ -45,8 +49,41 @@
|
|||
#:when (free-identifier=? #'y x)
|
||||
#'τ]
|
||||
[(_ . rst) (lookup x #'rst)]
|
||||
[() false])))
|
||||
[() false]))
|
||||
;; solve for tyvars Xs used in tys, based on types of args in stx
|
||||
;; infer types of args left-to-right:
|
||||
;; - use intermediate results to help infer remaining arg types
|
||||
;; - short circuit if done early
|
||||
;; return list of types if success; #f if fail
|
||||
(define (solve Xs tys stx)
|
||||
(define args (stx-cdr stx))
|
||||
(cond
|
||||
[(stx-null? Xs) #'()]
|
||||
[(or (stx-null? args) (not (stx-length=? tys args)))
|
||||
(type-error #:src stx
|
||||
#:msg (mk-app-err-msg stx #:expected tys
|
||||
#:note (format "Could not infer instantiation of polymorphic function ~a."
|
||||
(syntax->datum (stx-car stx)))))]
|
||||
[else
|
||||
(let loop ([a (stx-car args)] [args-rst (stx-cdr args)]
|
||||
[ty (stx-car tys)] [tys-rst (stx-cdr tys)]
|
||||
[old-cs #'()])
|
||||
(define/with-syntax [a- ty_a] (infer+erase a))
|
||||
(define cs
|
||||
(stx-append old-cs (compute-constraints (list (list ty #'ty_a)))))
|
||||
(define maybe-solved
|
||||
(filter (lambda (x) x) (stx-map (λ (X) (lookup X cs)) Xs)))
|
||||
(if (stx-length=? maybe-solved Xs)
|
||||
maybe-solved
|
||||
(if (or (stx-null? args-rst) (stx-null? tys-rst))
|
||||
(type-error #:src stx
|
||||
#:msg (mk-app-err-msg stx #:expected tys #:given (stx-map cadr (infers+erase args))
|
||||
#:note (format "Could not infer instantiation of polymorphic function ~a."
|
||||
(syntax->datum (stx-car stx)))))
|
||||
(loop (stx-car args-rst) (stx-cdr args-rst)
|
||||
(stx-car tys-rst) (stx-cdr tys-rst) cs))))])))
|
||||
|
||||
;; define --------------------------------------------------
|
||||
(define-typed-syntax define
|
||||
[(_ x:id e)
|
||||
#:with (e- τ) (infer+erase #'e)
|
||||
|
@ -88,6 +125,7 @@
|
|||
;; internal form used to expand many types at once under the same context
|
||||
(define-type-constructor Tmp #:arity >= 0 #:bvs >= 0) ; need a >= 0 arity
|
||||
|
||||
;; define-type --------------------------------------------------
|
||||
(define-syntax (define-type stx)
|
||||
(syntax-parse stx
|
||||
[(_ Name:id . rst)
|
||||
|
@ -141,7 +179,7 @@
|
|||
"\n"
|
||||
(format "and be applied to ~a arguments with type(s): "(stx-length #'(τ ...)))
|
||||
(string-join (stx-map type->str #'(τ ...)) ", ")))]
|
||||
[(_ τs e_arg ...)
|
||||
[(C τs e_arg ...)
|
||||
#:when (brace? #'τs) ; commit to this clause
|
||||
#:with {~! τ_X:type (... ...)} #'τs
|
||||
#:with (τ_in:type (... ...)) ; instantiated types
|
||||
|
@ -155,7 +193,7 @@
|
|||
#'(e_arg ...) #'(τ_in.norm (... ...)))
|
||||
#:fail-unless (typechecks? #'(τ_arg ...) #'(τ_in.norm (... ...)))
|
||||
;; need to duplicate #%app err msg here, to attach additional props
|
||||
(mk-app-err-msg stx
|
||||
(mk-app-err-msg #'(C e_arg ...)
|
||||
#:expected #'(τ_in.norm (... ...)) #:given #'(τ_arg ...)
|
||||
#:name (format "constructor ~a" 'Cons))
|
||||
#:with τ_out (syntax-property
|
||||
|
@ -168,31 +206,37 @@
|
|||
;; infer instantiation types from args left-to-right,
|
||||
;; short-circuit if done early, and use result to help infer remaining args
|
||||
#:with (~Tmp Ys . τs+) ((current-type-eval) #'(Tmp (X ...) τ ...))
|
||||
(let loop ([a (stx-car #'args)] [a-rst (stx-cdr #'args)]
|
||||
[τ+ (stx-car #'τs+)] [τ+rst (stx-cdr #'τs+)]
|
||||
[old-cs #'()])
|
||||
(define/with-syntax [a- τ_a] (infer+erase a))
|
||||
(define cs (stx-append old-cs (compute-constraints (list (list τ+ #'τ_a)))))
|
||||
(define maybe-solved (filter syntax-e (stx-map (λ (y) (lookup y cs)) #'Ys)))
|
||||
(if (stx-length=? maybe-solved #'Ys)
|
||||
#`(C #,(syntax-property #`{#,@maybe-solved} 'paren-shape #\{) . args) ; loses 'paren-shape
|
||||
(if (or (stx-null? a-rst) (stx-null? τ+rst))
|
||||
(type-error #:src stx
|
||||
#:msg "could not infer types for constructor ~a; add annotations" #'(C . args))
|
||||
(loop (stx-car a-rst) (stx-cdr a-rst) (stx-car τ+rst) (stx-cdr τ+rst) cs))))]))
|
||||
; #:with ([arg- τarg] (... ...)) (infers+erase #'args)
|
||||
; #:with (~Tmp Ys τ+ (... ...)) ((current-type-eval) #'(Tmp (X ...) τ ...))
|
||||
; #:with cs (compute-constraints #'((τ+ τarg) (... ...)))
|
||||
; #:with (τ_solved (... ...)) (stx-map (λ (y) (lookup y #'cs)) #'Ys)
|
||||
; #'(C {τ_solved (... ...)} . args)]))
|
||||
#:with (τ_solved (... ...)) (solve #'Ys #'τs+ stx)
|
||||
;; (let loop ([a (stx-car #'args)] [a-rst (stx-cdr #'args)]
|
||||
;; [τ+ (stx-car #'τs+)] [τ+rst (stx-cdr #'τs+)]
|
||||
;; [old-cs #'()])
|
||||
;; (define/with-syntax [a- τ_a] (infer+erase a))
|
||||
;; (define cs (stx-append old-cs (compute-constraints (list (list τ+ #'τ_a)))))
|
||||
;; (define maybe-solved (filter syntax-e (stx-map (λ (y) (lookup y cs)) #'Ys)))
|
||||
;; (if (stx-length=? maybe-solved #'Ys)
|
||||
;; #`(C #,(syntax-property #`{#,@maybe-solved} 'paren-shape #\{) . args) ; loses 'paren-shape
|
||||
;; (if (or (stx-null? a-rst) (stx-null? τ+rst))
|
||||
;; (type-error #:src stx
|
||||
;; #:msg "could not infer types for constructor ~a; add annotations" #'(C . args))
|
||||
;; (loop (stx-car a-rst) (stx-cdr a-rst) (stx-car τ+rst) (stx-cdr τ+rst) cs))))]))
|
||||
;; ; #:with ([arg- τarg] (... ...)) (infers+erase #'args)
|
||||
;; ; #:with (~Tmp Ys τ+ (... ...)) ((current-type-eval) #'(Tmp (X ...) τ ...))
|
||||
;; ; #:with cs (compute-constraints #'((τ+ τarg) (... ...)))
|
||||
;; ; #:with (τ_solved (... ...)) (stx-map (λ (y) (lookup y #'cs)) #'Ys)
|
||||
#'(C {τ_solved (... ...)} . args)]))
|
||||
...)]))
|
||||
|
||||
;; match --------------------------------------------------
|
||||
(define-syntax (match stx)
|
||||
(syntax-parse stx #:datum-literals (with ->)
|
||||
[(_ e with . clauses)
|
||||
#:fail-when (null? (syntax->list #'clauses)) "no clauses"
|
||||
#:with ([Clause:id x ... -> e_c_un] ...) (stx-sort #'clauses symbol<?) ; un = unannotated with expected ty
|
||||
#:with ([Clause:id x ...
|
||||
(~optional (~seq #:when e_guard) #:defaults ([e_guard #'#t]))
|
||||
-> e_c_un] ...) ; un = unannotated with expected ty
|
||||
(stx-sort #'clauses symbol<?)
|
||||
#:with [e- τ_e] (infer+erase #'e)
|
||||
#:with z (generate-temporary)
|
||||
#:with ((Cons Cons2 [fld (~datum :) τ] ...) ...) (stx-sort (syntax-property #'τ_e 'variants) symbol<?)
|
||||
#:fail-unless (= (stx-length #'(Clause ...)) (stx-length #'(Cons ...))) "wrong number of case clauses"
|
||||
#:fail-unless (typechecks? #'(Clause ...) #'(Cons ...)) "case clauses not exhaustive"
|
||||
|
@ -211,7 +255,12 @@
|
|||
;; #:with (acc ...) (syntax-property #'τ_e 'accessors)
|
||||
;; #:with (_ (x_out ...) e_out τ_out) (stx-assoc #'C #'((Clause (x- ...) e_c- τ_ec) ...))
|
||||
#:with τ_out (stx-car #'(τ_ec ...))
|
||||
(⊢ (cond [(Cons? e-) (let ([x- (acc e-)] ...) e_c-)] ...) : τ_out)]))
|
||||
(⊢ (let ([z e-])
|
||||
(cond
|
||||
[(and (Cons? z)
|
||||
(let ([x- (acc z)] ...) e_guard))
|
||||
(let ([x- (acc z)] ...) e_c-)] ...))
|
||||
: τ_out)]))
|
||||
|
||||
#;(define-syntax lifted→ ; wrap → with ∀
|
||||
(syntax-parser
|
||||
|
@ -258,27 +307,58 @@
|
|||
[(_ f . args)
|
||||
#'(ext-stlc:#%app (inst f) . args)])
|
||||
|
||||
;; #%app --------------------------------------------------
|
||||
(define-typed-syntax #%app
|
||||
[(_ e_fn e_arg ...) ; infer args first
|
||||
#:with ([e_arg- τ_arg] ...) (infers+erase #'(e_arg ...))
|
||||
[(_ e_fn e_arg ...)
|
||||
;; ) compute fn type (ie ∀ and →)
|
||||
;; - use multiple steps to produce better err msg
|
||||
;#:with [e_fn- ((X ...) ((~ext-stlc:→ τ_inX ... τ_outX)))] (⇑ e_fn as ∀)
|
||||
;#:with [e_fn- (~∀ (X ...) (~ext-stlc:→ τ_inX ... τ_outX))] (infer+erase #'e_fn)
|
||||
;; infer type step-by-step to produce better err msg
|
||||
#:with [e_fn- τ_fn] (infer+erase #'e_fn)
|
||||
#:fail-unless (and (∀? #'τ_fn) (syntax-parse #'τ_fn [(~∀ _ (~ext-stlc:→ . args)) #t][_ #f]))
|
||||
(format "Expected expression ~a to have → type, got: ~a"
|
||||
(syntax->datum #'e_fn) (type->str #'τ_fn))
|
||||
#:with (~∀ (X ...) (~ext-stlc:→ τ_inX ... τ_outX)) #'τ_fn
|
||||
#:fail-unless (stx-length=? #'(τ_inX ...) #'(e_arg ...)) ; check arity
|
||||
(mk-app-err-msg stx #:expected #'(τ_inX ...) #:given #'(τ_arg ...)
|
||||
#:with (~∀ Xs (~ext-stlc:→ τ_inX ... τ_outX)) #'τ_fn
|
||||
#:with (τ_solved ...) (solve #'Xs #'(τ_inX ...) (syntax/loc stx (e_fn e_arg ...)))
|
||||
;; ) instantiate polymorphic fn type
|
||||
;; #:with cs (compute-constraints #'((τ_inX τ_arg) ...))
|
||||
;; #:with (τ_solved ...) (filter (λ (x) x) (stx-map (λ (y) (lookup y #'cs)) #'(X ...)))
|
||||
;; #:fail-unless (stx-length=? #'(X ...) #'(τ_solved ...))
|
||||
;; (mk-app-err-msg stx #:expected #'(τ_inX ...) #:given #'(τ_arg ...)
|
||||
;; #:note "Could not infer instantiation of polymorphic function.")
|
||||
#:with (τ_in ... τ_out) (stx-map
|
||||
(λ (t) (substs #'(τ_solved ...) #'Xs t))
|
||||
#'(τ_inX ... τ_outX))
|
||||
#:fail-unless (stx-length=? #'(τ_inX ...) #'(e_arg ...))
|
||||
(mk-app-err-msg stx #:expected #'(τ_inX ...)
|
||||
; #:given #'(τ_arg ...)
|
||||
#:note "Wrong number of arguments.")
|
||||
#:with cs (compute-constraints #'((τ_inX τ_arg) ...))
|
||||
#:with (τ_solved ...) (filter (λ (x) x) (stx-map (λ (y) (lookup y #'cs)) #'(X ...)))
|
||||
#:fail-unless (stx-length=? #'(X ...) #'(τ_solved ...))
|
||||
(mk-app-err-msg stx #:expected #'(τ_inX ...) #:given #'(τ_arg ...)
|
||||
#:note "Could not infer instantiation of polymorphic function.")
|
||||
#:with (τ_in ... τ_out) (stx-map (λ (t) (substs #'(τ_solved ...) #'(X ...) t)) #'(τ_inX ... τ_outX))
|
||||
; some code duplication
|
||||
;; ) compute argument types; (possibly) double-expand args (for now)
|
||||
#:with ([e_arg- τ_arg] ...) (infers+erase (stx-map add-expected-ty #'(e_arg ...) #'(τ_in ...)))
|
||||
;; ) arity check
|
||||
#:fail-unless (stx-length=? #'(τ_inX ...) #'(e_arg ...))
|
||||
(mk-app-err-msg stx #:expected #'(τ_inX ...)
|
||||
#:given #'(τ_arg ...)
|
||||
#:note "Wrong number of arguments.")
|
||||
;; ) typecheck args
|
||||
#:fail-unless (typechecks? #'(τ_arg ...) #'(τ_in ...))
|
||||
(mk-app-err-msg stx #:given #'(τ_arg ...) #:expected #'(τ_in ...))
|
||||
(⊢ (#%app e_fn- e_arg- ...) : τ_out)])
|
||||
|
||||
|
||||
;; ;; infer instantiation types from args left-to-right,
|
||||
;; ;; short-circuit if done early, and use result to help infer remaining args
|
||||
;; #:with (~Tmp Ys . τs+) ((current-type-eval) #'(Tmp (X ...) τ ...))
|
||||
;; (let loop ([a (stx-car #'args)] [a-rst (stx-cdr #'args)]
|
||||
;; [τ+ (stx-car #'τs+)] [τ+rst (stx-cdr #'τs+)]
|
||||
;; [old-cs #'()])
|
||||
;; (define/with-syntax [a- τ_a] (infer+erase a))
|
||||
;; (define cs (stx-append old-cs (compute-constraints (list (list τ+ #'τ_a)))))
|
||||
;; (define maybe-solved (filter syntax-e (stx-map (λ (y) (lookup y cs)) #'Ys)))
|
||||
;; (if (stx-length=? maybe-solved #'Ys)
|
||||
;; #`(C #,(syntax-property #`{#,@maybe-solved} 'paren-shape #\{) . args) ; loses 'paren-shape
|
||||
;; (if (or (stx-null? a-rst) (stx-null? τ+rst))
|
||||
;; (type-error #:src stx
|
||||
;; #:msg "could not infer types for constructor ~a; add annotations" #'(C . args))
|
||||
;; (loop (stx-car a-rst) (stx-cdr a-rst) (stx-car τ+rst) (stx-cdr τ+rst) cs))))]))
|
||||
|
||||
|
|
|
@ -78,7 +78,14 @@
|
|||
#:note [note ""]
|
||||
#:name [name #f])
|
||||
(syntax-parse stx
|
||||
[(_ e_fn e_arg ...)
|
||||
[(app . rst)
|
||||
#:when (not (equal? '#%app (syntax->datum #'app)))
|
||||
(mk-app-err-msg #'(#%app app . rst)
|
||||
#:expected expected-τs
|
||||
#:given given-τs
|
||||
#:note note
|
||||
#:name name)]
|
||||
[(app e_fn e_arg ...)
|
||||
(define fn-name
|
||||
(if name name
|
||||
(format "function ~a"
|
||||
|
@ -93,7 +100,9 @@
|
|||
(string-join
|
||||
(map (λ (e t) (format " ~a : ~a" e t)) ; indent each line
|
||||
(syntax->datum #'(e_arg ...))
|
||||
(stx-map type->str given-τs))
|
||||
(if (stx-length=? #'(e_arg ...) given-τs)
|
||||
(stx-map type->str given-τs)
|
||||
(stx-map (lambda (e) "?") #'(e_arg ...))))
|
||||
"\n")
|
||||
"\n")]))
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
(typecheck-fail (g2 1)
|
||||
#:with-msg
|
||||
(expected "(List X)" #:given "Int"
|
||||
#:note "Could not infer instantiation of polymorphic function"))
|
||||
#:note "Could not infer instantiation of polymorphic function"))
|
||||
|
||||
;; todo? allow polymorphic nil?
|
||||
(check-type (g2 (Nil {Int})) : (List Int) ⇒ (Nil {Int}))
|
||||
|
@ -68,7 +68,39 @@
|
|||
(match lst with
|
||||
[Nil -> Nil]
|
||||
[Cons x xs -> (Cons (f x) (map f xs))]))
|
||||
(check-type map : (→ (→ X Y) (List X) (List Y)))
|
||||
(check-type map : (→ (→ Y X) (List Y) (List X)))
|
||||
(check-type map : (→ (→ A B) (List A) (List B)))
|
||||
(check-not-type map : (→ (→ A B) (List B) (List A)))
|
||||
(check-not-type map : (→ (→ X X) (List X) (List X))) ; only 1 bound tyvar
|
||||
|
||||
; nil without annotation tests fn-first, left-to-right arg inference
|
||||
; does work yet, need to add left-to-right inference in #%app
|
||||
(check-type (map add1 Nil) : (List Int) ⇒ (Nil {Int}))
|
||||
(check-type (map add1 (Cons 1 (Cons 2 (Cons 3 Nil))))
|
||||
: (List Int) ⇒ (Cons 2 (Cons 3 (Cons 4 Nil))))
|
||||
(typecheck-fail (map add1 (Cons "1" Nil)))
|
||||
;#:with-msg (expected "Int" #:given "String")) ; TODO: fix err msg
|
||||
(check-type (map (λ ([x : Int]) (+ x 2)) (Cons 1 (Cons 2 (Cons 3 Nil))))
|
||||
: (List Int) ⇒ (Cons 3 (Cons 4 (Cons 5 Nil))))
|
||||
;; ; doesnt work yet: all lambdas need annotations
|
||||
;; (check-type (map (λ (x) (+ x 2)) (list 1 2 3)) : (List Int) ⇒ (list 3 4 5))
|
||||
|
||||
(define (filter [p? : (→ X Bool)] [lst : (List X)] → (List X))
|
||||
(match lst with
|
||||
[Nil -> Nil]
|
||||
[Cons x xs -> (if (p? x)
|
||||
(Cons x (filter p? xs))
|
||||
(filter p? xs))]))
|
||||
(check-type (filter zero? Nil) : (List Int) ⇒ (Nil {Int}))
|
||||
(check-type (filter zero? (Cons 1 (Cons 2 (Cons 3 Nil))))
|
||||
: (List Int) ⇒ (Nil {Int}))
|
||||
(check-type (filter zero? (Cons 0 (Cons 1 (Cons 2 Nil))))
|
||||
: (List Int) ⇒ (Cons 0 Nil))
|
||||
(check-type (filter (λ ([x : Int]) (not (zero? x))) (Cons 0 (Cons 1 (Cons 2 Nil))))
|
||||
: (List Int) ⇒ (Cons 1 (Cons 2 Nil)))
|
||||
; doesnt work yet: all lambdas need annotations
|
||||
;(check-type (filter (λ (x) (not (zero? x))) (list 0 1 2)) : (List Int) ⇒ (list 1 2))
|
||||
|
||||
;; end infer.rkt tests --------------------------------------------------
|
||||
|
||||
|
@ -311,7 +343,7 @@
|
|||
#:with-msg (expected "Int, Int" #:given "(→ Int Int), (→ Int Int)"))
|
||||
(typecheck-fail
|
||||
((λ ([x : Int] [y : Int]) y) 1)
|
||||
#:with-msg (expected "Int, Int" #:given "Int"
|
||||
#:with-msg (expected "Int, Int" #:given "1"
|
||||
#:note "Wrong number of arguments"))
|
||||
|
||||
(check-type ((λ ([x : Int]) (+ x x)) 10) : Int ⇒ 20)
|
||||
|
|
Loading…
Reference in New Issue
Block a user