[occurrence] filters for products

This commit is contained in:
Ben Greenman 2015-10-16 18:56:54 -04:00
parent 36bec40650
commit 1887d99abb
2 changed files with 263 additions and 66 deletions

View File

@ -1,5 +1,6 @@
#lang s-exp "typecheck.rkt"
(extends "stlc+sub.rkt" #:except #%datum)
(extends "stlc+tup.rkt" #:except + #%datum and)
;; Calculus for occurrence typing.
;; - Types can be simple, or sets of simple types
@ -147,35 +148,36 @@
[else
(loop x* (cdr y*))])))
(define sub? (current-sub?))
(define (-sub? τ1-stx τ2-stx)
(define τ1 ((current-type-eval) τ1-stx))
(define τ2 ((current-type-eval) τ2-stx))
(or (Bot? τ1) (Top? τ2)
(match `(,(? τ1) ,(? τ2))
['(#f #t)
;; AMB : a<:b => a <: (U ... b ...)
(for/or ([τ (in-list (->list τ2))])
(sub? τ1 τ))]
['(#t #t)
(define τ1* (->list τ1))
(define τ2* (->list τ2))
(match `(,(length τ1*) ,(length τ2*))
[`(,L1 ,L2) #:when (< L1 L2)
;; - EXT : (U t' ...) <: (U t t' ...)
(subset? τ1* τ2* #:leq sub?)]
[`(,L1 ,L2) #:when (= L1 L2)
;; - SUB : a<:b => (U a t' ...) <: (U b t' ...)
;; `->list` guarantees same order on type members
;; `sub?` is reflexive
(andmap sub? τ1* τ2*)]
[_ #f])]
['(#t #f)
;; - ALL : t... <: t' => (U t ...) <: t'
(andmap (lambda (τ) (sub? τ τ2)) (->list τ1))]
['(#f #f)
(sub? τ1 τ2)])))
(define -sub?
(let ([sub? (current-sub?)])
(lambda (τ1-stx τ2-stx)
(define τ1 ((current-type-eval) τ1-stx))
(define τ2 ((current-type-eval) τ2-stx))
(or (Bot? τ1) (Top? τ2)
(match `(,(? τ1) ,(? τ2))
['(#f #t)
;; AMB : a<:b => a <: (U ... b ...)
(for/or ([τ (in-list (->list τ2))])
((current-sub?) τ1 τ))]
['(#t #t)
(define τ1* (->list τ1))
(define τ2* (->list τ2))
(match `(,(length τ1*) ,(length τ2*))
[`(,L1 ,L2) #:when (< L1 L2)
;; - EXT : (U t' ...) <: (U t t' ...)
(subset? τ1* τ2* #:leq (current-sub?))]
[`(,L1 ,L2) #:when (= L1 L2)
;; - SUB : a<:b => (U a t' ...) <: (U b t' ...)
;; `->list` guarantees same order on type members
;; `sub?` is reflexive
(andmap (current-sub?) τ1* τ2*)]
[_ #f])]
['(#t #f)
;; - ALL : t... <: t' => (U t ...) <: t'
(andmap (lambda (τ) ((current-sub?) τ τ2)) (->list τ1))]
['(#f #f)
;; Fall back to OLD sub
(sub? τ1 τ2)])))))
(current-sub? -sub?)
(current-typecheck-relation (current-sub?))
@ -187,49 +189,128 @@
;; Makes it easy to add a new filter & avoids duplicating this map
(begin-for-syntax
(define current-Π (make-parameter (lambda (x) (error 'Π))))
(define (simple-Π τ)
(syntax-parse (τ-eval τ)
[~Boolean
#'boolean?]
[~Int
#'integer?]
[~Str
#'string?]
[~Num
#'number?]
[~Nat
#'(lambda (n) (and (integer? n) (not (negative? n))))]
[(~→ τ* ... τ)
(define k (stx-length #'(τ* ...)))
#`(lambda (f) (and (procedure? f) (procedure-arity-includes? f #,k #f)))]
[(~ τ* ...)
(define filter* (for/list ([τ (in-syntax #'(τ* ...))])
((current-Π) τ)))
#`(lambda (v) (for/or ([f (in-list (list #,@filter*))]) (f v)))]
[_
(error 'Π "Cannot make filter for type ~a\n" (syntax->datum τ))]))
(current-Π simple-Π))
(define current-Π (make-parameter (lambda (x) (error 'Π))))
(define (type->filter τ)
(define f ((current-Π) τ))
(unless f
(error 'τ->filter (format "Could not express type '~a' as a filter." (syntax->datum τ))))
f)
(define (type*->filter* τ*)
(map (current-Π) τ*))
(define (simple-Π τ)
(syntax-parse (τ-eval τ)
[~Boolean
#'boolean?]
[~Int
#'integer?]
[~Str
#'string?]
[~Num
#'number?]
[~Nat
#'(lambda (n) (and (integer? n) (not (negative? n))))]
[(~→ τ* ... τ)
(define k (stx-length #'(τ* ...)))
#`(lambda (f) (and (procedure? f) (procedure-arity-includes? f #,k #f)))]
[(~ τ* ...)
(define filter* (type*->filter* (syntax->list #'(τ* ...))))
#`(lambda (v) (for/or ([f (in-list (list #,@filter*))]) (f v)))]
[_
(error 'Π "Cannot make filter for type ~a\n" (syntax->datum τ))]))
(current-Π simple-Π)
)
;; (test (τ ? x) e1 e2)
;; TODO:
;; - check if τ0 is a union type
;; - check if τ-filter is a subtype of τ0
;; - drop absurd branches?
;; - allow x not identifier (1. does nothing 2. latent filters)
(define-typed-syntax test #:datum-literals (?)
[(_ [τ-filter:type ? x-stx:id] e1 e2)
;; Get the filter type, evaluate to a runtime predicate
#:with f ((current-Π) #'τ-filter)
#:fail-unless (syntax-e #'f)
(format "Could not express type '~a' as a filter." #'τ-filter-stx)
;; TypeCheck e0:normally, e1:positive, e2:negative
;; -- THIS CASE BELONGS IN A NEW FILE
[(_ [τ0+:type ? (unop x-stx:id n-stx:nat)] e1 e2)
;; 1. Check that we're using a known eliminator
#:when (free-identifier=? #'proj #'unop)
;; 2. Make sure we're filtering with a valid type
#:with f (type->filter #'τ0+)
;; 3. Typecheck the eliminator call. Remember the type & apply the filter.
;; (This type is PROBABLY a union -- else why bother testing!)
#:with (e0+ τ0) (infer+erase #'(unop x-stx n-stx))
#:with τ0- ( #'τ0 #'τ0+)
;; 4. Build the +/- types for our identifier; the thing we apply the elim. + test to
;; We know that x has a pair type because (proj x n) typechecked
#:with (x (~× τi* ...)) (infer+erase #'x-stx)
#:with τ+ #`(× #,@(replace-at (syntax->list #'(τi* ...)) (syntax-e #'n-stx) #'τ0+))
#:with τ- #`(× #,@(replace-at (syntax->list #'(τi* ...)) (syntax-e #'n-stx) #'τ0-))
;; 5. Check the branches with the refined types
#:with [x1 e1+ τ1] (infer/ctx+erase #'([x-stx : τ+]) #'e1)
#:with [x2 e2+ τ2] (infer/ctx+erase #'([x-stx : τ-]) #'e2)
;; 6. Desugar, replacing the filtered identifier
( (if (f e0+)
((lambda x1 e1+) x-stx)
((lambda x2 e2+) x-stx))
: ( τ1 τ2))]
;; -- THE ORIGINAL
[(_ [τ0+:type ? x-stx:id] e1 e2)
#:with f (type->filter #'τ0+)
#:with (x τ0) (infer+erase #'x-stx)
#:with [x1 e1+ τ1] (infer/ctx+erase #'([x-stx : τ-filter]) #'e1)
#:with [x2 e2+ τ2] (infer/ctx+erase #`([x-stx : #,( #'τ0 #'τ-filter)]) #'e2)
#:with τ0- ( #'τ0 #'τ0+)
#:with [x1 e1+ τ1] (infer/ctx+erase #'([x-stx : τ0+]) #'e1)
#:with [x2 e2+ τ2] (infer/ctx+erase #'([x-stx : τ0-]) #'e2)
;; Expand to a conditional, using the runtime predicate
( (if (f x-stx)
((lambda x1 e1+) x-stx)
((lambda x2 e2+) x-stx))
: ( τ1 τ2))])
;; =============================================================================
;; === BELONGS IN A NEW FILE
;; (extends "stlc+occurrence.rkt"); #:rename [test ot:test])
;; (extends "stlc+tup.rkt" #:except + #%datum)
(define-for-syntax (replace-at τ* n τ-new)
(for/list ([τ-old (in-list τ*)]
[i (in-naturals)])
(if (= i n)
τ-new
τ-old)))
;; Add subtyping for tuples
(begin-for-syntax
(define ×-sub?
(let ([sub? (current-sub?)])
(lambda (τ1-stx τ2-stx)
(define τ1 ((current-type-eval) τ1-stx))
(define τ2 ((current-type-eval) τ2-stx))
(or (Bot? τ1) (Top? τ2)
(syntax-parse `(,τ1 ,τ2)
[((~× τi1* ...)
(~× τi2* ...))
(and (stx-length=? #'(τi1* ...)
#'(τi2* ...))
;; Gotta use (current-sub?), because products may be recursive
(stx-andmap (current-sub?) #'(τi1* ...) #'(τi2* ...)))]
[_
(sub? τ1 τ2)])))))
(current-sub? ×-sub?)
(current-typecheck-relation (current-sub?)))
;; --- Update Π for products
(begin-for-syntax
(define π-Π
(let ([Π (current-Π)])
(lambda (τ)
(syntax-parse (τ-eval τ)
[(~× τ* ...)
(define filter* (type*->filter* (syntax->list #'(τ* ...))))
#`(lambda (v*)
(and (list? v*)
(for/and ([v (in-list v*)]
[f (in-list (list #,@filter*))])
(f v))))]
[_ ;; Fall back
(Π τ)]))))
(current-Π π-Π))

View File

@ -374,6 +374,125 @@
"error")) "hi")
: ( Int Str) "hi")
;; -----------------------------------------------------------------------------
;; --- Subtyping products
(check-type (tup 1) : (× Nat))
(check-type (tup 1) : (× Int))
(check-type (tup 1) : (× Num))
(check-type (tup 1) : (× Top))
(check-type (tup 1) : Top)
(check-not-type (tup 1) : Boolean)
(check-not-type (tup 1) : Str)
(check-not-type (tup 1) : (× Str))
(check-not-type (tup 1) : (× Int Str))
(check-not-type (tup 1) : Bot)
(check-type (tup 1 2 3) : (× Int Nat Num))
(check-type (tup 1 2 3) : (× Num Nat Num))
(check-type (tup 1 2 3) : (× Top Top Num))
(check-type (tup 1 "2" 3) : (× Int Top Int))
(check-not-type (tup 1 2 3) : (× Nat Nat Str))
;; -----------------------------------------------------------------------------
;; --- Latent filters (on products)
(check-type
(λ ([v : (× ( Int Str) Int)])
(test (Int ? (proj v 0))
(+ (proj v 0) (proj v 1))
0))
: ( (× ( Int Str) Int) Num))
(check-type-and-result
((λ ([v : (× ( Int Str) Int)])
(test (Int ? (proj v 0))
(+ (proj v 0) (proj v 1))
0))
(tup ((λ ([x : ( Int Str)]) x) -2) -3))
: Num -5)
(check-type-and-result
((λ ([v : (× ( Int Str) Int)])
(test (Int ? (proj v 0))
(+ (proj v 0) (proj v 1))
0))
(tup "hi" -3))
: Num 0)
;; --- Use a product as filter
(check-type
(λ ([x : ( Int (× Int Int Int))])
(test (Int ? x)
(+ 1 x)
(+ (proj x 0) (+ (proj x 1) (proj x 2)))))
: ( ( (× Int Int Int) Int) Num))
(check-type-and-result
((λ ([x : ( Int (× Int Int Int))])
(test (Int ? x)
(+ 1 x)
(+ (proj x 0) (+ (proj x 1) (proj x 2)))))
0)
: Num 1)
(check-type-and-result
((λ ([x : ( Int (× Int Int Int))])
(test (Int ? x)
(+ 1 x)
(+ (proj x 0) (+ (proj x 1) (proj x 2)))))
(tup 2 2 2))
: Num 6)
(check-type-and-result
((λ ([x : ( Int (× Str Nat) (× Int Int Int))])
(test (Int ? x)
(+ 1 x)
(test ((× Int Int Int) ? x)
(+ (proj x 0) (+ (proj x 1) (proj x 2)))
(proj x 1))))
(tup 2 2 2))
: Num 6)
(check-type-and-result
((λ ([x : ( Int (× Str Nat) (× Int Int Int))])
(test (Int ? x)
(+ 1 x)
(test ((× Int Int Int) ? x)
(+ (proj x 0) (+ (proj x 1) (proj x 2)))
(proj x 1))))
(tup "yolo" 33))
: Num 33)
;; -- All together now
(check-type-and-result
((λ ([x : ( Int (× Boolean Boolean) (× Int ( Str Int)))])
(test (Int ? x)
"just an int"
(test ((× Boolean Boolean) ? x)
"pair of bools"
(test (Str ? (proj x 1))
(proj x 1)
"pair of ints"))))
(tup 33 "success"))
: Str "success")
(check-type-and-result
((λ ([x : ( Int (× Int Int) (× Int ( Str Int)))])
(test (Int ? x)
"just an int"
(test ((× Int Int) ? x)
"pair of ints"
(test (Str ? (proj x 1))
(proj x 1)
"another pair of ints"))))
(tup 33 "success"))
: Str "success")
;; -----------------------------------------------------------------------------
;; --- TODO CPS filters
@ -387,6 +506,3 @@
;; -----------------------------------------------------------------------------
;; --- TODO Values as filters (check equality)
;; -----------------------------------------------------------------------------
;; --- TODO Latent filters (on data structures)