dtlc

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

commit 5377553a6de6ea89292b8f5ea8199d249ee25e9d
parent 0f8ed905de3ec4f365268a8b523933f55945185c
Author: Robert Russell <robertrussell.72001@gmail.com>
Date:   Mon,  8 Jan 2024 23:09:47 -0800

Get (mutual) recursion working

Diffstat:
Mctx.ml | 71+++++++----------------------------------------------------------------
Mdesugar.ml | 2+-
Melab.ml | 85+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Menv.ml | 3---
Meval.ml | 36+++++++++++++++++++-----------------
Mmain.ml | 18+++++++++++-------
Mvalue.ml | 2+-
7 files changed, 94 insertions(+), 123 deletions(-)

diff --git a/ctx.ml b/ctx.ml @@ -11,18 +11,14 @@ type 'a assignment = { type 'a context = { assignments: 'a assignment tsil Name.Map.t; (* XXX: tsil! Yuck! *) - bindings: Index.UniqTsil.t; - definitions: 'a Uniq.Map.t; + env: 'a Env.environment; } let empty = { assignments = Name.Map.empty; - bindings = Index.UniqTsil.lin; - definitions = Uniq.Map.empty; + env = Env.empty; } -let to_env {bindings; definitions; _} = {Env.bindings; Env.definitions} - let assign ctx names typ value = let asn = {typ; value; active = true} in let shadow ctx name = @@ -33,11 +29,9 @@ let assign ctx names typ value = {ctx with assignments} in (Array.fold_left shadow ctx names, asn) -let bind ctx vars = - {ctx with bindings = Index.UniqTsil.snoc ctx.bindings vars} +let bind ctx vars = {ctx with env = Env.bind ctx.env vars} -let define ctx var value = - {ctx with definitions = Uniq.Map.add var value ctx.definitions} +let define ctx var value = {ctx with env = Env.define ctx.env var value} let lookup ctx name i = match Name.Map.find_opt name ctx.assignments with @@ -50,59 +44,8 @@ let lookup ctx name i = go i t | None -> None -let index ctx ij = Index.UniqTsil.var_of ctx.bindings ij - -let quote ctx var = Index.UniqTsil.index_of ctx.bindings var - -let eval ctx var = Uniq.Map.find_opt var ctx.definitions - - - - - - - -(* -type 'a assignee = { - names: Name.t array; - typ: 'a; -} -*) - -(* -let extend_and_bind_one ctx preb = - let (ctx, vars) = extend_fresh ctx 1 in - let (ctx, postb) = bind_one ctx preb vars.(0) in - (ctx, postb) - -let extend_and_bind_all ctx prebs = - let n = Array.length prebs in - let postbs = Array.make n garbage in - let (ctx, vars) = extend_fresh ctx n in - let ctx = ref ctx in - for i = 0 to n - 1 do - let (ctx', postb) = bind_one !ctx prebs.(i) vars.(i) in - ctx := ctx'; - postbs.(i) <- postb - done; - (!ctx, postbs) -*) +let index ctx ij = Env.index ctx.env ij -(* -let extend_fresh ctx n = - let vars = Uniq.fresh_array n in - (extend ctx vars, vars) -*) +let quote ctx var = Env.quote ctx.env var -(* -let bind_one ctx {names; vtyp} var = - let postb = {var; vtyp; active = true} in - let ctx = ref ctx in - for i = 0 to Array.length names - 1 do - let shadow = function - | None -> Some (Snoc (Lin, postb)) - | Some l -> Some (Snoc (l, postb)) in - ctx := {!ctx with names = Name.Map.update names.(i) shadow !ctx.names} - done; - (!ctx, postb) -*) +let eval ctx var = Env.eval ctx.env var diff --git a/desugar.ml b/desugar.ml @@ -64,7 +64,7 @@ and term' loc = function and definer d = Node.map definer' d and definer' = function | C.Definer (lhs, rhs) -> {recursive = false; lhs = patt lhs; rhs = term rhs} -| C.RecDefiner (lhs, rhs) -> {recursive = false; lhs = patt lhs; rhs = term rhs} +| C.RecDefiner (lhs, rhs) -> {recursive = true; lhs = patt lhs; rhs = term rhs} and case c = Node.map case' c and case' (C.Case (p, t)) = A.Case (patt p, term t) diff --git a/elab.ml b/elab.ml @@ -25,6 +25,26 @@ and error' = | ErrFnPlicityMismatch | ErrUnexpectedCtor of Tag.t +let rec string_of_error {loc; data = e} = + Loc.to_string loc ^ " " ^ string_of_error' e +and string_of_error' = function +| ErrNonInferablePatt _ -> "ErrNonInferablePatt" +| ErrCantInferData -> "ErrCantInferData" +| ErrNoLetAnnot _ -> "ErrNoLetAnnot" +| ErrNoBinderAnnot _ -> "ErrNoBinderAnnot" +| ErrUnannotImplicitIsType _ -> "ErrUnannotImplicitIsType" +| ErrApplyNonFunction _ -> "ErrApplyNonFunction" +| ErrUnifyFail _ -> "ErrUnifyFail" +| ErrDataPattRefutable -> "ErrDataPattRefutable" +| ErrBinderCycle _ -> "ErrBinderCycle" +| ErrArityMismatch -> "ErrArityMismatch" +| ErrUnboundVar _ -> "ErrUnboundVar" +| ErrExpectedFnType _ -> "ErrExpectedFnType" +| ErrExpectedDataType _ -> "ErrExpectedDataType" +| ErrFnArityMismatch -> "ErrFnArityMismatch" +| ErrFnPlicityMismatch -> "ErrFnPlicityMismatch" +| ErrUnexpectedCtor _ -> "ErrUnexpectedCtor" + exception Exn of error let abort e = raise (Exn e) @@ -39,7 +59,7 @@ exception BlockedOnFuture of Uniq.t * int * Name.t let rec infer ctx t = try Ok (infer' ctx t) with | Exn e -> Error e -and infer' ctx {loc; data = t} = match t with +and infer' (ctx: 'a Ctx.context) {loc; data = t} = match t with | A.Let (definers, body) -> let (ctx, definers) = infer_definers' ctx definers in let (body, vtyp) = infer' ctx body in @@ -49,13 +69,13 @@ and infer' ctx {loc; data = t} = match t with | A.Annot (t, a) -> let a = check' ctx a V.Type in - let va = eval (Ctx.to_env ctx) a in + let va = eval ctx.env a in let t = check' ctx t va in (t, va) | A.App (fn, explicit_args) -> let (fn, va) = infer' ctx fn in - begin match force (Ctx.to_env ctx) va with + begin match force ctx.env va with | V.FnTypeClosure (clo, {binders; codomain}) -> let (args, clo) = check_args' ctx loc explicit_args clo binders in let vcodomain = eval clo codomain in @@ -66,9 +86,9 @@ and infer' ctx {loc; data = t} = match t with | A.Fn (params, body) -> let (ctx', binders) = infer_binders' ctx loc params in let (body, vcodomain) = infer' ctx' body in - let codomain = quote (Ctx.to_env ctx') vcodomain in + let codomain = quote ctx'.env vcodomain in let fn = I.Fn {binders; body; codomain} in - let vfntype = V.FnTypeClosure (Ctx.to_env ctx, {binders; codomain}) in + let vfntype = V.FnTypeClosure (ctx.env, {binders; codomain}) in (fn, vfntype) | A.FnType (domains, codomain) -> @@ -87,7 +107,7 @@ and infer' ctx {loc; data = t} = match t with | A.Var (name, i) -> begin match Ctx.lookup ctx name i with | Some {typ = V.Future (id, j); _} -> raise (BlockedOnFuture (id, j, name)) - | Some {typ; value; _} -> (quote (Ctx.to_env ctx) value, typ) + | Some {typ; value; _} -> (quote ctx.env value, typ) | None -> abort (loc @$ ErrUnboundVar (name, i)) end @@ -96,7 +116,7 @@ and infer' ctx {loc; data = t} = match t with and check ctx t vtyp = try Ok (check' ctx t vtyp) with | Exn e -> Error e -and check' ctx ({loc; data = t} as node) vtyp = match t, force (Ctx.to_env ctx) vtyp with +and check' ctx ({loc; data = t} as node) vtyp = match t, force ctx.env vtyp with | A.Let (definers, body), vtyp -> let (ctx, definers) = infer_definers' ctx definers in let body = check' ctx body vtyp in @@ -133,7 +153,7 @@ and check' ctx ({loc; data = t} as node) vtyp = match t, force (Ctx.to_env ctx) let plicity = param.plicity in let vtyp = eval clo domain.typ in - let typ = quote (Ctx.to_env !ctx) vtyp in + let typ = quote !ctx.env vtyp in let names = check_irrefutable_patt' !ctx param.patt vtyp in (* Assign the names in ctx. The other params may refer to them. *) @@ -143,7 +163,7 @@ and check' ctx ({loc; data = t} as node) vtyp = match t, force (Ctx.to_env ctx) done; let vcodomain = eval clo codomain in - let codomain = quote (Ctx.to_env !ctx) vcodomain in + let codomain = quote !ctx.env vcodomain in let body = check' !ctx body vcodomain in I.Fn {binders = {binders = params'; order}; body; codomain} @@ -163,7 +183,7 @@ and check' ctx ({loc; data = t} as node) vtyp = match t, force (Ctx.to_env ctx) | (A.Annot _ | A.App _ | A.FnType _ | A.DataType _ | A.Nat _ | A.Var _ | A.Type), vexpected -> let (t, vinferred) = infer' ctx node in - if conv (Ctx.to_env ctx) vexpected vinferred then + if conv ctx.env vexpected vinferred then t else abort (loc @$ ErrUnifyFail (vexpected, vinferred)) @@ -207,7 +227,7 @@ and check_args' ctx loc explicit_args clo {binders; order} = let (arg, varg) = match args.(j) with | Some arg -> let arg = check' ctx arg vtyp in - let varg = eval (Ctx.to_env ctx) arg in + let varg = eval ctx.env arg in (arg, varg) | None -> no_impl "implict args" in args'.(j) <- arg; @@ -248,9 +268,11 @@ and infer_definers' ctx definers = let names = check_irrefutable_patt' ctx lhs vtyp in (names, rhs, vtyp) | Error e -> abort e in - let typ = quote (Ctx.to_env ctx) vtyp in + let typ = quote ctx.env vtyp in + (* XXX: vrhs doesn't need to be a Rec. We could eagerly evaluate it, or + * add some other mechanism for laziness (like glued eval). *) + let vrhs = V.Rec {env = ref ctx.env; rhs} in let (ctx, _) = Ctx.assign ctx names vtyp (V.Arb vars.(0)) in - let vrhs = lazy_eval (Ctx.to_env ctx) rhs in let ctx = Ctx.define ctx vars.(0) vrhs in (ctx, [|{rhs; typ}|]) @@ -261,7 +283,7 @@ and infer_definers' ctx definers = * to avoid meta-variables/unification, because they're unpredictable; * inference should only rely on local information. *) | definers -> - (* Infer each definer (in the original context, because definitions are + (* Infer each lhs (in the original context, because definitions are * not allowed to have mutually dependent types), and make all * assignments. We later set the active flag to false on assignments * for non-recusive definers, so that the rhs of non-recursive definers @@ -277,16 +299,18 @@ and infer_definers' ctx definers = let ctx = !ctx_with_asns in (* Check each rhs with the appropriate assignments active, but not any - * definitions. This avoids any non-terminating mutual recursion during - * elaboration. It also means that each definition is "opaque" during - * the elaboration of each rhs, i.e., the let-bound variables behave - * like fn-bound variables. *) - let ctx_with_defs = ref ctx in + * definitions. This means that each definition is "opaque" during the + * elaboration of each rhs, i.e., the let-bound variables behave like + * fn-bound variables. It's hard to get around this. It might be useful + * to elaborate each rhs with every other rhs defined in context, but + * that would require every rhs to already be elaborated! *) + let env = ref ctx.env in let definers' = Array.make ndefiners garbage in for i = 0 to ndefiners - 1 do - let rhs = definers.(i).data.rhs in let vtyp = asns.(i).typ in + let typ = quote ctx.env vtyp in + let rhs = definers.(i).data.rhs in let rhs = if definers.(i).data.recursive then check' ctx rhs vtyp @@ -298,15 +322,16 @@ and infer_definers' ctx definers = asns.(i).active <- true; rhs end in + let vrhs = V.Rec {env; rhs} in - let vrhs = lazy_eval (Ctx.to_env ctx) rhs in - ctx_with_defs := Ctx.define !ctx_with_defs vars.(i) vrhs; - - let typ = quote (Ctx.to_env ctx) vtyp in + env := Env.define !env vars.(i) vrhs; definers'.(i) <- {I.rhs; I.typ} done; - (!ctx_with_defs, definers') + (* TODO: For this situation, it might be nice to abstract the surface + * name assignment map from contexts. Then we can say a context is an + * environment + a surface name assignment. *) + ({ctx with env = !env}, definers') and infer_lhs' ctx {loc; data = {lhs; _}} = match infer_irrefutable_patt ctx lhs with @@ -443,7 +468,7 @@ and infer_binders' ctx loc binders = for i = 0 to arity - 1 do match states.(i) with | Elaborated j -> - let typ = quote (Ctx.to_env ctx) asns.(i).typ in + let typ = quote ctx.env asns.(i).typ in binders'.(i) <- {I.plicity = binders.(i).data.plicity; I.typ}; order.(j) <- i | Pending _ -> @@ -455,7 +480,7 @@ and infer_binders' ctx loc binders = and infer_binder' ctx {loc; data = {plicity; patt; typ}} = match typ with | Some a -> let a = check' ctx a V.Type in - let va = eval (Ctx.to_env ctx) a in + let va = eval ctx.env a in let _ = check_irrefutable_patt' ctx patt va in va | None -> @@ -482,7 +507,7 @@ and infer_irrefutable_patt' ctx ({loc; data = patt} as node) = match patt with | A.PBind _ -> abort (loc @$ ErrNonInferablePatt node) | A.PAnnot (patt, typ) -> let typ = check' ctx typ V.Type in - let vtyp = eval (Ctx.to_env ctx) typ in + let vtyp = eval ctx.env typ in let names = check_irrefutable_patt' ctx patt vtyp in (names, vtyp) | A.PData _ -> abort (loc @$ ErrDataPattRefutable) @@ -495,8 +520,8 @@ and check_irrefutable_patt' ctx {loc; data = patt} vtyp = match patt with | A.PBind name -> [|name|] | A.PAnnot (patt, typ') -> let typ' = check' ctx typ' V.Type in - let vtyp' = eval (Ctx.to_env ctx) typ' in - if conv (Ctx.to_env ctx) vtyp vtyp' then + let vtyp' = eval ctx.env typ' in + if conv ctx.env vtyp vtyp' then check_irrefutable_patt' ctx patt vtyp else abort (loc @$ ErrUnifyFail (vtyp, vtyp')) diff --git a/env.ml b/env.ml @@ -1,8 +1,5 @@ open Global -(* TODO: Smarter use modules to avoid redundancy between Env and Ctx. Or just - * store an env on each ctx instead of duplicating fields. *) - type 'a environment = { bindings: Index.UniqTsil.t; definitions: 'a Uniq.Map.t; diff --git a/eval.ml b/eval.ml @@ -9,40 +9,42 @@ module V = Value let rec eval env = function | I.Let (definers, body) -> let ndefiners = Array.length definers in + let vars = Uniq.fresh ndefiners in let env = Env.bind env vars in - let env' = ref env in + + let env = ref env in for i = 0 to ndefiners - 1 do - (* In typical programs, most definitions will not appear inside a type - * and hence won't be tested for conversion. Thus, it's important that - * we only lazily evaluate each rhs. *) - let vrhs = lazy_eval env definers.(i).rhs in - env' := Env.define !env' vars.(i) vrhs + let vrhs = V.Rec {env; rhs = definers.(i).rhs} in + env := Env.define !env vars.(i) vrhs done; - eval !env' body + eval !env body + | I.App (fn, args) -> apply (eval env fn) (Array.map (eval env) args) + | I.Fn fn -> V.FnClosure (env, fn) + | I.FnType fntype -> V.FnTypeClosure (env, fntype) + | I.Data (tag, args) -> V.Data (tag, Array.map (eval env) args) + | I.DataType datatype -> V.DataTypeClosure (env, datatype) + | I.Nat n -> V.Nat n + | I.Var ij -> let var = Env.index env ij in begin match Env.eval env var with | Some v -> v | None -> Arb var end + | I.Type -> V.Type -| I.Builtin b -> V.Builtin b -(* TODO: Optimization: Change `Lazy of value lazy` to `Lazy of env * term`, - * so that when later forcing/reevaluating a lazy value, we can just change the - * "values" field of the environment (but not the "indices" field, because that - * is sensitive to term structure) and call eval. *) -and lazy_eval env t = V.Lazy (lazy (eval env t)) +| I.Builtin b -> V.Builtin b and apply fn args = match fn, args with -| V.Lazy fn, args -> V.Lazy (lazy (apply (Lazy.force fn) args)) +| V.Rec {env; rhs}, args -> apply (eval !env rhs) args | V.FnClosure (clo, {binders = {binders; _}; body; _}), args -> let arity = Array.length binders in let vars = Uniq.fresh arity in @@ -57,16 +59,17 @@ and apply fn args = match fn, args with * stuck variables to the outside of neutral terms so we can force them without * needing to recurse. *) and force env = function +| V.Rec {env = env'; rhs} -> force env (eval !env' rhs) | V.App (fn, args) -> apply (force env fn) (Array.map (force env) args) | V.Arb var -> begin match Env.eval env var with | Some v -> force env v | None -> Arb var end -| V.Lazy l -> force env (Lazy.force l) | v -> v let rec quote env = function +| V.Rec {env = env'; rhs} -> quote env (eval !env' rhs) | V.App (fn, args) -> I.App (quote env fn, Array.map (quote env) args) | V.FnClosure (clo, {binders; body; codomain}) -> let (env, clo, binders) = quote_binders env clo binders in @@ -87,11 +90,10 @@ let rec quote env = function | V.Arb var -> begin match Env.quote env var with | Some ij -> I.Var ij - | None -> impossible () (* XXX: Is it? *) + | None -> impossible () end | V.Type -> I.Type | V.Builtin b -> I.Builtin b -| V.Lazy l -> quote env (Lazy.force l) | V.Future _ -> impossible () and quote_binders env clo {binders; order} = diff --git a/main.ml b/main.ml @@ -11,8 +11,16 @@ let buf = Lexing.from_string "\ five Nat 0 (fn n => n + 1)\n\ " *) + +(* +let buf = Lexing.from_string "T := #{@a Nat; @b #()}; @a 3 : T" +*) + let buf = Lexing.from_string "\ -T := #{@a Nat; @b #()}; @a 3 : T" + fact: Nat -> Nat := rec fn n =>\n\ + if eq n 0 then 1 else n * fact (n + 1);\n\ + fact 3\n\ +" let concrete = try Parser.start Lexer.token buf with @@ -38,16 +46,12 @@ let ctx = let (ctx, _) = Ctx.assign ctx [|Name.Of "`*"|] nat_op_typ (V.Builtin I.NatOpMul) in ctx -(* let (internal, ty) = match Elab.infer ctx abstract with | Ok r -> r - | Error e -> print_endline "elab error"; exit 1 -*) -let (internal, ty) = Elab.infer' ctx abstract + | Error e -> print_endline (Elab.string_of_error e); exit 1 -let env = Ctx.to_env ctx -let internal' = Eval.quote env (Eval.eval env internal) +let internal' = Eval.quote ctx.env (Eval.eval ctx.env internal) let () = print_endline (I.to_string internal) let () = print_endline (I.to_string internal') diff --git a/value.ml b/value.ml @@ -2,6 +2,7 @@ open Global module I = Internal type value = +| Rec of {env: value Env.environment ref; rhs: I.term} (*| Switch of ...*) | App of value * value array | FnClosure of value Env.environment * I.fn @@ -13,7 +14,6 @@ type value = (*| Meta of Uniq.t*) | Type | Builtin of I.builtin -| Lazy of value Lazy.t (* TODO: See comment in eval.ml about optimization. *) | Future of Uniq.t * int (*