Updated lcalc to Rust stable
Getty Ritter
10 years ago
| 1 | use std::thread; | |
| 2 | ||
| 1 | 3 | // This isn't a very Rust-ey lambda-calculus implementation. It's much |
| 2 | 4 | // more TAPL-ey, which I think makes it nice for pedagogical purposes |
| 3 | 5 | // when aimed at functional programmers, but you wouldn't actually |
| 4 | 6 | // use Rust like this in practice. |
| 5 | 7 | |
| 6 |
#[deriv |
|
| 8 | #[derive(Eq,PartialEq,Clone,Debug)] | |
| 7 | 9 | enum Term { |
| 8 |
Num(i |
|
| 10 | Num(i32), | |
| 9 | 11 | Var(String), |
| 10 | 12 | Lam(String, Box<Term>), |
| 11 | 13 | App(Box<Term>, Box<Term>), |
| 14 | 16 | |
| 15 | 17 | // The following are wrappers over λ-terms to simplify writing |
| 16 | 18 | // allocations. It really does help, as you can see in main. |
| 17 | fn num(n: int) -> Box<Term> { | |
| 18 | box Term::Num(n) | |
| 19 | fn num(n: i32) -> Box<Term> { | |
| 20 | Box::new(Term::Num(n)) | |
| 19 | 21 | } |
| 20 | 22 | |
| 21 | 23 | fn var(s: &str) -> Box<Term> { |
| 22 |
|
|
| 24 | Box::new(Term::Var(s.to_string())) | |
| 23 | 25 | } |
| 24 | 26 | |
| 25 | 27 | fn lam(x: &str, n: Box<Term>) -> Box<Term> { |
| 26 |
|
|
| 28 | Box::new(Term::Lam(x.to_string(), n)) | |
| 27 | 29 | } |
| 28 | 30 | |
| 29 | 31 | fn app(x: Box<Term>, y: Box<Term>) -> Box<Term> { |
| 30 |
|
|
| 32 | Box::new(Term::App(x, y)) | |
| 31 | 33 | } |
| 32 | 34 | |
| 33 | 35 | fn let_(x: &str, y: Box<Term>, z: Box<Term>) -> Box<Term> { |
| 34 |
|
|
| 36 | Box::new(Term::Let(x.to_string(), y, z)) | |
| 35 | 37 | } |
| 36 | 38 | |
| 37 | 39 | // A value is either a number or a closure, which has to have |
| 38 | 40 | // its environment around. We'll have to clone the environment |
| 39 | 41 | // into the closure to make sure that it stays around even if |
| 40 | 42 | // the closure is returned from the environment where it was used. |
| 41 |
#[deriv |
|
| 43 | #[derive(Eq,PartialEq,Clone,Debug)] | |
| 42 | 44 | enum Val { |
| 43 |
Num(i |
|
| 45 | Num(i32), | |
| 44 | 46 | Lam(String, Box<Term>, Box<Env>), |
| 47 | } | |
| 48 | ||
| 49 | fn vnum(n: i32) -> Box<Val> { | |
| 50 | Box::new(Val::Num(n)) | |
| 51 | } | |
| 52 | ||
| 53 | fn vlam(x: String, b: Box<Term>, e: Box<Env>) -> Box<Val> { | |
| 54 | Box::new(Val::Lam(x, b, e)) | |
| 45 | 55 | } |
| 46 | 56 | |
| 47 | 57 | // I could also use a pair of a map and a parent pointer, but |
| 48 | 58 | // this is a little more TAPL-ish. Plus, we generally always |
| 49 | 59 | // bind a single variable at a time. |
| 50 |
#[deriv |
|
| 60 | #[derive(Eq,PartialEq,Clone,Debug)] | |
| 51 | 61 | enum Env { |
| 52 | 62 | Empty, |
| 53 | 63 | Binding(String, Box<Val>, Box<Env>), |
| 73 | 83 | // ownership. |
| 74 | 84 | fn lcalc_eval(t: &Term, e: &Env) -> Box<Val> { |
| 75 | 85 | match t { |
| 76 | &Term::Num(num) => { | |
| 77 | box Val::Num(num) | |
| 86 | &Term::Num(num) => vnum(num), | |
| 87 | &Term::Var(ref str) => lookup(str, e), | |
| 88 | &Term::Lam(ref s, ref b) => { | |
| 89 | vlam(s.clone(), b.clone(), Box::new(e.clone())) | |
| 78 | 90 | } |
| 79 | &Term::Var(ref str) => { | |
| 80 | lookup(str, e) | |
| 81 | } | |
| 82 | &Term::Lam(ref s, ref b) => { | |
| 83 | box Val::Lam(s.clone(), b.clone(), box e.clone()) | |
| 84 | } | |
| 85 |
&Term::App( |
|
| 91 | &Term::App(ref f, ref x) => { | |
| 86 | 92 | match *lcalc_eval(f, e) { |
| 87 | Val::Lam(ref arg, box ref body, box ref env) => { | |
| 88 | let new_env = Env::Binding(arg.clone(), | |
| 89 | lcalc_eval(x, e), | |
| 90 | box env.clone()); | |
| 93 | Val::Lam(ref arg, ref body, ref env) => { | |
| 94 | let new_env = Env::Binding(arg.clone(), | |
| 95 | lcalc_eval(x, e), | |
| 96 | Box::new(*env.clone())); | |
| 91 | 97 | lcalc_eval(body, &new_env) |
| 92 | 98 | } |
| 93 | 99 | _ => panic!("Tried to apply a non-function!") |
| 94 | 100 | } |
| 95 | 101 | } |
| 96 |
&Term::Let(ref s, |
|
| 102 | &Term::Let(ref s, ref t, ref b) => { | |
| 97 | 103 | let new_env = |
| 98 | Env::Binding(s.clone(), | |
| 99 | lcalc_eval(t, e), | |
| 100 |
|
|
| 104 | Env::Binding(s.clone(), | |
| 105 | lcalc_eval(t, e), | |
| 106 | Box::new(e.clone())); | |
| 101 | 107 | lcalc_eval(b, &new_env) |
| 102 | 108 | } |
| 103 | 109 | } |
| 108 | 114 | fn lcalc_eval_opt(t: &Term, e: &Env) -> Option<Box<Val>> { |
| 109 | 115 | let new_term = t.clone(); |
| 110 | 116 | let new_env = e.clone(); |
| 111 |
let guard = |
|
| 117 | let guard = thread::spawn(move || { | |
| 112 | 118 | lcalc_eval(&new_term, &new_env) |
| 113 | 119 | }); |
| 114 | 120 | match guard.join() { |
| 133 | 139 | // (2)(3), which will also obviously fail |
| 134 | 140 | let s4 = app(num(2), num(3)); |
| 135 | 141 | let e = Env::Empty; |
| 136 | println!("s1: {:}", lcalc_eval_opt(&*s1, &e)); | |
| 137 | println!("s2: {:}", lcalc_eval_opt(&*s2, &e)); | |
| 138 | println!("s3: {:}", lcalc_eval_opt(&*s3, &e)); | |
| 139 | println!("s4: {:}", lcalc_eval_opt(&*s4, &e)); | |
| 142 | println!("s1: {:?}", lcalc_eval_opt(&*s1, &e)); | |
| 143 | println!("s2: {:?}", lcalc_eval_opt(&*s2, &e)); | |
| 144 | println!("s3: {:?}", lcalc_eval_opt(&*s3, &e)); | |
| 145 | println!("s4: {:?}", lcalc_eval_opt(&*s4, &e)); | |
| 140 | 146 | } |