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