#lang s-exp "redex-curnel.rkt" ;; Use racket libraries over your dependently typed code!?!? ;; TODO: actually, I'm not sure this should work quite as well as it ;; seems to with check-equal? (require rackunit) (require (only-in "redex-curnel.rkt" [#%app real-app] [define real-define])) (define-syntax (#%app syn) (syntax-case syn () [(_ e1 e2) #'(real-app e1 e2)] [(_ e1 e2 e3 ...) #'(#%app (#%app e1 e2) e3 ...)])) ;; ------------------- ;; Define inductive data (data true : Type (T : true)) (data false : Type) ;; ------------------- ;; Look, syntax extension! (define-syntax (-> syn) (syntax-case syn () [(_ t1 t2) (with-syntax ([(x) (generate-temporaries '(1))]) #`(forall (x : t1) t2))])) (data nat : Type (z : nat) (s : (-> nat nat))) (define nat-is-now-a-type (lambda (y : (-> nat nat)) (lambda (x : nat) (y x)))) ;; ------------------- ;; Lexical scoping! I can reuse the name nat! (define nat-is-just-a-name (lambda (nat : Type) nat)) ;; ------------------- ;; Reuse some familiar syntax (define y (lambda (x : true) x)) ;(define (y1 (x : true)) x) ;(define (y2 (x1 : true) (x2 : true)) x1) ;; ------------------- ;; Unit test dependently typed code! (check-equal? (y2 T T) T) ;; ------------------- ;; Write functions on inductive data (define (add1 (n : nat)) (s n)) (check-equal? (add1 (s z)) (s (s z))) (define (sub1 (n : nat)) (case n [z z] [s (lambda (x : nat) x)])) (check-equal? (sub1 (s z)) z) (define plus (fix (plus : (forall (n1 : nat) (forall (n2 : nat) nat))) (lambda (n1 : nat) (lambda (n2 : nat) (case n1 [z n2] [s (λ (x : nat) (plus x (s n2)))]))))) (check-equal? (plus (s (s z)) (s (s z))) (s (s (s (s z))))) ;; ------------------- ;; It's annoying to have to write things explicitly curried ;; Macros to the rescue! (define-syntax forall* (syntax-rules (:) [(_ (a : t) (ar : tr) ... b) (forall (a : t) (forall* (ar : tr) ... b))] [(_ b) b])) (define-syntax lambda* (syntax-rules (:) [(_ (a : t) (ar : tr) ... b) (lambda (a : t) (lambda* (ar : tr) ... b))] [(_ b) b])) (define-syntax (define syn) (syntax-case syn () [(define (name (x : t) ...) body) #'(real-define name (lambda* (x : t) ... body))] [(define id body) #'(real-define id body)])) (data and : (forall* (A : Type) (B : Type) Type) (conj : (forall* (A : Type) (B : Type) (x : A) (y : B) (and A B)))) ;; ------------------- ;; Prove interesting theorems! (define (thm:and-is-symmetric (x : (forall* (P : Type) (Q : Type) ;; TODO: Can't use -> for the final clause because generated ;; name has to match name in proof. (ab : (and P Q)) (and P Q)))) T) (define proof:and-is-symmetric (lambda* (P : Type) (Q : Type) (ab : (and P Q)) (case ab (conj (lambda* (P : Type) (Q : Type) (x : P) (y : Q) (conj Q P y x)))))) (check-equal? (thm:and-is-symmetric proof:and-is-symmetric) T) ;; ------------------- ;; Ugh, why did the language implementer make the syntax for case so stupid? ;; I wish I could fix that ... Oh I can. (begin-for-syntax (define (rewrite-clause clause) (syntax-case clause (:) [((con (a : A) ...) body) #'(con (lambda* (a : A) ... body))] [(e body) #'(e body)]))) (define-syntax (case* syn) (syntax-case syn () [(_ e clause* ...) #`(case e #,@(map rewrite-clause (syntax->list #'(clause* ...))))])) (define proof:and-is-symmetric^ (lambda* (S : Type) (R : Type) (ab : (and S R)) (case* ab [(conj (S : Type) (R : Type) (s : S) (r : R)) (conj R S r s)]))) (check-equal? (thm:and-is-symmetric proof:and-is-symmetric^) T) ;; ------------------- ;; Gee, I wish there was a special syntax for theorems and proofs so I could think of ;; them as seperate from types and programs. (define-syntax-rule (define-theorem name prop) (define name prop)) (define-syntax-rule (qed thm pf) (check-equal? T ((lambda (x : thm) T) pf))) (define-theorem thm:and-is-symmetric^^ (forall* (P : Type) (Q : Type) (ab : (and P Q)) (and Q P))) (qed thm:and-is-symmetric^^ proof:and-is-symmetric) (define-theorem thm:proj1 (forall* (A : Type) (B : Type) (c : (and A B)) A)) (define proof:proj1 (lambda* (A : Type) (B : Type) (c : (and A B)) (case c (conj (lambda* (A : Type) (B : Type) (a : A) (b : B) a))))) (qed thm:proj1 proof:proj1) (define-theorem thm:proj2 (forall* (A : Type) (B : Type) (c : (and A B)) B)) (define proof:proj2 (lambda* (A : Type) (B : Type) (c : (and A B)) (case c (conj (lambda* (A : Type) (B : Type) (a : A) (b : B) b))))) (qed thm:proj2 proof:proj2) ;; ------------------- ;; Gee, I wish I had special syntax for defining types like I do for ;; defining functions. (define-syntax-rule (define-type (name (a : t) ...) body) (define name (forall* (a : t) ... body))) (define-type (not (A : Type)) (-> A false)) #| ;;TODO: Can't pattern match on a inductive with 0 constructors yet. (define-theorem thm:absurd (forall* (P : Type) (A : Type) (a : A) (~a : (not A)) P)) (qed thm:absurd (lambda* (P : Type) (A : Type) (a : A) (~a : (not A)) (case (~a a)))) (define-theorem thm:absurdidty (forall* (P : Type) (A : Type) (a*nota : (and A (not A))) P)) ;; TODO: Not sure why this doesn't type-check.. probably not handling ;; inductive families correctly in (case ...) (define (proof:absurdidty (P : Type) (A : Type) (a*nota : (and A (not A)))) ((proof:proj2 A (not A) a*nota) (proof:proj1 A (not A) a*nota))) |# ;; ------------------- ;; Automate theorem proving! With real meta-programming, syntax is just data. (define-syntax (inhabit-type syn) (syntax-case syn (true false nat forall :) [(_ true) #'T] [(_ nat) #'z] [(_ false) (raise-syntax-error 'inhabit "Actually, this type is unhabited" syn)] ;; TODO: We want all forall*s to be expanded by this point. Should ;; look into Racket macro magic to expand syn before matching on it. [(_ (forall (x : t0) t1)) ;; TODO: Should carry around assumptions to enable inhabit-type to use ;; them #'(lambda (x : t0) (inhabit-type t1))] [(_ t) (raise-syntax-error 'inhabit "Sorry, this type is too much for me" syn)])) ;; TODO: Would be great if meta-programs could reference things by name. ;; e.g. if I run (inhabit-type thm:true-is-proveable), it would first ;; lookup the syntax of the definition thm:true-is-proveable, then ;; run ... this would require some extra work in define, and a macro for ;; defining macros this way. (define-theorem thm:true-is-proveable true) (qed thm:true-is-proveable (inhabit-type true)) (define-theorem thm:anything-implies-true (forall (P : Type) true)) (qed thm:true-is-proveable (inhabit-type (forall (P : Type) true))) #;(define is-this-inhabited? (inhabit-type false)) ;; ------------------- ;; Unit test your theorems before proving them! (define-syntax ->* (syntax-rules () [(->* a) a] [(->* a a* ...) (-> a (->* a* ...))])) (define-theorem thm:true? (forall* (A : Type) (B : Type) (P : Type) ;; If A implies P and (and A B) then P (->* (-> A P) (and A B) P))) (qed (thm:true? true true true) ;; TODO: inhabit-type ought to be able to reduce (type-thm:true? true true true) ;; but can't. Maybe instead there should be a reduce tactic/macro. (inhabit-type (forall (a : (-> true true)) (forall (f : (and true true)) true)))) ;; ------------------- ;; Okay but what about *real* proofs, like formalizing STLC, complete ;; with fancy syntax? (data type : Type (Unit : type) (Pair : (->* type type type)) (Fun : (->* type type type))) (data var : Type (v : (-> nat var))) (data term : Type (tvar : (-> var term)) (unitv : term) (pair : (->* term term term)) (lam : (->* var type term term)) (prj : (->* nat term term)) (app : (->* term term term))) ;; Now, fancy syntax to write these terms in a more natural syntax, ;; instead of via applications of inductives. Also, use variables names ;; and automatically get de Bruijn indecies. (begin-for-syntax (define index #'z)) (define-syntax (begin-stlc syn) (set! index #'z) (let stlc ([syn (syntax-case syn () [(_ e) #'e])]) (syntax-case syn (lambda : prj * -> quote) [(lambda (x : t) e) (let ([oldindex index]) (set! index #`(s #,index)) #`((lambda (x : term) (lam (v #,oldindex) #,(stlc #'t) #,(stlc #'e))) (tvar (v #,oldindex))))] [(quote (e1 e2)) #`(pair #,(stlc #'e1) #,(stlc #'e2))] [(prj i e1) #`(prj i #,(stlc #'e1))] [() #'unitv] [(t1 * t2) #`(Pair #,(stlc #'t1) #,(stlc #'t2))] [(t1 -> t2) #`(Fun #,(stlc #'t1) #,(stlc #'t2))] [(e1 e2) #`(app #,(stlc #'e1) #,(stlc #'e2))] [e (if (eq? 1 (syntax->datum #'e)) #'Unit #'e)]))) (check-equal? (begin-stlc (lambda (x : 1) x)) (lam (v z) Unit (tvar (v z)))) (check-equal? (begin-stlc ((lambda (x : 1) x) ())) (app (lam (v z) Unit (tvar (v z))) unitv)) (check-equal? (begin-stlc '(() ())) (pair unitv unitv)) ;; --------------- (data gamma : Type (emp-gamma : gamma) (ext-gamma : (->* gamma var type gamma))) (data maybe : (forall (A : Type) Type) (none : (forall (A : Type) (maybe A))) (some : (forall* (A : Type) (a : A) (maybe A)))) (data bool : Type (btrue : bool) (bfalse : bool)) (define-syntax (if syn) (syntax-case syn () [(_ t s f) #'(case t [btrue s] [bfalse f])])) (define-syntax (define-rec syn) (syntax-case syn (:) [(_ (name (a : t) ... : t_res) body) #'(define name (fix (name : (forall* (a : t) ... t_res)) (lambda* (a : t) ... body)))])) (define-rec (nat-equal? (n1 : nat) (n2 : nat) : bool) (case* n1 [z (case* n2 [z btrue] [(s (n3 : nat)) bfalse])] [(s (n3 : nat)) (case* n2 [z btrue] [(s (n4 : nat)) (nat-equal? n3 n4)])])) (define (var-equal? (v1 : var) (v2 : var)) (case* v1 [(v (n1 : nat)) (case* v2 [(v (n2 : nat)) (nat-equal? n1 n2)])])) (define-rec (lookup-gamma (g : gamma) (x : var) : (maybe type)) (case* g [emp-gamma (none type)] [(ext-gamma (g1 : gamma) (v1 : var) (t1 : type)) (if (var-equal? v1 x) (some type t1) (lookup-gamma g1 x))])) (define extend-gamma ext-gamma) (data == : (forall* (A : Type) (x : A) (-> A Type)) (refl : (forall* (A : Type) (x : A) (== A x x)))) #| (data has-type : (->* gamma term type Type) (T-Unit : (forall* (g : gamma) (has-type g unitv Unit))) ....) |# ;; Those inferrence rules look terrible. Gee, it would be great if I ;; could write them in a more natural, math notation.... (begin-for-syntax (define-syntax-class dash (pattern x:id #:fail-unless (regexp-match #rx"-+" (symbol->string (syntax-e #'x))) "Invalid dash")) (define-syntax-class decl (pattern (x:id (~datum :) t:id))) ;; TODO: Automatically infer decl ... by binding all free identifiers? (define-syntax-class inferrence-rule (pattern (d:decl ... x*:expr ... line:dash lab:id (name:id y* ...)) #:with rule #'(lab : (forall* d ... (->* x* ... (name y* ...))))))) (define-syntax (define-relation syn) (syntax-parse syn [(_ (n:id types* ...) rules:inferrence-rule ...) #:fail-unless (andmap (curry equal? (length (syntax->datum #'(types* ...)))) (map length (syntax->datum #'((rules.y* ...) ...)))) "Mismatch between relation declared and relation definition" #:fail-unless (andmap (curry equal? (syntax->datum #'n)) (syntax->datum #'(rules.name ...))) "Mismatch between relation declared name and result of inference rule" #`(data n : (->* types* ... Type) rules.rule ...)])) (define-relation (has-type gamma term type) [(g : gamma) ------------------------ T-Unit (has-type g unitv Unit)] [(g : gamma) (x : var) (t : type) (== (maybe type) (lookup-gamma g x) (some type t)) ------------------------ T-Var (has-type g (tvar x) t)] [(g : gamma) (e1 : term) (e2 : term) (t1 : type) (t2 : type) (has-type g e1 t1) (has-type g e2 t2) ---------------------- T-Pair (has-type g (pair e1 e2) (Pair t1 t2))] [(g : gamma) (e : term) (t1 : type) (t2 : type) (has-type g e (Pair t1 t2)) ----------------------- T-Prj1 (has-type g (prj z e) t1)] [(g : gamma) (e : term) (t1 : type) (t2 : type) (has-type g e (Pair t1 t2)) ----------------------- T-Prj2 (has-type g (prj (s z) e) t1)] [(g : gamma) (e1 : term) (t1 : type) (t2 : type) (x : var) (has-type (extend-gamma g x t1) e1 t2) ---------------------- T-Fun (has-type g (lam x t1 e1) (Fun t1 t2))] [(g : gamma) (e1 : term) (e2 : term) (t1 : type) (t2 : type) (has-type g e1 (Fun t1 t2)) (has-type g e2 t1) ---------------------- T-App (has-type g (app e1 e2) t2)])