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)); |