dtlc

dependently-typed lambda calculus toy
git clone git://git.rr3.xyz/dtlc
Log | Files | Refs | README | LICENSE

commit 55f4454813f79263221e199532d0865c602ba94e
parent ef23c70fab3f52ab642184b3dd7fe3c26b9c36e7
Author: Robert Russell <robert@rr3.xyz>
Date:   Mon, 26 Aug 2024 15:21:07 -0700

Clean up, add examples, and add README

Diffstat:
AREADME | 9+++++++++
Ddep/dep.ml | 216-------------------------------------------------------------------------------
Rexamples/church -> examples/church.dtlc | 0
Dexamples/list | 10----------
Aexamples/list.dtlc | 26++++++++++++++++++++++++++
Aexamples/nat.dtlc | 2++
Dlam/lam.ml | 59-----------------------------------------------------------
Dporig.ml | 66------------------------------------------------------------------
Drepr.ml | 32--------------------------------
9 files changed, 37 insertions(+), 383 deletions(-)

diff --git a/README b/README @@ -0,0 +1,9 @@ +Dependently-typed lambda calculus with Type-in-Type, general (mutual) +recursive let bindings, (non-recursive) data types and pattern matching, and +a built-in Nat type with some arthimetic and comparison operations + +This project is somewhat incomplete: implicit arguments are only half +implemented, dependent pattern matching is broken, pattern matching +on built-in Nats is not implemented. + +See the examples in the examples directory. diff --git a/dep/dep.ml b/dep/dep.ml @@ -1,216 +0,0 @@ -type ident = string - -type index = int -type level = int - -type surf = -| SVar of ident (* x *) -| SAnn of surf * surf (* t : u *) -| SLet of patt * surf * surf (* let p = t in u *) -| SType (* Type *) -| SPi of ident * surf * surf (* (x : t) -> u *) -| SLam of patt * surf (* λp. t *) -| SApp of surf * surf (* t u *) -| SNat (* Nat *) -| SLit of int (* 0, 1, ... *) -| SAdd of surf * surf (* t + u *) -and patt = -| PVar of ident (* x *) -| PAnn of patt * surf (* p : t *) - -type core = -| CVar of index -| CLet of core * core -| CType -| CPi of core * core -| CLam of core -| CApp of core * core -| CNat -| CLit of int -| CAdd of core * core - -type value = -| VVar of level -| VType -| VPi of value * closure -| VLam of closure -| VApp of value * value -| VNat -| VLit of int -| VAdd of value * value -and closure = environ * core -and environ = value list - -let rec eval env = function -| CVar i -> List.nth env i -| CLet (a, b) -> eval (eval env a :: env) b -| CType -> VType -| CPi (dom, cod) -> VPi (eval env dom, (env, cod)) -| CLam body -> VLam (env, body) -| CApp (f, a) -> - begin match eval env f, eval env a with - | VLam clo, va -> apply clo va - | vf, va -> VApp (vf, va) - end -| CNat -> VNat -| CLit n -> VLit n -| CAdd (a, b) -> - begin match eval env a, eval env b with - | VLit m, VLit n -> VLit (m + n) - | va, vb -> VAdd (va, vb) - end - -and apply (env, c) v = eval (v :: env) c - -let rec quote l = function -| VVar i -> CVar (l - i - 1) -| VType -> CType -| VPi (dom, cod) -> CPi (quote l dom, quote (l + 1) (apply cod (VVar l))) -| VLam clo -> CLam (quote (l + 1) (apply clo (VVar l))) -| VApp (f, a) -> CApp (quote l f, quote l a) -| VNat -> CNat -| VLit n -> CLit n -| VAdd (a, b) -> CAdd (quote l a, quote l b) - -let rec conv l a b = match a, b with -| VVar x, VVar y -> x = y -| VType, VType -> true -| VPi (dom0, cod0), VPi (dom1, cod1) -> conv l dom0 dom1 - && conv (l + 1) (apply cod0 (VVar l)) (apply cod1 (VVar l)) -(* no β nor η for lams? *) -| VApp (a0, a1), VApp (b0, b1) -> conv l a0 b0 && conv l a1 b1 -| VNat, VNat -> true -| VLit m, VLit n -> m = n -| VAdd (a0, a1), VAdd (b0, b1) -> conv l a0 b0 && conv l a1 b1 -| _, _ -> false - -type context = {env: environ; free: (ident * value) list} - -let let_bind ctx x t v = { - env = v :: ctx.env; - free = (x, t) :: ctx.free; -} - -let var_bind ctx x t = { - env = VVar (List.length ctx.env) :: ctx.env; - free = (x, t) :: ctx.free; -} - -let rec check (ctx: context) s t = match s, t with -| SLet (p, s0, s1), t -> - let (c0, t0, x) = infer_with_patt ctx s0 p in - let v0 = eval ctx.env c0 in - let c1 = check (let_bind ctx x t0 v0) s1 t in - CLet (c0, c1) -| SLam (p, s), VPi (dom, cod) -> - let x = check_patt ctx p dom in - let c = check (var_bind ctx x dom) s (apply cod (VVar (List.length ctx.env))) in - CLam c -| s, t -> - let (c, u) = infer ctx s in - if conv (List.length ctx.env) t u then c else failwith "type error" - -and infer (ctx: context) = function -| SVar x -> - let rec go i = function - | [] -> failwith ("variable '" ^ x ^ "' out of scope") - | (y, v) :: rest -> if x = y then (CVar i, v) else go (i + 1) rest in - go 0 ctx.free -| SAnn (s0, s1) -> - let c1 = check ctx s1 VType in - let v1 = eval ctx.env c1 in - let c0 = check ctx s0 v1 in - (c0, v1) -| SLet (p, s0, s1) -> - let (c0, t0, x) = infer_with_patt ctx s0 p in - let v0 = eval ctx.env c0 in - let (c1, t1) = infer (let_bind ctx x t0 v0) s1 in - (CLet (c0, c1), t1) -| SType -> (CType, VType) -| SPi (x, s0, s1) -> - let c0 = check ctx s0 VType in - let t0 = eval ctx.env c0 in - let c1 = check (var_bind ctx x t0) s1 VType in - (CPi (c0, c1), VType) -| SLam (p, s) -> - let (x, t) = infer_patt ctx p in - let (c, u) = infer (var_bind ctx x t) s in - (CLam c, VPi (t, (ctx.env, quote (List.length ctx.env) u))) -| SApp (s0, s1) -> - let (c0, t0) = infer ctx s0 in - begin match t0 with - | VPi (dom, cod) -> - let c1 = check ctx s1 dom in - (CApp (c0, c1), apply cod (eval ctx.env c1)) - | _ -> failwith "expected function type in application" - end -| SNat -> (CNat, VType) -| SLit n -> (CLit n, VNat) -| SAdd (s0, s1) -> - let c0 = check ctx s0 VNat in - let c1 = check ctx s1 VNat in - (CAdd (c0, c1), VNat) - -and infer_with_patt ctx s0 = function -| PVar x -> let (c0, t0) = infer ctx s0 in (c0, t0, x) -| PAnn (q, s1) -> - let c1 = check ctx s1 VType in - let v1 = eval ctx.env c1 in - let x = check_patt ctx q v1 in - let c0 = check ctx s0 v1 in - (c0, v1, x) - -and check_patt ctx p t = match p with -| PVar x -> x -| PAnn (q, s) -> - let c = check ctx s VType in - let v = eval ctx.env c in - if conv (List.length ctx.env) t v then - check_patt ctx q t - else - failwith "patt_check fail" - -and infer_patt ctx = function -| PVar _ -> failwith "can not infer type for variable pattern" -| PAnn (q, s) -> - let c = check ctx s VType in - let v = eval ctx.env c in - let x = check_patt ctx q v in - (x, v) - -let rec string_of_core c = - let go = function - | CVar i -> "$" ^ string_of_int i - | CLet (a, b) -> "let" ^ string_of_core a ^ string_of_core b - | CType -> "Type" - | CPi (a, b) -> "Π" ^ string_of_core a ^ string_of_core b - | CLam a -> "λ" ^ string_of_core a - | CApp (a, b) -> string_of_core a ^ string_of_core b - | CNat -> "Nat" - | CLit n -> string_of_int n - | CAdd (a, b) -> string_of_core a ^ "+" ^ string_of_core b in - "(" ^ go c ^ ")" - - - -let s = SLet ( - PVar "f", - SLam ( - PAnn ( - PVar "n", - SApp ( - SLam ( - PAnn (PVar "α", SType), - SNat - ), - SType - ) - ), - SAdd (SLit 3, SVar "n") - ), - SApp (SVar "f", SLit 4) -) -let (c, t) = infer {env = []; free = []} s -let () = print_endline (string_of_core c) -let () = print_endline (string_of_core (quote 0 t)) -let () = print_endline (string_of_core (quote 0 (eval [] c))) diff --git a/examples/church b/examples/church.dtlc diff --git a/examples/list b/examples/list @@ -1,10 +0,0 @@ -UnaryNat: Type := rec #{ - @Z; - @S UnaryNat; -}; -@S (@S @Z): UnaryNat -\List: Type -> Type := rec fn A => #{ -\ @nil; -\ @cons A, List A; -\}; -\@cons 1 @nil : List Nat diff --git a/examples/list.dtlc b/examples/list.dtlc @@ -0,0 +1,26 @@ +\ Church-encoded unary natural numbers (pattern matching on built-in Nats is +\ not implemented) +UNat: Type := [M: Type, M, M -> M] -> M; +zero: UNat := fn M, z, s => z; +succ: UNat -> UNat := fn n => fn M, z, s => s (n M z s); + +\ Conversion from UNat to built-in Nat type for pretty printing +toNat: UNat -> Nat := fn n => n Nat 0 (fn m => m + 1); + +\ Polymorphic Church-encoded lists +List: Type -> Type := fn A => + [M: Type, M, [A, M] -> M] -> M; +nil: [A: Type] -> List A := fn A => + fn M, n, c => n; +cons: [A: Type, A, List A] -> List A := rec fn A, hd, tl => + fn M, n, c => c hd (tl M n c); + +\ Example operations on lists +length: [A: Type, List A] -> UNat := fn A, l => + l UNat zero (fn _, n => succ n); +map: [A: Type, B: Type, A -> B, List A] -> List B := fn A, B, f, l => + l (List B) (nil B) (fn a, tl' => cons B (f a) tl'); + +l0: List UNat := cons UNat zero (cons UNat zero (cons UNat zero (nil UNat))); +l1: List UNat := map UNat UNat succ l0; +(map UNat Nat toNat l0, map UNat Nat toNat l1) diff --git a/examples/nat.dtlc b/examples/nat.dtlc @@ -0,0 +1 @@ +(5 + 3, 5 - 3, 5 * 3, 5 / 3, 5 % 3, 5 < 3, 5 = 3, 5 > 3) +\ No newline at end of file diff --git a/lam/lam.ml b/lam/lam.ml @@ -1,59 +0,0 @@ -type idx = int - -type term = - | TVar of idx - | TLam of term - | TApp of term * term - | TLet of term * term - | TInt of int - | TAdd of term * term - | TMul of term * term - -let rec string_of_term = function - | TVar i -> "$" ^ string_of_int i - | TLam t -> "(λ" ^ string_of_term t ^ ")" - | TApp (t, u) -> "(" ^ string_of_term t ^ ") (" ^ string_of_term u ^ ")" - | TLet (t, u) -> "(" ^ string_of_term t ^ "; " ^ string_of_term u ^ ")" - | TInt i -> string_of_int i - | TAdd (t, u) -> "(" ^ string_of_term t ^ " + " ^ string_of_term u ^ ")" - | TMul (t, u) -> "(" ^ string_of_term t ^ " * " ^ string_of_term u ^ ")" - -type value = - | VLam of env * term - | VInt of int -and env = value list - -let rec string_of_value = function - | VLam (e, t) -> String.concat ", " (List.map string_of_value e) ^ " ⊢ " ^ string_of_term t - | VInt i -> string_of_int i - -let rec lookup e i = - match e, i with - | v :: _, 0 -> v - | _ :: vs, j -> lookup vs j - | [], _ -> failwith "variable not in scope" - -let rec eval e = function - | TVar i -> lookup e i - | TLam t -> VLam (e, t) - | TApp (t, u) -> ( - match eval e t, eval e u with - | VLam (c, b), v -> eval (v :: c) b - | _, _ -> failwith "can only apply functions" - ) - | TLet (t, u) -> eval (eval e t :: e) u - | TInt i -> VInt i - | TAdd (t, u) -> ( - match eval e t, eval e u with - | VInt i, VInt j -> VInt (i + j) - | _, _ -> failwith "can only add ints" - ) - | TMul (t, u) -> ( - match eval e t, eval e u with - | VInt i, VInt j -> VInt (i * j) - | _, _ -> failwith "can only multiply ints" - ) - -let t = TLet (TLam (TAdd (TInt 3, TVar 0)), TApp (TVar 0, TInt 4)) -let () = print_endline (string_of_term t) -let () = print_endline (string_of_value (eval [] t)) diff --git a/porig.ml b/porig.ml @@ -1,66 +0,0 @@ -(* TODO: Generalize to partial add and mul? *) -module type Porig = sig - type t - val zero : t - val one : t - val default : t - val add : t -> t -> t - val mul : t -> t -> t - val leq : t -> t -> bool - val lub : t -> t -> t option - val to_string : t -> string -end - -(* TODO: How does compilation work with this porig? What gets erased? *) -module Intuitionistic: Porig = struct - type t = Zero - - let zero = Zero - let one = Zero - let default = Zero - - let add Zero Zero = Zero - - let mul Zero Zero = Zero - - let leq Zero Zero = true - - let lub Zero Zero = Zero - - let to_string Zero = "0" -end - -(* TODO: module DiscreteBoolean: Porig = struct ... end*) -(* TODO: module TotalBoolean: Porig = struct ... end*) - -module NoneOneTons: Porig = struct - type t = Zero | One | Omega - - let zero = Zero - let one = One - let default = Omega - - let add a b = match a, b with - | Zero, b -> b - | a, Zero -> a - | _, _ -> Omega - - let mul a b = match a, b with - | Zero, _ -> Zero - | _, Zero -> Zero - | One, b -> b - | _, _ -> Omega - - let leq a b = a = b || - match a, b with - | Zero, Omega -> true - | One, Omega -> true - | _, _ -> false - - let lub a b = if a = b then a else Omega - - let to_string = function - | Zero -> "0" - | One -> "1" - | Omega -> "ω" -end diff --git a/repr.ml b/repr.ml @@ -1,32 +0,0 @@ -(* TODO: functions/closures *) -(* TODO: packing control *) - -type repr = -| Constant of constant -| Uniform of uniform -| NonUniform of nonuniform -and constant = -| CAlign of constant * term1 (* The term has type Nat1. *) -| CPtr -| CBits of term1 (* The term has type Nat1. *) -| CData of constant array -| CTuple of constant array -| CArray of constant * term1 (* The term has type Nat1. *) -and uniform = -| UConstant of constant -| UAlign of uniform * term1 (* The term has type Nat1. *) -| UData of uniform array -| UTuple of (uniform * term1) array * uniform -| UArray of uniform * term1 (* The term has type ↑Nat0. *) -and nonuniform = -| NUniform of uniform -| NTuple of (uniform * term1) array * nonuniform - (* The array is of (repr, lifted type) pairs, and each repr is formed in a - * context with variables of the preceding lifted types bound. E.g., if - * type T has uniform repr R and type Nat0 has repr CBits 64, then - * #(n: Nat0, a: Arr T n) has repr - * NTuple ([|(CBits 64, ↑Nat0)|], n: ↑Nat0 ⊢ UArray (R, n)). *) - -(* This isn't quite right, because we also need to allow for representation - * variables. The above types need to be embedded in the term1 syntax, just - * like how we embed types in terms. *)