Updated lcalc to Rust stable
Getty Ritter
9 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 | } |