summaryrefslogtreecommitdiff
path: root/lambda-calcul/rust/src/lambda.rs
diff options
context:
space:
mode:
Diffstat (limited to 'lambda-calcul/rust/src/lambda.rs')
-rw-r--r--lambda-calcul/rust/src/lambda.rs292
1 files changed, 292 insertions, 0 deletions
diff --git a/lambda-calcul/rust/src/lambda.rs b/lambda-calcul/rust/src/lambda.rs
new file mode 100644
index 0000000..a73ca34
--- /dev/null
+++ b/lambda-calcul/rust/src/lambda.rs
@@ -0,0 +1,292 @@
+use proptest::{
+ arbitrary::any,
+ prelude::*,
+ strategy::{Strategy, ValueTree},
+ test_runner::TestRunner,
+};
+use rand::Rng;
+use std::collections::HashMap;
+
+use crate::ast::*;
+
+#[derive(Debug, PartialEq)]
+pub struct Environment<'a> {
+ parent: Box<Option<&'a Environment<'a>>>,
+ bindings: HashMap<String, Value>,
+}
+
+impl<'a> Environment<'a> {
+ pub fn new() -> Self {
+ Environment {
+ parent: Box::new(None),
+ bindings: HashMap::new(),
+ }
+ }
+
+ fn bind(&mut self, var: &str, value: &Value) {
+ self.bindings.insert(var.to_string(), value.clone());
+ }
+
+ fn extends(&'a self) -> Self {
+ Environment {
+ parent: Box::new(Some(self)),
+ bindings: HashMap::new(),
+ }
+ }
+
+ fn lookup(&self, var: &str) -> Option<&Value> {
+ self.bindings.get(var).or_else(|| match *self.parent {
+ Some(parent) => parent.lookup(var),
+ None => None,
+ })
+ }
+}
+
+impl<'a> Default for Environment<'a> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+pub fn eval_all(values: &[Value]) -> Vec<Value> {
+ let mut env = Environment::new();
+ values.iter().map(|v| eval_whnf(v, &mut env)).collect()
+}
+
+/// Reduce the given value to weak head normal form using call-by-name
+/// evaluation strategy.
+///
+/// call-by-name reduces the leftmost outermost redex first, which is
+/// not under a lambda abstraction.
+pub fn eval_whnf(arg: &Value, env: &mut Environment) -> Value {
+ match arg {
+ Value::Def(var, value) => {
+ env.bind(var, value);
+ Value::Bool(true) // TODO: return a more meaningful value?
+ }
+ Value::Let(var, value, expr) => {
+ let mut newenv = env.extends();
+ newenv.bind(var, value);
+ eval_whnf(expr, &mut newenv)
+ }
+ Value::App(l, r) => match eval_whnf(l, env) {
+ Value::Lam(v, body) => eval_whnf(&subst(&v, &body, r), env),
+ Value::Sym(var) => match env.lookup(&var) {
+ Some(val) => eval_whnf(&Value::App(Box::new(val.clone()), r.clone()), env),
+ None => arg.clone(),
+ },
+ other => Value::App(Box::new(other), r.clone()),
+ },
+ Value::Sym(var) => env.lookup(var).unwrap_or(arg).clone(),
+ other => other.clone(),
+ }
+}
+
+fn subst(var: &str, body: &Value, e: &Value) -> Value {
+ match body {
+ Value::Sym(x) if x == var => e.clone(),
+ Value::Lam(x, b) if x == var => {
+ let y = gensym();
+ let bd = subst(x, b, &Value::Sym(y.clone()));
+ Value::Lam(y, Box::new(bd))
+ }
+ Value::Lam(x, b) => Value::Lam(x.to_string(), Box::new(subst(var, b, e))),
+ Value::App(l, r) => Value::App(Box::new(subst(var, l, e)), Box::new(subst(var, r, e))),
+ other => other.clone(),
+ }
+}
+
+pub fn gensym() -> String {
+ let mut rng = rand::thread_rng();
+
+ let n1: u8 = rng.gen();
+ format!("x_{}", n1)
+}
+
+pub fn generate_expr(size: u32, runner: &mut TestRunner) -> Value {
+ match size {
+ 0 | 1 => {
+ let n = any::<u16>().new_tree(runner).unwrap().current();
+ Value::Num(n.into())
+ }
+ 2 => Value::Sym(ascii_identifier().new_tree(runner).unwrap().current()),
+ 3 => any_sym().new_tree(runner).unwrap().current(),
+ 4 => simple_app().new_tree(runner).unwrap().current(),
+ 5 => nested_simple_app().new_tree(runner).unwrap().current(),
+ 6 => simple_lambda().new_tree(runner).unwrap().current(),
+ 7 => app_to_lambda().new_tree(runner).unwrap().current(),
+ 8 => multi_app().new_tree(runner).unwrap().current(),
+ _ => any::<u32>()
+ .prop_flat_map(gen_terms)
+ .new_tree(runner)
+ .unwrap()
+ .current(),
+ }
+}
+
+pub fn generate_exprs(size: u32, runner: &mut TestRunner) -> Vec<Value> {
+ let sz = (0..size).new_tree(runner).unwrap().current();
+ (0..sz)
+ .collect::<Vec<_>>()
+ .into_iter()
+ .map(|_| generate_expr(size, runner))
+ .collect()
+}
+
+fn simple_app() -> impl Strategy<Value = Value> {
+ let leaf = prop_oneof![any_num(), any_sym()];
+ (leaf.clone(), leaf.clone()).prop_map(|(l, r)| Value::App(Box::new(l), Box::new(r)))
+}
+
+fn multi_app() -> impl Strategy<Value = Value> {
+ let leaf = prop_oneof![any_num(), any_sym()];
+ (leaf.clone(), leaf.clone()).prop_map(|(l, r)| Value::App(Box::new(l), Box::new(r)))
+}
+
+fn any_num() -> impl Strategy<Value = Value> {
+ any::<i32>().prop_map(Value::Num)
+}
+
+fn nested_simple_app() -> impl Strategy<Value = Value> {
+ let leaf = prop_oneof![any_num(), ascii_identifier().prop_map(Value::Sym)];
+ leaf.prop_recursive(4, 128, 5, move |inner| {
+ (inner.clone(), inner.clone()).prop_map(|(l, r)| Value::App(Box::new(l), Box::new(r)))
+ })
+}
+
+fn any_sym() -> impl Strategy<Value = Value> {
+ identifier().prop_map(Value::Sym)
+}
+
+fn simple_lambda() -> impl Strategy<Value = Value> {
+ // TODO: there's nothing to guarantee the variable appears in the body
+ (ascii_identifier(), nested_simple_app()).prop_map(|(v, b)| Value::Lam(v, Box::new(b)))
+}
+
+fn app_to_lambda() -> impl Strategy<Value = Value> {
+ let lam = simple_lambda();
+ let arg = prop_oneof![any_num(), any_sym(), nested_simple_app()];
+ (lam, arg).prop_map(|(l, a)| Value::App(Box::new(l), Box::new(a)))
+}
+
+/// Cantor pairing function
+/// See https://en.wikipedia.org/wiki/Pairing_function
+fn pairing(k: u32) -> (u32, u32) {
+ let a = ((((8 * (k as u64) + 1) as f64).sqrt() - 1.0) / 2.0).floor();
+ let b = (a * (a + 1.0)) / 2.0;
+ let n = (k as f64) - b;
+ (n as u32, (a - n) as u32)
+}
+
+fn gen_terms(u: u32) -> impl Strategy<Value = Value> {
+ if u % 2 != 0 {
+ let j = (u - 1) / 2;
+ if j % 2 == 0 {
+ let k = j / 2;
+ let (n, m) = pairing(k);
+ let r = (gen_terms(n), gen_terms(m))
+ .prop_map(move |(l, r)| Value::App(Box::new(l), Box::new(r)));
+ r.boxed()
+ } else {
+ let k = (j - 1) / 2;
+ let (n, m) = pairing(k);
+ let r = gen_terms(m).prop_map(move |v| Value::Lam(format!("x_{}", n), Box::new(v)));
+ r.boxed()
+ }
+ } else {
+ let j = u / 2;
+ Just(Value::Sym(format!("x_{}", j))).boxed()
+ }
+}
+
+#[cfg(test)]
+mod lambda_test {
+ use crate::parser::parse;
+
+ use super::{eval_all, eval_whnf, Environment, Value};
+
+ fn parse1(string: &str) -> Value {
+ parse(string).pop().unwrap()
+ }
+
+ fn eval1(value: &Value) -> Value {
+ eval_whnf(value, &mut Environment::new())
+ }
+
+ #[test]
+ fn evaluating_a_non_reducible_value_yields_itself() {
+ let value = parse1("(foo 12)");
+ assert_eq!(value, eval1(&value));
+ }
+
+ #[test]
+ fn evaluating_application_on_an_abstraction_reduces_it() {
+ let value = parse1("((lam x x) 12)");
+ assert_eq!(Value::Num(12), eval1(&value));
+ }
+
+ #[test]
+ fn substitution_occurs_within_abstraction_body() {
+ let value = parse1("(((lam x (lam y x)) 13) 12)");
+ assert_eq!(Value::Num(13), eval1(&value));
+ }
+
+ #[test]
+ fn substitution_occurs_within_application_body() {
+ let value = parse1("(((lam x (lam y (y x))) 13) 12)");
+ assert_eq!(
+ Value::App(Box::new(Value::Num(12)), Box::new(Value::Num(13))),
+ eval1(&value)
+ );
+ }
+
+ #[test]
+ fn substitution_does_not_capture_free_variables() {
+ let value = parse1("(((lam x (lam x x)) 13) 12)");
+ assert_eq!(Value::Num(12), eval1(&value));
+ }
+
+ #[test]
+ fn interpretation_applies_to_both_sides_of_application() {
+ let value = parse1("((lam x x) ((lam x x) 12))");
+ assert_eq!(Value::Num(12), eval1(&value));
+ }
+
+ #[test]
+ fn reduction_is_applied_until_normal_form_is_reached() {
+ let value = parse1("((((lam y (lam x (lam y (x y)))) 13) (lam x x)) 11)");
+ assert_eq!(Value::Num(11), eval1(&value));
+ }
+
+ #[test]
+ fn reduction_always_select_leftmost_outermost_redex() {
+ // this should not terminate if we evaluate the rightmost redex first, eg.
+ // applicative order reduction
+ let value = parse1("((lam x 1) ((lam x (x x)) (lam x (x x))))");
+ assert_eq!(Value::Num(1), eval1(&value));
+ }
+
+ #[test]
+ fn defined_symbols_are_evaluated_to_their_definition() {
+ let values = parse("(def foo 12) foo");
+ assert_eq!(vec![Value::Bool(true), Value::Num(12)], eval_all(&values));
+ }
+
+ #[test]
+ fn let_expressions_bind_symbol_to_expression_in_environment() {
+ let values = parse("(let (foo (lam x x)) (foo 12))");
+ assert_eq!(vec![Value::Num(12)], eval_all(&values));
+ }
+
+ #[test]
+ fn let_expressions_introduce_new_scope_for_bindings() {
+ let values = parse("(let (foo (lam x x)) ((let (foo foo) foo) 13))");
+ assert_eq!(vec![Value::Num(13)], eval_all(&values));
+ }
+
+ #[test]
+ fn bound_symbol_in_higher_scope_are_resolved() {
+ let values = parse("(let (id (lam x x)) (let (foo 12) (id foo)))");
+ assert_eq!(vec![Value::Num(12)], eval_all(&values));
+ }
+}