[overloading] initial revision, overloading via parameter

This commit is contained in:
Ben Greenman 2015-10-21 00:47:21 -04:00
parent e6e538c3ed
commit 0b587697f4
4 changed files with 259 additions and 175 deletions

View File

@ -0,0 +1,163 @@
#lang s-exp "typecheck.rkt"
(reuse List cons nil #:from "stlc+cons.rkt")
(extends "stlc+sub.rkt" #:except #%datum #:rename [#%app stlc:#%app])
(reuse #:from "stlc+rec-iso.rkt") ; load current-type=?
;; Apparently cannot propogate type information across statements.
;; Until the first-class ones work, let's do the big dumb parameter
;; So here's what going to happen
;; - current-Σ will be a map from identifiers to resolvers
;; - resolvers will map overloaded signatures and types to concrete instances
;; - extending a resolver (via instance) will add a new (τ, e) pair to a mutable list
;; =============================================================================
(define-base-type Bot)
(define-base-type Str)
(define-typed-syntax #%datum
[(_ . n:str) ( (#%datum . n) : Str)]
[(_ . x) #'(stlc+sub:#%datum . x)])
(define-for-syntax xerox syntax->datum)
;; =============================================================================
;; === Resolvers
(begin-for-syntax
(struct (
name ;; Symbol
dom* ;; (Box (Listof (Pairof Type Expr)))
cod ;; Type
) #:transparent
#:property prop:procedure
(lambda (self τ-or-e)
(define r
(if (syntax? τ-or-e) ;; Can I ask "type?"
(-resolve-syntax self τ-or-e)
(-resolve-value self τ-or-e)))
(or r
(error ' (format "Resolution for '~a' failed at type ~a"
(syntax->datum (-name self))
τ-or-e))))
)
;; Rad!
(define (-add! τ e)
(define dom* (-dom* ))
(set-box! dom* (cons (cons τ e) (unbox dom*))))
(define (-init name τ-cod)
( name (box '()) τ-cod))
(define (->type )
((current-type-eval) #`( #,(assign-type #''α #'#%type) #,(-cod ))))
(define (-find τ)
(define τ=?
(let ([type=? (current-type=?)])
(lambda (τ2)
(type=? τ τ2))))
(assf τ=? (unbox (-dom* ))))
(define (-resolve-syntax τ)
(define result (-find τ))
(and (pair? result)
(cdr result)))
(define (-resolve-value e)
(error ' (format "Runtime resolution not implemented. Anyway your value was ~a" e)))
(define (-unbound? τ)
(not (-find τ)))
)
;; =============================================================================
;; === Overloaded signature environment
(begin-for-syntax
(define current-Σ (make-parameter (lambda (id) #f)))
)
(define-typed-syntax signature
[(_ (name:id α:id) τ)
#:with ((α+) (~→ τ_α:id τ-cod) _) (infer/tyctx+erase #'([α : #%type]) #'τ)
(let ([-old ((current-Σ) #'name)])
(when -old
(error 'signature (format "Identifier '~a' already bound to a type ~a"
(syntax->datum #'name) (syntax->datum (->type -old))))))
(define (-init #'name #'τ-cod))
(current-Σ
(let ([old-Σ (current-Σ)])
(lambda (id)
(if (free-identifier=? id #'name)
(old-Σ id)))))
( (define name #,)
: Bot)]
[(_ e* ...)
(error 'signature (format "Expected (signature (NAME VAR) (→ VAR τ)), got ~a" (xerox #'(e* ...))))])
;; TODO make this available to users
(define-for-syntax (resolve stx)
(syntax-parse stx
[(name:id τ)
#:with [name+ τ_fn+] (infer+erase #'name)
#:with τ+ ((current-type-eval) #'τ)
(define ((current-Σ) #'name+))
(unless
(error 'resolve (format "Identifier '~a' is not overloaded" (syntax->datum #'name+))))
( #'τ)]))
(define-typed-syntax #%app
[(_ e_fn:id e_arg)
#:with [e_fn+ τ_fn+] (infer+erase #'e_fn)
;; Beware 3D syntax
#:when ((current-Σ) #'e_fn+)
#:with [e_arg+ τ_arg+] (infer+erase #'e_arg)
(unless (syntax-e #'τ_arg+)
(error '#%app "No type for expression ~a\n" (syntax->datum #'e_arg)))
(define fn (resolve #'(e_fn+ τ_arg+)))
#`(#%app #,fn e_arg+)]
[(_ e* ...)
#'(stlc:#%app e* ...)])
(begin-for-syntax
(define-syntax-rule (instance-error id τ r)
(error 'instance (format "Cannot make instance for '~a' at type '~a'. ~a"
(syntax->datum id) (syntax->datum τ) r)))
)
(define-typed-syntax instance
[(_ (name:id τ-stx) e)
#:with τ ((current-type-eval) #'τ-stx)
#:with [e+ τ+] (infer+erase #'e)
(define ((current-Σ) #'name))
(unless (instance-error #'name #'τ "Not an overloaded identifier."))
(unless (-unbound? #'τ) (instance-error #'name #'τ "Overlaps with existing instance."))
(define does-this-id-matter?
(syntax-parse #`(τ+ #,(->type ))
[((~→ τ_dom1 τ_cod1)
(~→ _ τ_cod2))
;; Really, need to unify this type with the template
;; (unless ((current-type=?) τ_dom1 τ_dom2)
;; (instance-error #'name #'τ (format "Domain '~a' must unify with template domain '~a'."
;; (syntax->datum #'τ_dom1) (syntax->datum #'τ_dom2))))
(unless ((current-type=?) ((current-type-eval) #'τ) #'τ_dom1)
(instance-error #'name #'τ (format "Domain '~a' must be the instance type, for now (2015-10-20)." (syntax->datum #'τ_dom1))))
(unless ((current-type=?) #'τ_cod1 #'τ_cod2)
(instance-error #'name #'τ (format "Codomain '~a' must match template codomain '~a'"
(syntax->datum #'τ_cod1) (syntax->datum #'τ_cod2))))
(void)]
[(a b)
(instance-error #'name #'τ (format "May only overload single-argument functions. (Got ~a and ~a)"
(syntax->datum #'a) (syntax->datum #'b))
)]))
(-add! #'τ #'e+) ;; Should we use syntax instead of e+ ?
( (void) : Bot)]
[_
(error 'instance "Expected (instance (id τ) e).")])

View File

@ -1,123 +0,0 @@
#lang s-exp "typecheck.rkt"
(extends "stlc+sub.rkt" #:except #%datum); #:rename [#%app stlc:#%app])
(reuse #:from "stlc+rec-iso.rkt") ; load current-type=?
;(extends "stlc+tup.rkt" #:except + #%datum and)
;(extends "stlc+cons.rkt" #:except + #%datum and)
;; Parametric overloading.
;; - define overloadable functions with "template" types
;; - later, add implementations
;; -- typecheck the impl
;; -- save in a table
;; - for app, lookup the overloaded ID
;; - allow higher-order positions
;; Implementation strategy
;; - make explicit type for overloadables
;; showing the __free variables__ and __instance carrier__
;; - new instances update the carrier
;; - lookups query the type; the type contains the lookup table
;; =============================================================================
(define-base-type Bot)
(define-base-type Str)
(define-typed-syntax #%datum
[(_ . n:str) ( (#%datum . n) : Str)]
[(_ . x) #'(stlc+sub:#%datum . x)])
;; =============================================================================
;; === ψ types
;; TODO make it arity 2
(define-type-constructor ψ #:arity = 1 #:bvs = 1)
(begin-for-syntax
(define ψ-eval
(let ([τ-eval (current-type-eval)])
(lambda (τ-stx)
(define τ+ (τ-eval τ-stx))
(syntax-parse τ+
[( (α) τ)
;; TODO ?
τ+]
[_ τ+]))))
(current-type-eval ψ-eval)
;; TODO my types are unequal. How does equality for ∀ types work?
;; (define ψ=?
)
;; =============================================================================
;; === Signature maps
;; Covert a type to an implementation. Use current-type-eval to normalize keys
(begin-for-syntax
(define (Σ-print Σ port mode)
(define (print-k+v k+v)
(display (syntax->datum (car k+v)) port))
(display "{" port)
(define k+v* (Σ-map Σ))
(when (not (null? k+v*))
(print-k+v (car k+v*))
(for ([k+v (in-list k+v*)])
(display " " port)
(print-k+v k+v)))
(display "}" port))
(struct Σ (
map ;; (Listof (Pairof τ* expr)), maps types to implementations
) #:property prop:procedure
(lambda (self arg)
(error 'Σ "Cannot apply struct, don't yet know how to turn types into predicates"))
#:methods gen:custom-write
[(define write-proc Σ-print)])
(define Σ-empty
(Σ '()))
(define (Σ-key* Σ)
(map car (Σ-map Σ)))
)
;; =============================================================================
(begin-for-syntax
(define-syntax-rule (signature-error τ reason)
(error 'signature (format "Cannot define overloadable signature for at type '~a'. ~a"
(syntax->datum τ) reason)))
)
(define-typed-syntax signature
[(_ (α:id) τ)
;; Expand the type τ with α bound as a valid type
#:with ((α+) τ+ _) (infer/tyctx+erase #'([α : #%type]) #'τ)
;; Make sure τ is (→ α τ') for some real type τ'
#:when (syntax-parse #'τ+
[(~→ τ-dom τ-cod)
;; τ-dom MUST be the (expanded) identifier α+
(unless (and (identifier? #'τ-dom)
(free-identifier=? #'τ-dom #'α+))
(signature-error #'τ
(format "Variable '~a' must be free in the signature type." (syntax->datum #'α))))]
[_
(signature-error #'τ "We only support single-argument functions with overloaded domains.")])
;; Using define to ensure top-level use
;; (define k* (assign-type #'() #'#%type))
( #,Σ-empty
: (ψ (α) τ))]) ;; TODO add Σ-keys to the type?
#;(define-typed-syntax #%app
[(_ e_fn e_arg)
#:with [e_fn+ τ_fn] (infers+erase #'e_fn)
#:when (ψ? #'τ_fn)
#:when (error 'APP (format "YOLO e = ~a arg = ~a τ = ~a"
(syntax->datum #'e_fn)
(syntax->datum #'e_arg)
(syntax->datum #'τ_fn)))
#'(void)
]
[(_ e* ...)
#'(stlc:#%app e* ...)])

View File

@ -0,0 +1,96 @@
#lang s-exp "../stlc+overloading-param.rkt"
(require "rackunit-typechecking.rkt")
;; -----------------------------------------------------------------------------
;; --- syntax for ψ types
(typecheck-fail
(signature (to-string0 α) ( α Str Str))
#:with-msg "Expected")
(typecheck-fail
(signature (to-string0 α) ( Str Str))
#:with-msg "Expected")
(typecheck-fail
(signature (to-string0 α) ( α Str))
#:with-msg "not allowed in an expression context")
;; -----------------------------------------------------------------------------
;; --- basic overloading
(signature (to-string α) ( α Str))
(typecheck-fail
(to-string 1)
#:with-msg "Resolution for 'to-string' failed")
(typecheck-fail
(to-string "yolo")
#:with-msg "Resolution for 'to-string' failed")
;; -- can later add cases to an overloaded name
(instance (to-string Nat)
(λ ([x : Nat]) "num"))
(instance (to-string Str)
(λ ([x : Str]) "string"))
;; TODO can't use check-type for some reason. Literal #f is not allowed... missing type?
;; (check-type-and-result
(to-string 3)
;; : Str ⇒ "num")
;; (check-type-and-result
;; (to-string (+ 2 2))
;; : Str ⇒ "num")
;; (check-type-and-result
(to-string "hi")
;; : Str ⇒ "string")
;; -- instances are type-checked. They must match
(typecheck-fail
(instance (to-string Int)
(λ ([x : Num]) "num"))
#:with-msg "must be the instance type")
(typecheck-fail
(instance (to-string Int)
(λ ([x : Int]) 0))
#:with-msg "must match template codomain")
(typecheck-fail
(instance (to-string Int)
42)
#:with-msg "May only overload single-argument functions")
;; -- no overlapping instances
(typecheck-fail
(instance (to-string Nat)
(λ ([x : Nat]) "wrong"))
#:with-msg "Overlaps with existing instance")
;; -- can't instantiate non-overloadeds
(typecheck-fail
(λ ([x : ( Int Int)])
(instance (x Int)
0))
#:with-msg "Not an overloaded identifier")
;; -- subtypes do not overlap
(instance (to-string Int)
(λ ([x : Int]) "int"))
;; -- explicit resolve
;; -- recursive instances are fine [TODO really want (List α)]
(instance (to-string (List Nat))
(λ ([x : (List Nat)]) "listnat"))
;; (check-type-and-result
(to-string (cons 1 (cons 2 (nil {Nat}))))
;; : Str ⇒ "listnat")
;; -- higher-order use

View File

@ -1,52 +0,0 @@
#lang s-exp "../stlc+overloading.rkt"
(require "rackunit-typechecking.rkt")
;; -----------------------------------------------------------------------------
;; --- syntax for ψ types
(typecheck-fail
(signature (α) ( α Str Str))
#:with-msg "We only support single-argument functions")
(typecheck-fail
(signature (α) ( Str Str))
#:with-msg "Variable 'α' must be free")
(check-type
(signature (α) ( α Str))
: (ψ (α) ( α Str)))
;; -----------------------------------------------------------------------------
;; --- basic overloading
;; -- creating a signature does nothing, at first
;; (signature (to-string α) (→ α Str))
;; (typecheck-fail
;; (to-string 1)
;; #:with-msg "no instance")
;; (typecheck-fail
;; (to-string "yolo")
;; #:with-msg "no instance")
;; ;; -- can later add cases to an overloaded name
;; (instance (to-string Num)
;; (λ ([x : Num]) "num"))
;; (instance (to-string Str)
;; (λ ([x : Str]) "string"))
;; (check-type-and-result
;; (to-string 43)
;; : Str ⇒ "num")
;; (check-type-and-result
;; (to-string (+ 2 2))
;; : Str ⇒ "num")
;; (check-type-and-result
;; (to-string "hi")
;; : Str ⇒ "string")
;; ;; --