├── .gitignore ├── project └── build.properties ├── src ├── test │ └── scala │ │ ├── lambda │ │ ├── DeBruijnSpec.scala │ │ ├── TermSpec.scala │ │ ├── ParserSpec.scala │ │ └── EvalSpec.scala │ │ └── typing │ │ ├── ShowSpec.scala │ │ └── TypingSpec.scala └── main │ └── scala │ ├── typing │ ├── Environment.scala │ ├── Substitution.scala │ ├── Unification.scala │ ├── Typing.scala │ └── Show.scala │ ├── lambda │ ├── DeBruijn.scala │ ├── Term.scala │ ├── Eval.scala │ └── Parser.scala │ └── util │ └── HList.scala ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .bsp 2 | metals.sbt 3 | target 4 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /src/test/scala/lambda/DeBruijnSpec.scala: -------------------------------------------------------------------------------- 1 | package lambda 2 | 3 | object DeBruijnSpec { 4 | summon[DeBruijn.Of[Abs["x", Abs["y", Var["x"]]]] =:= 5 | DeBruijn.Abs["x", DeBruijn.Abs["y", DeBruijn.Var[2, "x"]]] 6 | ] 7 | 8 | summon[DeBruijn.Of[App[Abs["x", Abs["y", Var["x"]]], Var["b"]]] =:= 9 | DeBruijn.App[ 10 | DeBruijn.Abs["x", DeBruijn.Abs["y", DeBruijn.Var[2, "x"]]], 11 | DeBruijn.Var[0, "b"], 12 | ] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/typing/Environment.scala: -------------------------------------------------------------------------------- 1 | package typing 2 | 3 | import scala.compiletime.ops.any 4 | import util.{:+:, HList, HNil} 5 | 6 | object Environment { 7 | sealed trait Ty[Var <: String, T <: Type] 8 | 9 | type Lookup[Var <: String, Env <: HList] <: Type = Env match { 10 | case HNil => None 11 | case Ty[v, t] :+: rest => any.==[v, Var] match { 12 | case true => t 13 | case false => Lookup[Var, rest] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/scala/typing/ShowSpec.scala: -------------------------------------------------------------------------------- 1 | package typing 2 | 3 | object ShowSpec { 4 | summon[Show[Var[0]] =:= "a"] 5 | summon[Show[Var[1]] =:= "a"] 6 | summon[Show[Var[100]] =:= "a"] 7 | 8 | summon[Show[Fun[Var[1], Var[2]]] =:= "a -> b"] 9 | summon[Show[Fun[Var[1], Var[1]]] =:= "a -> a"] 10 | summon[Show[Fun[Var[1], Fun[Var[2], Var[3]]]] =:= "a -> b -> c"] 11 | summon[Show[Fun[Var[1], Fun[Var[2], Var[1]]]] =:= "a -> b -> a"] 12 | summon[Show[ 13 | Fun[ 14 | Fun[Var[1], Fun[Var[2], Var[3]]], 15 | Fun[ 16 | Fun[Var[1], Var[2]], 17 | Fun[Var[1], Var[3]] 18 | ] 19 | ] 20 | ] =:= "(a -> b -> c) -> (a -> b) -> a -> c"] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/scala/typing/Substitution.scala: -------------------------------------------------------------------------------- 1 | package typing 2 | 3 | import scala.compiletime.ops.any 4 | import util.{:+:, HList, HNil} 5 | 6 | sealed trait Subst[V <: Var[_], T <: Type] 7 | 8 | object Subst { 9 | type Ty[S <: HList, T <: Type] <: Type = S match { 10 | case HNil => T 11 | case Subst[Var[j], t] :+: rest => T match { 12 | case Var[i] => any.==[i, j] match { 13 | case true => Ty[rest, t] 14 | case _ => Ty[rest, T] 15 | } 16 | case Fun[s, t] => Fun[Ty[S, s], Ty[S, t]] 17 | } 18 | } 19 | 20 | type Eqn[S <: HList, E <: HList] <: HList = E match { 21 | case HNil => HNil 22 | case Eq[t1, t2] :+: rest => Eq[Ty[S, t1], Ty[S, t2]] :+: Eqn[S, rest] 23 | case Subst[t1, t2] :+: rest => Eqn[S, Eq[t1, t2] :+: rest] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/scala/lambda/DeBruijn.scala: -------------------------------------------------------------------------------- 1 | package lambda 2 | 3 | import util.{:+:, HList, HNil} 4 | 5 | object DeBruijn { 6 | sealed trait Term 7 | case class Var[I <: Int, V <: String](i: I, v: V) extends Term 8 | case class Abs[V <: String, T <: Term](v: V, t: T) extends Term 9 | case class App[T1 <: Term, T2 <: Term](t1: T1, t2: T2) extends Term 10 | 11 | type Of[T <: lambda.Term] <: Term = 12 | T match { 13 | case _ => Of.Transform[T, HNil] 14 | } 15 | object Of { 16 | 17 | type Transform[T <: lambda.Term, L <: HList] <: Term = 18 | T match { 19 | case lambda.Var[v] => 20 | HList.Index[v, L] match { 21 | case (i, _) => Var[i, v] 22 | } 23 | case lambda.Abs[v, t] => Abs[v, Transform[t, v :+: L]] 24 | case lambda.App[t1, t2] => App[Transform[t1, L], Transform[t2, L]] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/util/HList.scala: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import scala.compiletime.ops.{any, int} 4 | 5 | sealed trait HList { 6 | def :+:[T](t: T): T :+: this.type = new :+:(t, this) 7 | } 8 | object HList { 9 | type Index[A, L <: HList] <: (Int, Boolean) = 10 | L match { 11 | case h :+: t => 12 | any.==[h, A] match { 13 | case true => (1, true) 14 | case false => 15 | Index[A, t] match { 16 | case (i, true) => (int.+[i, 1], true) 17 | case (_, false) => (0, false) 18 | } 19 | } 20 | case HNil => (0, false) 21 | } 22 | 23 | type Concat[L1 <: HList, L2 <: HList] <: HList = L1 match { 24 | case h :+: t => h :+: Concat[t, L2] 25 | case HNil => L2 26 | } 27 | } 28 | 29 | sealed trait HNil extends HList 30 | case object HNil extends HNil 31 | 32 | final case class :+:[H, +T <: HList](h: H, t: T) extends HList 33 | -------------------------------------------------------------------------------- /src/main/scala/typing/Unification.scala: -------------------------------------------------------------------------------- 1 | package typing 2 | 3 | import util.{:+:, HList, HNil} 4 | 5 | trait Eq[T1 <: Type, T2 <: Type] 6 | 7 | trait Unified[S <: HList, Ok <: Boolean] 8 | 9 | type Unify[Eqn <: HList] <: Unified[_, _] = Eqn match { 10 | case HNil => 11 | Unified[HNil, true] 12 | case Eq[Var[i], t] :+: rest => 13 | Type.Equals[Var[i], t] match { 14 | case true => Unify[rest] 15 | case _ => Type.NotIn[i, t] match { 16 | case true => Unify[Subst.Eqn[Subst[Var[i], t] :+: HNil, rest]] match { 17 | case Unified[subst, ok] => Unified[Subst[Var[i], t] :+: subst, ok] 18 | } 19 | case _ => Unified[HNil, false] 20 | } 21 | } 22 | case Eq[Fun[s, t], Var[i]] :+: rest => 23 | Unify[Eq[Var[i], Fun[s, t]] :+: rest] 24 | case Eq[Fun[s1, t1], Fun[s2, t2]] :+: rest => 25 | Unify[Eq[s1, s2] :+: Eq[t1, t2] :+: rest] 26 | case Subst[s, t] :+: rest => Unify[Eq[s, t] :+: rest] 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 INA Lintaro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/test/scala/lambda/TermSpec.scala: -------------------------------------------------------------------------------- 1 | package lambda 2 | 3 | object TermSpec { 4 | summon[Show[Var["a"]] =:= "a"] 5 | 6 | summon[Show[Abs["a", Var["a"]]] =:= "λa.a"] 7 | 8 | summon[Show[App[Abs["a", Var["a"]], Var["b"]]] =:= "(λa.a) b"] 9 | 10 | summon[Show[Abs["x", Abs["y", Var["x"]]]] =:= "λxy.x"] 11 | 12 | summon[Show[Abs["x", Abs["y", Var["x"]]]] =:= "λxy.x"] 13 | 14 | summon[Show[Abs["x", Abs["y", Abs["z", App[App[Var["x"], Var["z"]], App[Var["y"], Var["z"]]]]]]] =:= "λxyz.x z (y z)"] 15 | 16 | summon[Show[App[App[Abs["x", Abs["y", Abs["z", App[App[Var["x"], Var["z"]], App[Var["y"], Var["z"]]]]]], Abs["x", Abs["y", Var["x"]]]], Abs["x", Abs["y", Var["x"]]]]] =:= "(λxyz.x z (y z)) (λxy.x) (λxy.x)"] 17 | } 18 | 19 | object ReadEvalPrintSpec { 20 | summon[ReadEvalPrint["(λa.a) b"] =:= "b"] 21 | 22 | summon[ReadEvalPrint["(λxyz.x z (y z)) (λxy.x) (λxy.x)"] =:= "λz.z"] 23 | 24 | summon[ReadEvalPrint["(λxyz.x z (y z)) (((λxyz.x z (y z)) ((λxy.x) (λxyz.x z (y z)))) (λxy.x)) (λz.z)"] =:= "λzz.z (z z)"] 25 | 26 | summon[ReadEvalPrint["(λxyz.x z (y z)) (((λxyz.x z (y z)) ((λxy.x) (λxyz.x z (y z)))) (λxy.x)) (λz.z) a b"] =:= "a (a b)"] 27 | } 28 | -------------------------------------------------------------------------------- /src/test/scala/typing/TypingSpec.scala: -------------------------------------------------------------------------------- 1 | package typing 2 | 3 | import lambda.Parse 4 | 5 | object TypingSpec { 6 | import lambda.EvalSpec.{s, k, skk, _0, _1, _2, _3, _4, o, y, z} 7 | 8 | summon[Type.Check[Parse["x"]] =:= false] 9 | summon[Type.Check[o] =:= false] 10 | summon[Type.Check[y] =:= false] 11 | summon[Type.Check[z] =:= false] 12 | 13 | summon[Type.Check[Parse["λx.x"]] =:= true] 14 | summon[Type.Check[k] =:= true] 15 | summon[Type.Check[s] =:= true] 16 | summon[Type.Check[skk] =:= true] 17 | summon[Type.Check[_1] =:= true] 18 | summon[Type.Check[_2] =:= true] 19 | summon[Type.Check[_3] =:= true] 20 | summon[Type.Check[_4] =:= true] 21 | 22 | summon[Type.Infer[Parse["x"]] =:= None] 23 | summon[Type.Infer[o] =:= None] 24 | summon[Type.Infer[y] =:= None] 25 | summon[Type.Infer[z] =:= None] 26 | 27 | summon[Show[Type.Infer[Parse["λx.x"]]] =:= "a -> a"] 28 | summon[Show[Type.Infer[k]] =:= "a -> b -> a"] 29 | summon[Show[Type.Infer[s]] =:= "(a -> b -> c) -> (a -> b) -> a -> c"] 30 | summon[Show[Type.Infer[skk]] =:= "a -> a"] 31 | summon[Show[Type.Infer[_1]] =:= "(a -> b) -> a -> b"] 32 | summon[Show[Type.Infer[_2]] =:= "(a -> a) -> a -> a"] 33 | summon[Show[Type.Infer[_3]] =:= "(a -> a) -> a -> a"] 34 | summon[Show[Type.Infer[_4]] =:= "(a -> a) -> a -> a"] 35 | } 36 | -------------------------------------------------------------------------------- /src/main/scala/lambda/Term.scala: -------------------------------------------------------------------------------- 1 | package lambda 2 | 3 | import scala.compiletime.ops.string 4 | 5 | sealed trait Term 6 | case class Var[V <: String](v: V) extends Term 7 | case class Abs[V <: String, T <: Term](v: V, t: T) extends Term 8 | case class App[T1 <: Term, T2 <: Term](t1: T1, t2: T2) extends Term 9 | 10 | type Eval[T <: Term] <: Term = 11 | T match { 12 | case _ => Term.Of[Eval.EvalM[DeBruijn.Of[T]]] 13 | } 14 | 15 | type Show[T <: Term] <: String = 16 | T match { 17 | case Var[v] => v 18 | case Abs[_, _] => string.+["λ", Show.AbsBody[T]] 19 | case App[t1, t2] => string.+[Show.AppL[t1], string.+[" ", Show.AppR[t2]]] 20 | } 21 | object Show { 22 | type AbsBody[T <: Term] <: String = 23 | T match { 24 | case Var[_] => string.+[".", Show[T]] 25 | case Abs[v, t] => string.+[v, AbsBody[t]] 26 | case App[_, _] => string.+[".", Show[T]] 27 | } 28 | 29 | type AppL[T <: Term] <: String = 30 | T match { 31 | case Var[_] => Show[T] 32 | case Abs[_, _] => string.+["(", string.+[Show[T], ")"]] 33 | case App[t1, t2] => Show[T] 34 | } 35 | 36 | type AppR[T <: Term] <: String = 37 | T match { 38 | case Var[_] => Show[T] 39 | case Abs[_, _] => string.+["(", string.+[Show[T], ")"]] 40 | case App[_, _] => string.+["(", string.+[Show[T], ")"]] 41 | } 42 | } 43 | 44 | type ReadEvalPrint[Src <: String] <: String = 45 | Src match { 46 | case _ => Show[Eval[Parse[Src]]] 47 | } 48 | 49 | object Term { 50 | type Of[T <: DeBruijn.Term] <: Term = 51 | T match { 52 | case DeBruijn.Var[_, v] => Var[v] 53 | case DeBruijn.Abs[v, t] => Abs[v, Of[t]] 54 | case DeBruijn.App[t1, t2] => App[Of[t1], Of[t2]] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/scala/lambda/ParserSpec.scala: -------------------------------------------------------------------------------- 1 | package lambda 2 | 3 | object ParserSpec { 4 | summon[Parse["x"] =:= Var["x"]] 5 | 6 | summon[Parse["λx.x"] =:= Abs["x", Var["x"]]] 7 | 8 | summon[Parse["x y"] =:= App[Var["x"], Var["y"]]] 9 | 10 | summon[Parse["(x)"] =:= Var["x"]] 11 | 12 | summon[Parse["((x))"] =:= Var["x"]] 13 | 14 | summon[Parse["(λx.x)"] =:= Abs["x", Var["x"]]] 15 | 16 | summon[Parse["((λx.x))"] =:= Abs["x", Var["x"]]] 17 | 18 | summon[Parse["λx.(x)"] =:= Abs["x", Var["x"]]] 19 | 20 | summon[Parse["(λx.(x))"] =:= Abs["x", Var["x"]]] 21 | 22 | summon[Parse["(x y)"] =:= App[Var["x"], Var["y"]]] 23 | 24 | summon[Parse["x (y)"] =:= App[Var["x"], Var["y"]]] 25 | 26 | summon[Parse["(x) y"] =:= App[Var["x"], Var["y"]]] 27 | 28 | summon[Parse["(x (y))"] =:= App[Var["x"], Var["y"]]] 29 | 30 | summon[Parse["((x) y)"] =:= App[Var["x"], Var["y"]]] 31 | 32 | summon[Parse["λx.x y"] =:= Abs["x", App[Var["x"], Var["y"]]]] 33 | 34 | summon[Parse["λx.λy.x"] =:= Abs["x", Abs["y", Var["x"]]]] 35 | 36 | summon[Parse["λxy.x"] =:= Abs["x", Abs["y", Var["x"]]]] 37 | 38 | summon[Parse["λx.(λy.x)"] =:= Abs["x", Abs["y", Var["x"]]]] 39 | 40 | summon[Parse["x y z"] =:= App[App[Var["x"], Var["y"]], Var["z"]]] 41 | 42 | summon[Parse["x (y z)"] =:= App[Var["x"], App[Var["y"], Var["z"]]]] 43 | 44 | summon[Parse["λxyz.x z (y z)"] =:= Abs["x", Abs["y", Abs["z", App[App[Var["x"], Var["z"]], App[Var["y"], Var["z"]]]]]]] 45 | 46 | summon[Parse["λx.xy"] =:= ParseError["EOF expected"]] 47 | 48 | summon[Parse["λ"] =:= ParseError["unexpected EOF"]] 49 | 50 | summon[Parse["λ.x"] =:= ParseError["unexpected token: ."]] 51 | 52 | summon[Parse["λx.x λx.x"] =:= ParseError["variable or parenthesis expected"]] 53 | 54 | summon[Parse["(x."] =:= ParseError["unclosed parenthesis"]] 55 | 56 | summon[Parse["(x "] =:= ParseError["variable or parenthesis expected"]] 57 | } 58 | -------------------------------------------------------------------------------- /src/main/scala/lambda/Eval.scala: -------------------------------------------------------------------------------- 1 | package lambda 2 | 3 | import scala.compiletime.ops.{any, int} 4 | 5 | // leftmost-outermost strategy 6 | object Eval { 7 | type Shift[T <: DeBruijn.Term, I <: Int, D <: Int] <: DeBruijn.Term = 8 | T match { 9 | case DeBruijn.Var[i, v] => 10 | DeBruijn.Var[int.>[i, D] match { 11 | case true => int.+[i, I] 12 | case false => i 13 | }, v] 14 | case DeBruijn.Abs[v, t] => 15 | DeBruijn.Abs[v, Shift[t, I, int.+[D, 1]]] 16 | case DeBruijn.App[t1, t2] => 17 | DeBruijn.App[Shift[t1, I, D], Shift[t2, I, D]] 18 | } 19 | 20 | // T1[I/T2] (replace I in T1 by T2) 21 | type Subst[T1 <: DeBruijn.Term, T2 <: DeBruijn.Term, I <: Int] <: DeBruijn.Term = 22 | T1 match { 23 | case DeBruijn.Var[i, v] => 24 | any.==[i, I] match { 25 | case true => Shift[T2, int.+[i, -1], 0] 26 | case false => 27 | DeBruijn.Var[int.>[i, I] match { 28 | case true => int.+[i, -1] 29 | case false => i 30 | }, v] 31 | } 32 | case DeBruijn.Abs[v, t] => 33 | DeBruijn.Abs[v, Subst[t, T2, int.+[I, 1]]] 34 | case DeBruijn.App[t1, t2] => 35 | DeBruijn.App[Subst[t1, T2, I], Subst[t2, T2, I]] 36 | } 37 | 38 | 39 | // single-step beta-reduction 40 | type Eval1[T <: DeBruijn.Term] <: (DeBruijn.Term, Boolean) = 41 | T match { 42 | case DeBruijn.App[DeBruijn.Abs[v, t1], t2] => // beta-redex 43 | (Subst[t1, t2, 1], true) 44 | case DeBruijn.App[t1, t2] => 45 | Eval1[t1] match { 46 | case (_, false) => 47 | Eval1[t2] match { 48 | case (t3, true) => (DeBruijn.App[t1, t3], true) 49 | case (t3, false) => (DeBruijn.App[t1, t3], false) 50 | } 51 | case (t3, true) => (DeBruijn.App[t3, t2], true) 52 | } 53 | case DeBruijn.Abs[v, t] => 54 | Eval1[t] match { 55 | case (t2, true) => (DeBruijn.Abs[v, t2], true) 56 | case (t2, false) => (DeBruijn.Abs[v, t2], false) 57 | } 58 | case _ => (T, false) 59 | } 60 | 61 | // multi-step beta-reduction 62 | type EvalM[T <: DeBruijn.Term] <: DeBruijn.Term = 63 | Eval1[T] match { 64 | case (t, true) => EvalM[t] 65 | case (_, false) => T 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Type-level lambda calculus in Scala 3 2 | ===================================== 3 | 4 | This repository demonstrates compile-time lambda calculus parser, evalator, and type 5 | checker in Scala 3. It is heavily depending on [match types][] feature. 6 | 7 | Components 8 | ---------- 9 | 10 | ### Parser 11 | 12 | `Parse[_]` returns a type representing an abstract syntax tree of the parsed term. 13 | 14 | ```scala 15 | import lambda.{Parse, Var, Abs, App} 16 | 17 | summon[Parse["λx.x"] =:= Abs["x", Var["x"]]] 18 | 19 | summon[Parse["λxy.x"] =:= Abs["x", Abs["y", Var["x"]]]] 20 | 21 | summon[Parse["x y"] =:= App[Var["x"], Var["y"]]] 22 | ``` 23 | 24 | ### Evaluator 25 | 26 | `Eval[_]` returns beta-normal form of the specified term. The evaluation strategy is 27 | leftmost-outermost. 28 | 29 | ```scala 30 | import lambda.{Show, Eval, Parse} 31 | 32 | summon[Show[Eval[Parse["(λxy.x) a b"]]] =:= "a"] 33 | ``` 34 | 35 | You can also use `ReadEvalPrint[_]` to `Parse` and `Show` together at once. 36 | 37 | ```scala 38 | import lambda.ReadEvalPrint 39 | 40 | summon[ReadEvalPrint["(λxy.x) a b"] =:= "a"] 41 | ``` 42 | 43 | ### Type checker 44 | 45 | `Type.Infer[_]` returns a (Scala) type representing an abstract syntax tree of the type 46 | (of simply typed lambda calculus) of the specified term. 47 | 48 | ```scala 49 | import lambda.Parse 50 | import typing.{Show, Type} 51 | 52 | summon[Show[Type.Infer[Parse["λx.x"]]] =:= "a -> a"] 53 | ``` 54 | 55 | You can also use `Type.Check[_]` (returns a boolean literal type) to determine whether a 56 | term is typeable. 57 | 58 | Related Work 59 | ------------ 60 | 61 | - [tarao/lambda-scala: Type level lambda calculus in Scala](https://github.com/tarao/lambda-scala) 62 | - Scala 2 implmentation of type-level lambda calculus 63 | - It has no type checker 64 | - [About type-level lambda calculus in C++03 templates](https://tarao.hatenablog.com/entry/20111101/1320143278) (in Japanese) 65 | - [source code](https://gist.github.com/tarao/1330110) 66 | - lambda-scala3 is a translation from this implementation (except the parser) 67 | - [About match types and compile-time string literals](https://xuwei-k.hatenablog.com/entry/2022/03/02/081401) by @xuwei-k (in Japanese) 68 | - [source code 1](https://gist.github.com/xuwei-k/dca46ea655c0a1666891901af80b6790) 69 | - [source code 2](https://gist.github.com/xuwei-k/521638aa17ebc839c8d8519bcdfdc7ae) 70 | - This taught me how to write a compile-time parser 71 | 72 | [match types]: https://dotty.epfl.ch/docs/reference/new-types/match-types.html 73 | -------------------------------------------------------------------------------- /src/main/scala/typing/Typing.scala: -------------------------------------------------------------------------------- 1 | package typing 2 | 3 | import scala.compiletime.ops.{any, boolean, int} 4 | import util.{:+:, HList, HNil} 5 | 6 | sealed trait Type 7 | case class Var[I <: Int](i: I) extends Type 8 | case class Fun[S <: Type, T <: Type](s: S, t: T) extends Type 9 | case class None() extends Type 10 | 11 | object Type { 12 | type Check[Term <: lambda.Term] <: Boolean = Infer[Term] match { 13 | case None => false 14 | case _ => true 15 | } 16 | 17 | type Infer[Term <: lambda.Term] <: Type = Infer0[HNil, Term, 0] match { 18 | case Typed[_, t, _] => t 19 | } 20 | 21 | trait Typed[S <: HList, T <: Type, Fresh <: Int] 22 | 23 | type Infer0[E <: HList, Term <: lambda.Term, Fresh <: Int] <: Typed[_, _, _] = Term match { 24 | case lambda.Var[v] => 25 | Typed[HNil, Environment.Lookup[v, E], Fresh] 26 | case lambda.Abs[v, term] => 27 | Infer0[Environment.Ty[v, Var[Fresh]] :+: E, term, int.+[Fresh, 1]] match { 28 | case Typed[subst, None, fresh] => Typed[subst, None, fresh] 29 | case Typed[subst, t, fresh] => 30 | Typed[subst, Fun[Subst.Ty[subst, Var[Fresh]], t], fresh] 31 | } 32 | case lambda.App[t1, t2] => 33 | Infer0[E, t1, int.+[Fresh, 1]] match { 34 | case Typed[eq1, None, fresh1] => Typed[eq1, None, fresh1] 35 | case Typed[eq1, type1, fresh1] => 36 | Infer0[E, t2, fresh1] match { 37 | case Typed[eq2, None, fresh2] => Typed[eq2, None, fresh2] 38 | case Typed[eq2, type2, fresh2] => 39 | Unify[ 40 | HList.Concat[eq1, HList.Concat[eq2, Eq[type1, Fun[type2, Var[Fresh]]] :+: HNil]] 41 | ] match { 42 | case Unified[subst, ok] => ok match { 43 | case false => Typed[subst, None, fresh2] 44 | case _ => Typed[subst, Subst.Ty[subst, Var[Fresh]], fresh2] 45 | } 46 | } 47 | } 48 | } 49 | } 50 | 51 | type Equals[T1 <: Type, T2 <: Type] <: Boolean = T1 match { 52 | case None => T2 match { 53 | case None => true 54 | case _ => false 55 | } 56 | case Var[i] => T2 match { 57 | case Var[j] => any.==[i, j] 58 | case _ => false 59 | } 60 | case Fun[s1, t1] => T2 match { 61 | case Fun[s2, t2] => boolean.&&[Eq[s1, s2], Eq[t1, t2]] 62 | case _ => false 63 | } 64 | } 65 | 66 | type NotIn[I <: Int, T <: Type] <: Boolean = T match { 67 | case Var[i] => any.!=[I, i] 68 | case Fun[s, t] => boolean.&&[NotIn[I, s], NotIn[I, t]] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/scala/typing/Show.scala: -------------------------------------------------------------------------------- 1 | package typing 2 | 3 | import scala.compiletime.ops.{any, int, string} 4 | import util.{:+:, HList, HNil} 5 | 6 | type Show[T <: Type] <: String = Show.AnyType[T, Show.VarMap[HNil, 0]] match { 7 | case Show.Str[str, _] => str 8 | } 9 | 10 | object Show { 11 | trait Str[+S <: String, +M <: VarMap[_, _]] 12 | 13 | type AnyType[T <: Type, M <: VarMap[_, _]] <: Str[String, VarMap[_, _]] = T match { 14 | case Var[i] => VarMapGetOrUpdate[i, M] match { 15 | case LookedUp[j, map] => Str[IntToString[j], map] 16 | } 17 | case Fun[s, t] => 18 | FunL[s, M] match { 19 | case Str[str1, map1] => 20 | AnyType[t, map1] match { 21 | case Str[str2, map2] => 22 | Str[string.+[str1, string.+[" -> ", str2]], map2] 23 | } 24 | } 25 | } 26 | 27 | type FunL[T <: Type, M <: VarMap[_, _]] <: Str[String, VarMap[_, _]] = T match { 28 | case Fun[_, _] => AnyType[T, M] match { 29 | case Str[str, map] => 30 | Str[string.+["(", string.+[str, ")"]], map] 31 | } 32 | case _ => AnyType[T, M] 33 | } 34 | 35 | trait ICons[A <: Int, B <: Int] 36 | trait VarMap[AssocList <: HList, Min <: Int] 37 | trait LookedUp[+I <: Int, +M <: VarMap[_, _]] 38 | 39 | type VarMapGetOrUpdate[I <: Int, M <: VarMap[_, _]] <: LookedUp[Int, VarMap[_, _]] = M match { 40 | case VarMap[map, min] => VarMapGet0[I, map] match { 41 | case ICons[_, i] => int.>=[i, 0] match { 42 | case true => LookedUp[i, M] 43 | case false => LookedUp[min, VarMap[ICons[I, min] :+: map, int.+[min, 1]]] 44 | } 45 | } 46 | } 47 | 48 | type VarMapGet0[I <: Int, Map <: HList] <: ICons[_, _] = Map match { 49 | case ICons[i, j] :+: rest => any.==[i, I] match { 50 | case true => ICons[i, j] 51 | case false => VarMapGet0[I, rest] 52 | } 53 | case HNil => ICons[I, -1] 54 | } 55 | 56 | type IntToString[I <: Int] <: String = 57 | int./[I, 26] match { 58 | case 0 => IntToString0[int.%[I, 26]] 59 | case _ => string.+[IntToString[int.+[int./[I, 26], -1]], IntToString0[int.%[I, 26]]] 60 | } 61 | 62 | type IntToString0[I <: Int] <: String = I match { 63 | case 0 => "a" 64 | case 1 => "b" 65 | case 2 => "c" 66 | case 3 => "d" 67 | case 4 => "e" 68 | case 5 => "f" 69 | case 6 => "g" 70 | case 7 => "h" 71 | case 8 => "i" 72 | case 9 => "j" 73 | case 10 => "k" 74 | case 11 => "l" 75 | case 12 => "m" 76 | case 13 => "n" 77 | case 14 => "o" 78 | case 15 => "p" 79 | case 16 => "q" 80 | case 17 => "r" 81 | case 18 => "s" 82 | case 19 => "t" 83 | case 20 => "u" 84 | case 21 => "v" 85 | case 22 => "w" 86 | case 23 => "x" 87 | case 24 => "y" 88 | case 25 => "z" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/scala/lambda/EvalSpec.scala: -------------------------------------------------------------------------------- 1 | package lambda 2 | 3 | import scala.compiletime.ops.int 4 | 5 | object Eval1Spec { 6 | summon[Eval.Eval1[DeBruijn.Of[App[Abs["x", Abs["y", Var["x"]]], Abs["y", Var["y"]]]]] =:= 7 | (DeBruijn.Abs["y", DeBruijn.Abs["y", DeBruijn.Var[1, "y"]]], true)] 8 | 9 | summon[Eval.Eval1[DeBruijn.Of[App[Abs["x", App[Var["x"], Abs["y", Var["x"]]]], Abs["y", Var["y"]]]]] =:= 10 | (DeBruijn.App[DeBruijn.Abs["y", DeBruijn.Var[1, "y"]], DeBruijn.Abs["y", DeBruijn.Abs["y", DeBruijn.Var[1, "y"]]]], true)] 11 | 12 | summon[Eval.Eval1[DeBruijn.Of[Abs["y", App[Abs["x", Abs["y", Var["x"]]], Abs["z", Var["y"]]]]]] =:= 13 | (DeBruijn.Abs["y", DeBruijn.Abs["y", DeBruijn.Abs["z", DeBruijn.Var[3, "y"]]]], true)] 14 | 15 | summon[Eval.Eval1[DeBruijn.Of[Abs["z", App[Abs["x", App[Var["z"], Abs["y", Var["x"]]]], Abs["y", Var["y"]]]]]] =:= 16 | (DeBruijn.Abs["z", DeBruijn.App[DeBruijn.Var[1, "z"], DeBruijn.Abs["y", DeBruijn.Abs["y", DeBruijn.Var[1, "y"]]]]], true)] 17 | } 18 | 19 | object EvalSpec { 20 | type s = Abs["x", Abs["y", Abs["z", App[App[Var["x"], Var["z"]], App[Var["y"], Var["z"]]]]]] 21 | type k = Abs["x", Abs["y", Var["x"]]] 22 | type ks = App[k, s] 23 | type skk = App[App[s, k], k] 24 | type skkski = App[App[s, App[App[s, ks], k]], skk] 25 | 26 | type yy = Abs["x", App[Var["f"], App[Var["x"], Var["x"]]]] 27 | type y = Abs["f", App[yy, yy]] 28 | type zz = Abs["x", App[Var["f"], Abs["y", App[App[Var["x"], Var["x"]], Var["y"]]]]] 29 | type z = Abs["f", App[zz, zz]] 30 | 31 | summon[Show[Eval[App[App[s, k], k]]] =:= "λz.z"] 32 | 33 | summon[Show[Eval[App[App[s, k], k]]] =:= "λz.z"] 34 | 35 | summon[Show[Eval[skkski]] =:= "λzz.z (z z)"] 36 | 37 | summon[Show[Eval[App[App[skkski, Var["a"]], Var["b"]]]] =:= "a (a b)"] 38 | 39 | type S[n <: Int] <: Term = n match { 40 | case 0 => Var["z"] 41 | case _ => App[Var["s"], S[int.+[n, -1]]] 42 | } 43 | 44 | type Church[n <: Int] <: Term = n match { case _ => Abs["s", Abs["z", S[n]]] } 45 | 46 | type _0 = Church[0] 47 | type _1 = Church[1] 48 | type _2 = Church[2] 49 | type _3 = Church[3] 50 | type _4 = Church[4] 51 | type _5 = Church[5] 52 | type _6 = Church[6] 53 | 54 | type t = k 55 | type f = Abs["x", Abs["y", Var["y"]]] 56 | type if0 = Abs["n", App[App[Var["n"], Abs["x", f]], t]] 57 | type mul = Abs["m", Abs["n", Abs["s", Abs["z", App[ 58 | App[Var["n"], App[Var["m"], Var["s"]]], 59 | Var["z"] 60 | ]]]]] 61 | type pred = Abs["n", Abs["s", Abs["z", App[ 62 | App[ 63 | App[Var["n"], Abs["f", Abs["g", App[Var["g"], App[Var["f"], Var["s"]]]]]], 64 | Abs["x", Var["z"]] 65 | ], 66 | Abs["x", Var["x"]] 67 | ]]]] 68 | type fact = Abs["r", Abs["n", 69 | App[ 70 | App[ 71 | App[if0, Var["n"]], // if 0 = n 72 | _1 // then 1 73 | ], 74 | App[ // otherwise 75 | App[mul, Var["n"]], // n * 76 | App[Var["r"], App[pred, Var["n"]]] // fact(n-1) 77 | ] 78 | ] 79 | ]] 80 | 81 | summon[Show[Eval[App[App[App[if0, _0], Var["a"]], Var["b"]]]] =:= "a"] 82 | summon[Show[Eval[App[App[App[if0, _1], Var["a"]], Var["b"]]]] =:= "b"] 83 | summon[Show[Eval[App[App[mul, _2], _2]]] =:= Show[_4]] 84 | summon[Show[Eval[App[App[mul, _2], _3]]] =:= Show[_6]] 85 | summon[Show[Eval[App[App[y, fact], _3]]] =:= Show[_6]] // this takes long 86 | 87 | type o = Abs["a", App[Var["a"], Var["a"]]] 88 | 89 | summon[Show[Eval[App[App[k, Var["c"]], App[o, o]]]] =:= "c"] // the strategy is normalizing 90 | } 91 | -------------------------------------------------------------------------------- /src/main/scala/lambda/Parser.scala: -------------------------------------------------------------------------------- 1 | package lambda 2 | 3 | import scala.compiletime.ops.{boolean, int, string} 4 | import scala.compiletime.ops.string.{Length, Matches, Substring} 5 | import util.{:+:, HList, HNil} 6 | 7 | type Parse[Src <: String] = 8 | Parser.ParseExp[Src] match { 9 | case ParsedTerm[t, rest] => rest match { 10 | case "" => t 11 | case _ => ParseError["EOF expected"] 12 | } 13 | case ParseError[s] => ParseError[s] 14 | } 15 | 16 | sealed trait ParseResult 17 | case class ParsedTerm[T <: Term, R <: String](term: T, rest: R) extends ParseResult 18 | case class ParseError[R <: String](reason: R) extends ParseResult 19 | 20 | @annotation.experimental // Length, Matches, Substring 21 | object Parser { 22 | // e := λf [ParseExp] 23 | // a 24 | // f := x.e [ParseAbs] 25 | // xf 26 | // a := p [ParseApp] 27 | // a p 28 | // p := x [ParsePrimary] 29 | // (e) 30 | 31 | type ParseExp[Src <: String] <: ParseResult = 32 | SafeSubstring[Src, 0, 1] match { 33 | case "λ" => ParseAbs[HNil, Substring[Src, 1, Length[Src]]] 34 | case _ => ParseApp[Src] 35 | } 36 | 37 | type ParseAbs[Args <: HList, Src <: String] <: ParseResult = 38 | Matches[Src, "[a-z][.].*"] match { 39 | case true => ParseExp[Substring[Src, 2, Length[Src]]] match { 40 | case ParsedTerm[t, rest] => ParsedTerm[MakeAbs[Substring[Src, 0, 1] :+: Args, t], rest] 41 | case ParseError[s] => ParseError[s] 42 | } 43 | case _ => Matches[SafeSubstring[Src, 0, 1], "[a-z]"] match { 44 | case true => ParseAbs[Substring[Src, 0, 1] :+: Args, Substring[Src, 1, Length[Src]]] 45 | case _ => SafeSubstring[Src, 0, 1] match { 46 | case "" => ParseError["unexpected EOF"] 47 | case _ => ParseError[string.+["unexpected token: ", Substring[Src, 0, 1]]] 48 | } 49 | } 50 | } 51 | 52 | type ParseApp[Src <: String] <: ParseResult = 53 | ParsePrimary[Src] match { 54 | case ParsedTerm[t, rest] => ParseApp1[t, rest] 55 | case ParseError[s] => ParseError[s] 56 | } 57 | 58 | type ParseApp1[Prev <: Term, Src <: String] <: ParseResult = 59 | SafeSubstring[Src, 0, 1] match { 60 | case " " => ParsePrimary[Substring[Src, 1, Length[Src]]] match { 61 | case ParsedTerm[t, rest] => ParseApp1[App[Prev, t], rest] 62 | case ParseError[s] => ParseError[s] 63 | } 64 | case _ => ParsedTerm[Prev, Src] 65 | } 66 | 67 | type ParsePrimary[Src <: String] <: ParseResult = 68 | Matches[SafeSubstring[Src, 0, 1], "[a-z]"] match { 69 | case true => ParsedTerm[Var[Substring[Src, 0, 1]], Substring[Src, 1, Length[Src]]] 70 | case _ => SafeSubstring[Src, 0, 1] match { 71 | case "(" => ParseExp[Substring[Src, 1, Length[Src]]] match { 72 | case ParsedTerm[t, rest] => Substring[rest, 0, 1] match { 73 | case ")" => ParsedTerm[t, Substring[rest, 1, Length[rest]]] 74 | case _ => ParseError["unclosed parenthesis"] 75 | } 76 | case ParseError[s] => ParseError[s] 77 | } 78 | case _ => ParseError["variable or parenthesis expected"] 79 | } 80 | } 81 | 82 | type MakeAbs[Args <: HList, T <: Term] <: Term = 83 | Args match { 84 | case v :+: rest => MakeAbs[rest, Abs[v, T]] 85 | case HNil => T 86 | } 87 | 88 | type SafeSubstring[S <: String, IBeg <: Int, IEnd <: Int] <: String = 89 | boolean.&&[int.>=[Length[S], IBeg], int.>=[Length[S], IEnd]] match { 90 | case true => Substring[S, IBeg, IEnd] 91 | case _ => "" 92 | } 93 | } 94 | --------------------------------------------------------------------------------