https://github.com/pixelcmtd/clac
λ Rust implementation of the Lambda Calculus.
https://github.com/pixelcmtd/clac
Last synced: 3 months ago
JSON representation
λ Rust implementation of the Lambda Calculus.
- Host: GitHub
- URL: https://github.com/pixelcmtd/clac
- Owner: pixelcmtd
- License: gpl-2.0
- Created: 2021-09-29T17:47:06.000Z (almost 5 years ago)
- Default Branch: daddy
- Last Pushed: 2022-12-09T06:04:31.000Z (over 3 years ago)
- Last Synced: 2025-12-26T16:37:34.208Z (7 months ago)
- Language: Rust
- Homepage:
- Size: 2.68 MB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# λ
### also known as `clac`
This is an implementation of the λ-calculus in Rust. It has a few niceties to
make it more of a usable programming language. As a mathematician/computer
scientist, you should be familiar with the basic syntax: `λa.a` is the Identity
function (also known as `I`, „Identitätsfunktion“, “Idiot”). We also support the
shortened syntax, so that `λa b.a` is the Kestrel (also known as `K`, `C`,
„Konstante Funktion“, “Constant Function”). It is transparently π-expanded to
`λa.λb.a` by the parser. Additionally, you can assign variables: `I ← λa.a`,
`😈 ⇐ (λf.ff)(λf.ff)`. As you can see, `😈` is initialized using the expression
`(λf.ff)(λf.ff)` instead of `(λf.f f)(λf.f f)`, which is because `⇐` activates
the single-letter-form (aka. math-form).
> But how do you run programs using this notation?
That’s pretty simple: You mutate it, in different ways. The normal computations
are done using β-reduction, but the other procedures are also important.
## α-renaming
This is probably the most complicated algorithm as there is no obvious approach.
Take the identity function `λa.a`. It can also be expressed as `λb.b`, `λc.c`,
`λα.α`, `λÄ.Ä`, `λᴍʏᴠᴀʀɪᴀʙʟᴇ.ᴍʏᴠᴀʀɪᴀʙʟᴇ`, `λ🏳️⚧️.🏳️⚧️`, or any other way to
replace `a` everywhere in the function. This works as long as our new symbol
doesn't appear freely in our original function. For example, α-renaming a
function `λa.λb.a` to `λa.λa.a` is wrong, because `a` is free in `λb.a`.
This might seem simple, but, as I already said, it isn't. The difficult part is
determining, where to α-rename to which variable names. That has no standard
solution.
## β-reduction
The most important part.
Take the term `(λa.a)(λb.b)`. It being β-reduced is commonly written as
`(a)[a := (λb.b)]`, which results in `λb.b`.
In a general way, the term `(λx.f x)y` is β-reduced to `f y`.
To give you another example, let's add one and one:
```λ
+ 1 1 = (λm n f x . m f (n f x))(λf x.f x)(λf x.f x)
→ (λn f x . (λf x.f x) f (n f x))(λf x.f x)
→ λf x . (λf x.f x) f ((λf x.f x) f x)
→ λf x . (λx.f x) ((λx.f x) x)
→ λf x . (λx.f x) (f x)
→ λf x . f (f x) = 2
```
## η-reduction
This is quite simple, you might also know it as “point-free programming”.
A function `λx.f x` can be written as `f`. That's it!
A real world example: You want a function for adding two. The obvious solution
would be `λ x . + 2 x`. But if you want to feel like a **real** badass hacker,
you can write it as `+ 2`.
Most Haskell linters even force you to write your code this way, and you should.
## ι-expansion
This one implements unsigned integers aka natural numbers.
When [β-reducing](#β-reduction), integers are automatically ι-expanded like
this:
```
num = Σ("x")
while i > 0:
num = Α(Σ("f"), num)
i--
return Λ("f", Λ("x", num))
```
This gives you the correct Church encodings for all unsigned integers:
- 0 → `λf x.x`
- 1 → `λf x.f x`
- 2 → `λf x.f (f x)`
- …
## π-expansion
Here, functions with multiple parameters are converted into proper λ-calculus.
For example, the function `λa b.a` is expanded into `λa.λb.a`.
It works like this:
```
func = body
for param in params.reverse():
func = Λ(param, func)
return func
```
## Implementation details
We use `pest` to parse your statements into a high-level AST, then we generate
proper ASTs from that. Those basically look like that:

`λa b.a b` η-reduces to `λa.a`.
Here's how to add 1 and 1 using β-reduction:
