Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Folders without a `test.sh` are skipped until backfilled.
One language per step, each landing as its own PR with implementation, README,
test.sh, and expected output, CI-green before merge. Order roughly by reach:

- [ ] Rust
- [x] Rust
- [ ] C#
- [ ] Swift
- [ ] Scala
Expand Down
54 changes: 54 additions & 0 deletions languages/r/rust/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Rust Lambda Core

This implementation expresses the project's Church booleans and Church
numerals as callable Rust values. It includes `TRUE`, `FALSE`, `NOT`, `AND`,
`OR`, `ZERO`, `SUCC`, `PRED`, and `ONE`, then checks and prints examples of
each operation.

## Requirements

- A stable Rust compiler (`rustc`)
- No external crates or other dependencies

Rust is included in GitHub's current
[`ubuntu-latest` runner image](https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md#rust-tools),
so this folder does not require a workflow installation step.

## Run and verify

From this directory, run:

```sh
sh test.sh
```

The test script compiles `lambda-core.rs` with warnings treated as errors,
runs it, and removes the temporary executable. From the repository root,
`./run-tests.sh` also runs this script and compares its output with
`expected-output.txt`.

## How the representation works

A lambda-calculus term accepts one term and returns another. Rust cannot give
a directly recursive closure type a finite size, so `Term` stores the closure
behind `Rc`:

```rust
struct Term(Rc<dyn Fn(Term) -> Term>);
```

`Rc` is part of Rust's standard library. It supplies the required indirection
and lets a captured term be referenced more than once; it does not implement
any boolean or numeral behavior itself.

The core definitions are direct translations of the lambda-calculus forms.
For example, `TRUE` selects its first argument, `FALSE` selects its second,
and `SUCC` applies a supplied function once more than its input numeral does.
`PRED` uses the standard higher-order predecessor expression shown beside its
implementation.

Only the two output helpers cross back into native Rust values. The boolean
helper gives a term two distinct markers and reports which marker it selects.
The numeral helper gives a term an incrementing function and counts how many
times that function is applied. The encodings and operations themselves never
store or inspect a Rust `bool` or integer.
20 changes: 20 additions & 0 deletions languages/r/rust/expected-output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
CHURCH BOOLEANS
TRUE: true
FALSE: false
NOT TRUE: false
NOT FALSE: true
FALSE AND FALSE: false
FALSE AND TRUE: false
TRUE AND FALSE: false
TRUE AND TRUE: true
FALSE OR FALSE: false
FALSE OR TRUE: true
TRUE OR FALSE: true
TRUE OR TRUE: true
CHURCH NUMERALS
ZERO: 0
ONE: 1
SUCC ONE: 2
PRED ZERO: 0
PRED ONE: 0
PRED (SUCC ONE): 1
197 changes: 197 additions & 0 deletions languages/r/rust/lambda-core.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
use std::cell::Cell;
use std::rc::Rc;

// A lambda-calculus term is a function from one term to another term.
// Rc supplies the indirection needed for this recursive type and lets a term
// be referenced more than once without copying the closure inside it.
#[derive(Clone)]
struct Term(Rc<dyn Fn(Term) -> Term>);

impl Term {
fn new(function: impl Fn(Term) -> Term + 'static) -> Self {
Self(Rc::new(function))
}

fn apply(&self, argument: Term) -> Term {
(self.0)(argument)
}

fn is_same_term_as(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}

// TRUE = λx.λy.x
fn church_true() -> Term {
Term::new(|x| Term::new(move |_y| x.clone()))
}

// FALSE = λx.λy.y
fn church_false() -> Term {
Term::new(|_x| Term::new(|y| y))
}

// NOT = λb.b FALSE TRUE
fn church_not() -> Term {
Term::new(|boolean| boolean.apply(church_false()).apply(church_true()))
}

// AND = λp.λq.p q FALSE
fn church_and() -> Term {
Term::new(|left| Term::new(move |right| left.apply(right).apply(church_false())))
}

// OR = λp.λq.p TRUE q
fn church_or() -> Term {
Term::new(|left| Term::new(move |right| left.apply(church_true()).apply(right)))
}

// ZERO = λf.λx.x
fn zero() -> Term {
Term::new(|_function| Term::new(|value| value))
}

// SUCC = λn.λf.λx.f (n f x)
fn successor() -> Term {
Term::new(|numeral| {
Term::new(move |function| {
let numeral = numeral.clone();

Term::new(move |value| {
let previous = numeral.apply(function.clone()).apply(value);
function.apply(previous)
})
})
})
}

// PRED = λn.λf.λx.
// n (λg.λh.h (g f)) (λu.x) (λu.u)
fn predecessor() -> Term {
Term::new(|numeral| {
Term::new(move |function| {
let numeral = numeral.clone();

Term::new(move |value| {
let function_for_step = function.clone();
let step = Term::new(move |g| {
let function = function_for_step.clone();
Term::new(move |h| h.apply(g.apply(function.clone())))
});

let constant = {
let value = value.clone();
Term::new(move |_unused| value.clone())
};
let identity = Term::new(|term| term);

numeral.apply(step).apply(constant).apply(identity)
})
})
})
}

fn one() -> Term {
successor().apply(zero())
}

// The observers below exist only to turn encoded values into printable Rust
// values. None of the encodings or operations use Rust bools or integers.
fn church_to_bool(boolean: Term) -> bool {
let when_true = Term::new(|term| term);
let when_false = Term::new(|term| term);
let selected = boolean.apply(when_true.clone()).apply(when_false.clone());

if selected.is_same_term_as(&when_true) {
true
} else if selected.is_same_term_as(&when_false) {
false
} else {
panic!("a Church boolean did not select either argument")
}
}

fn church_to_usize(numeral: Term) -> usize {
let applications = Rc::new(Cell::new(0));
let count = applications.clone();
let increment = Term::new(move |term| {
count.set(count.get() + 1);
term
});
let seed = Term::new(|term| term);

numeral.apply(increment).apply(seed);
applications.get()
}

fn check_bool(label: &str, boolean: Term, expected: bool) {
let actual = church_to_bool(boolean);
assert_eq!(actual, expected, "{label}");
println!("{label}: {actual}");
}

fn check_numeral(label: &str, numeral: Term, expected: usize) {
let actual = church_to_usize(numeral);
assert_eq!(actual, expected, "{label}");
println!("{label}: {actual}");
}

fn main() {
println!("CHURCH BOOLEANS");
check_bool("TRUE", church_true(), true);
check_bool("FALSE", church_false(), false);
check_bool("NOT TRUE", church_not().apply(church_true()), false);
check_bool("NOT FALSE", church_not().apply(church_false()), true);
check_bool(
"FALSE AND FALSE",
church_and().apply(church_false()).apply(church_false()),
false,
);
check_bool(
"FALSE AND TRUE",
church_and().apply(church_false()).apply(church_true()),
false,
);
check_bool(
"TRUE AND FALSE",
church_and().apply(church_true()).apply(church_false()),
false,
);
check_bool(
"TRUE AND TRUE",
church_and().apply(church_true()).apply(church_true()),
true,
);
check_bool(
"FALSE OR FALSE",
church_or().apply(church_false()).apply(church_false()),
false,
);
check_bool(
"FALSE OR TRUE",
church_or().apply(church_false()).apply(church_true()),
true,
);
check_bool(
"TRUE OR FALSE",
church_or().apply(church_true()).apply(church_false()),
true,
);
check_bool(
"TRUE OR TRUE",
church_or().apply(church_true()).apply(church_true()),
true,
);

println!("CHURCH NUMERALS");
check_numeral("ZERO", zero(), 0);
check_numeral("ONE", one(), 1);
check_numeral("SUCC ONE", successor().apply(one()), 2);
check_numeral("PRED ZERO", predecessor().apply(zero()), 0);
check_numeral("PRED ONE", predecessor().apply(one()), 0);
check_numeral(
"PRED (SUCC ONE)",
predecessor().apply(successor().apply(one())),
1,
);
}
18 changes: 18 additions & 0 deletions languages/r/rust/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/sh
set -eu

command -v rustc >/dev/null 2>&1 || exit 42

directory=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
build_directory=$(mktemp -d)

cleanup() {
[ ! -e "$build_directory/lambda-core" ] || rm -f "$build_directory/lambda-core"
[ ! -d "$build_directory" ] || rmdir "$build_directory"
}
trap cleanup EXIT HUP INT TERM

rustc --edition=2021 -D warnings \
"$directory/lambda-core.rs" \
-o "$build_directory/lambda-core"
"$build_directory/lambda-core"
Loading