namespace Mlang abbrev Name := String inductive Effect where | error : Effect deriving Repr, Inhabited, BEq abbrev Effects := List Effect def Effects.contains (effects : Effects) (effect : Effect) : Bool := effects.any (· == effect) def Effects.insert (effects : Effects) (effect : Effect) : Effects := if effects.contains effect then effects else effect :: effects def Effects.union (lhs rhs : Effects) : Effects := rhs.foldl Effects.insert lhs def Effects.erase (effects : Effects) (target : Effect) : Effects := effects.filter (· != target) def Effects.render : Effects → String | [] => "{}" | effects => let names := effects.reverse.map (fun | .error => "Error") "{" ++ String.intercalate ", " names ++ "}" inductive Ty where | int : Ty | bool : Ty | funTy : Ty → Effects → Ty → Ty deriving Repr, Inhabited, BEq inductive Expr where | int : Int → Expr | bool : Bool → Expr | var : Name → Expr | add : Expr → Expr → Expr | sub : Expr → Expr → Expr | mul : Expr → Expr → Expr | div : Expr → Expr → Expr | eq : Expr → Expr → Expr | letE : Name → Expr → Expr → Expr | ifE : Expr → Expr → Expr → Expr | tryE : Expr → Expr → Expr | lam : Name → Ty → Expr → Expr | app : Expr → Expr → Expr deriving Repr, Inhabited inductive Value where | int : Int → Value | bool : Bool → Value | closure : Name → Expr → List (Name × Value) → Value deriving Repr, Inhabited abbrev Env := List (Name × Value) inductive RuntimeError where | unboundVariable : Name → RuntimeError | typeError : String → RuntimeError | divisionByZero : RuntimeError deriving Repr, Inhabited abbrev EvalM := Except RuntimeError def Env.lookup (env : Env) (name : Name) : Option Value := match env with | [] => none | (n, v) :: rest => if n = name then some v else rest.lookup name def expectInt : Value → EvalM Int | .int n => .ok n | _ => .error (.typeError "expected an integer") def expectBool : Value → EvalM Bool | .bool b => .ok b | _ => .error (.typeError "expected a boolean") partial def eval (env : Env) : Expr → EvalM Value | .int n => .ok (.int n) | .bool b => .ok (.bool b) | .var name => match env.lookup name with | some v => .ok v | none => .error (.unboundVariable name) | .add lhs rhs => do let l ← expectInt (← eval env lhs) let r ← expectInt (← eval env rhs) pure (.int (l + r)) | .sub lhs rhs => do let l ← expectInt (← eval env lhs) let r ← expectInt (← eval env rhs) pure (.int (l - r)) | .mul lhs rhs => do let l ← expectInt (← eval env lhs) let r ← expectInt (← eval env rhs) pure (.int (l * r)) | .div lhs rhs => do let l ← expectInt (← eval env lhs) let r ← expectInt (← eval env rhs) if r = 0 then .error .divisionByZero else pure (.int (l / r)) | .eq lhs rhs => do let l ← expectInt (← eval env lhs) let r ← expectInt (← eval env rhs) pure (.bool (l = r)) | .letE name value body => do let value' ← eval env value eval ((name, value') :: env) body | .ifE cond thenBranch elseBranch => do let c ← expectBool (← eval env cond) if c then eval env thenBranch else eval env elseBranch | .tryE body handler => match eval env body with | .ok value => .ok value | .error _ => eval env handler | .lam param _ body => pure (.closure param body env) | .app fn arg => do let fnVal ← eval env fn let argVal ← eval env arg match fnVal with | .closure param body closureEnv => eval ((param, argVal) :: closureEnv) body | _ => .error (.typeError "expected a function") def renderValue : Value → String | .int n => toString n | .bool b => toString b | .closure _ _ _ => "" abbrev TyEnv := List (Name × Ty) structure Judgment where ty : Ty effects : Effects deriving Repr, Inhabited inductive TypeError where | unboundVariable : Name → TypeError | mismatch : Ty → Ty → TypeError | expectedFunction : Ty → TypeError deriving Repr, Inhabited abbrev CheckM := Except TypeError def TyEnv.lookup (env : TyEnv) (name : Name) : Option Ty := match env with | [] => none | (n, ty) :: rest => if n = name then some ty else rest.lookup name def ensureType (expected actual : Ty) : CheckM Unit := if expected == actual then pure () else throw (.mismatch expected actual) def pureJudgment (ty : Ty) : Judgment := { ty := ty, effects := [] } partial def inferType (env : TyEnv) : Expr → CheckM Judgment | .int _ => pure (pureJudgment .int) | .bool _ => pure (pureJudgment .bool) | .var name => match env.lookup name with | some ty => pure (pureJudgment ty) | none => throw (.unboundVariable name) | .add lhs rhs | .sub lhs rhs | .mul lhs rhs => do let lhsJ ← inferType env lhs let rhsJ ← inferType env rhs ensureType .int lhsJ.ty ensureType .int rhsJ.ty pure { ty := .int, effects := lhsJ.effects.union rhsJ.effects } | .div lhs rhs => do let lhsJ ← inferType env lhs let rhsJ ← inferType env rhs ensureType .int lhsJ.ty ensureType .int rhsJ.ty pure { ty := .int effects := (lhsJ.effects.union rhsJ.effects).insert .error } | .eq lhs rhs => do let lhsJ ← inferType env lhs let rhsJ ← inferType env rhs ensureType .int lhsJ.ty ensureType .int rhsJ.ty pure { ty := .bool, effects := lhsJ.effects.union rhsJ.effects } | .letE name value body => do let valueJ ← inferType env value let bodyJ ← inferType ((name, valueJ.ty) :: env) body pure { ty := bodyJ.ty effects := valueJ.effects.union bodyJ.effects } | .ifE cond thenBranch elseBranch => do let condJ ← inferType env cond ensureType .bool condJ.ty let thenJ ← inferType env thenBranch let elseJ ← inferType env elseBranch ensureType thenJ.ty elseJ.ty pure { ty := thenJ.ty effects := condJ.effects.union (thenJ.effects.union elseJ.effects) } | .tryE body handler => do let bodyJ ← inferType env body let handlerJ ← inferType env handler ensureType bodyJ.ty handlerJ.ty pure { ty := bodyJ.ty effects := (bodyJ.effects.erase .error).union handlerJ.effects } | .lam param paramTy body => do let bodyJ ← inferType ((param, paramTy) :: env) body pure (pureJudgment (.funTy paramTy bodyJ.effects bodyJ.ty)) | .app fn arg => do let fnJ ← inferType env fn let argJ ← inferType env arg match fnJ.ty with | .funTy paramTy latentEffects resultTy => ensureType paramTy argJ.ty pure { ty := resultTy effects := fnJ.effects.union (argJ.effects.union latentEffects) } | _ => throw (.expectedFunction fnJ.ty) def renderType : Ty → String | .int => "Int" | .bool => "Bool" | .funTy lhs effects rhs => s!"({renderType lhs} -> {renderType rhs} ! {effects.render})" def renderJudgment (judgment : Judgment) : String := s!"{renderType judgment.ty} ! {judgment.effects.render}" def sampleProgram : Expr := .letE "inc" (.lam "x" .int (.add (.var "x") (.int 1))) (.app (.var "inc") (.int 41)) def lexicalScopeProgram : Expr := .letE "x" (.int 10) (.letE "f" (.lam "y" .int (.add (.var "x") (.var "y"))) (.letE "x" (.int 100) (.app (.var "f") (.int 5)))) def conditionalProgram : Expr := .ifE (.eq (.mul (.int 6) (.int 7)) (.int 42)) (.int 1) (.int 0) end Mlang