Church encoding rust
Tutorial Video
Text description (accessibility)
This video demonstrates the "Church encoding rust" functional Rust example. Difficulty level: Expert. Key concepts covered: Functional Programming. This example explores advanced type theory concepts applied to Rust. Key difference from OCaml: 1. **HKT gap**: Haskell and partly OCaml have higher
Tutorial
The Problem
This example explores advanced type theory concepts applied to Rust. Church encoding, Scott encoding, finally tagless, free monads, and effect systems are all techniques from the typed lambda calculus and category theory research community. They demonstrate how to encode data and control as pure functions, how to build extensible DSLs without committing to a representation, and how to model effects as values rather than side effects. These patterns originate in Haskell and OCaml research and require adapting to Rust's ownership model.
🎯 Learning Outcomes
Code Example
fn church_true<T>() -> impl Fn(T, T) -> T { |a, _| a }
fn church_false<T>() -> impl Fn(T, T) -> T { |_, b| b }
fn to_bool(b: impl Fn(bool, bool) -> bool) -> bool { b(true, false) }Key Differences
OCaml Approach
OCaml is the natural home for these patterns — they originate in the ML/Haskell research community:
(* OCaml's polymorphism and first-class modules make these patterns
more elegant than in Rust. Higher-kinded types are emulated in OCaml
using functors and first-class modules rather than GATs. *)
Full Source
#![allow(clippy::all)]
//! # Church Encoding
//!
//! Represent data using only functions (lambda calculus style).
/// Church numeral type - a number is how many times you apply f.
pub type ChurchNum<T> = Box<dyn Fn(Box<dyn Fn(T) -> T>) -> Box<dyn Fn(T) -> T>>;
/// Church zero - apply f zero times.
pub fn zero<T: 'static>() -> impl Fn(Box<dyn Fn(T) -> T>) -> Box<dyn Fn(T) -> T> {
|_f| Box::new(|x| x)
}
/// Church successor - apply f one more time.
pub fn succ<T: Clone + 'static>(
n: impl Fn(Box<dyn Fn(T) -> T>) -> Box<dyn Fn(T) -> T> + 'static,
) -> impl Fn(Box<dyn Fn(T) -> T>) -> Box<dyn Fn(T) -> T> {
move |f: Box<dyn Fn(T) -> T>| {
let nf = n(Box::new(move |x| f(x)));
Box::new(move |x| {
let inner = nf(x);
inner
})
}
}
/// Convert Church numeral to integer.
pub fn to_int(n: impl Fn(Box<dyn Fn(i32) -> i32>) -> Box<dyn Fn(i32) -> i32>) -> i32 {
n(Box::new(|x| x + 1))(0)
}
/// Church boolean - true.
pub fn church_true<T>() -> impl Fn(T, T) -> T {
|a, _b| a
}
/// Church boolean - false.
pub fn church_false<T>() -> impl Fn(T, T) -> T {
|_a, b| b
}
/// Church boolean to Rust bool.
pub fn to_bool(b: impl Fn(bool, bool) -> bool) -> bool {
b(true, false)
}
/// Church pair constructor.
pub fn pair<A: Clone + 'static, B: Clone + 'static>(
a: A,
b: B,
) -> impl Fn(Box<dyn Fn(A, B) -> A>) -> A + Clone {
move |f| f(a.clone(), b.clone())
}
/// Church pair first.
pub fn fst<A: Clone, B>(p: impl Fn(Box<dyn Fn(A, B) -> A>) -> A) -> A {
p(Box::new(|a, _b| a))
}
/// Simple demonstration with closures.
pub fn demo_church_bool() -> (bool, bool) {
let t = church_true();
let f = church_false();
(to_bool(t), to_bool(f))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_church_bool() {
assert!(to_bool(church_true()));
assert!(!to_bool(church_false()));
}
#[test]
fn test_zero() {
assert_eq!(to_int(zero()), 0);
}
#[test]
fn test_demo() {
let (t, f) = demo_church_bool();
assert!(t);
assert!(!f);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_church_bool() {
assert!(to_bool(church_true()));
assert!(!to_bool(church_false()));
}
#[test]
fn test_zero() {
assert_eq!(to_int(zero()), 0);
}
#[test]
fn test_demo() {
let (t, f) = demo_church_bool();
assert!(t);
assert!(!f);
}
}
Deep Comparison
OCaml vs Rust: Church Encoding
Church encoding represents data purely with functions.
Church Booleans
OCaml
let church_true a b = a
let church_false a b = b
let to_bool b = b true false
Rust
fn church_true<T>() -> impl Fn(T, T) -> T { |a, _| a }
fn church_false<T>() -> impl Fn(T, T) -> T { |_, b| b }
fn to_bool(b: impl Fn(bool, bool) -> bool) -> bool { b(true, false) }
Church Numerals
A number N is represented as "apply f N times":
Key Differences
| Aspect | OCaml | Rust |
|---|---|---|
| Closures | Lightweight | Require explicit types |
| Higher-rank | Easy | Requires impl Fn or dyn |
| Boxing | GC handles | Manual Box<dyn Fn> |