├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md └── src ├── lib.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pts" 3 | version = "0.0.1" 4 | authors = ["Andy Shiue "] 5 | 6 | [dependencies] 7 | log = "0.3" 8 | env_logger = "0.3" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PTS (Pure Type Systems) 2 | 3 | [![Build Status](https://travis-ci.org/AndyShiue/pts.svg?branch=master)](https://travis-ci.org/AndyShiue/pts) 4 | 5 | This is an implementation of *pure type systems* written in Rust. 6 | It's basically a rewrite from the Haskell version, [Simpler, Easier!](http://augustss.blogspot.tw/2007/10/simpler-easier-in-recent-paper-simply.html) 7 | 8 | --- 9 | 10 | Installation: 11 | 12 | 1. First make sure [Rust](https://www.rust-lang.org/en-US/) (and obviously also [`git`](https://git-scm.com)) has already been installed on your machine. 13 | 14 | 2. Clone this repository: `git clone https://github.com/AndyShiue/pts.git` 15 | 16 | 3. Navigate to the root of the project: `cd pts` 17 | 18 | 4. Run `cargo test` to run all the tests in the project. It might take some time. 19 | 20 | --- 21 | 22 | Originally, lambda calculus is invented to be a Turing-complete model of computation. 23 | Subsequent works add type systems on top of the lambda calculus, usually making it **not** Turing-complete, but stronger type systems lead to the ability to write mathematical proofs in it. 24 | Pure type systems are a generalization of the lambda cube, which consists of the simply typed lambda calculus, system F, calculus of constructions, etc. 25 | In this implementation, you can define your own pure type systems, consisting of one or more, or even infinite sorts. 26 | Currently, this project can only be used as a library, not an application, because I haven't dealt with parsing stuff. 27 | See the tests at the end [of the source code](https://github.com/AndyShiue/pts/blob/master/src/lib.rs) and also the comments for thorough explanation of the algorithms. 28 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // This is for debugging. 2 | #[macro_use] 3 | extern crate log; 4 | 5 | use std::fmt::{self, Debug, Display}; 6 | use std::hash::Hash; 7 | use std::collections::{HashSet, HashMap}; 8 | 9 | // A newtype wrapper representing a symbol. 10 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 11 | pub struct Symbol(pub String); 12 | 13 | impl Display for Symbol { 14 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 15 | write!(f, "{}", self.0) 16 | } 17 | } 18 | 19 | // The type of terms generic over pure type systems. 20 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 21 | pub enum Term { 22 | Var(Symbol), 23 | App(Box>, Box>), 24 | Lam(Symbol, Box>, Box>), 25 | Pi(Symbol, Box>, Box>), 26 | Sort(System::Sort), 27 | } 28 | 29 | impl Display for Term { 30 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 31 | use Term::*; 32 | match *self { 33 | Var(Symbol(ref str)) => write!(f, "{}", str), 34 | App(ref left, ref right) => write!(f, "({} {})", left, right), 35 | Lam(ref bound, ref ty, ref inner) => write!(f, "(\\{}: {}. {})", bound, ty, inner), 36 | Pi(ref bound, ref left, ref right) => write!(f, "({}: {}) -> {}", bound, left, right), 37 | Sort(ref sort) => write!(f, "{}", sort), 38 | } 39 | } 40 | } 41 | 42 | // Below, I provide several macros for generating terms easily. 43 | // (Explicitly writing out `Box`es is utterly cumbersome.) 44 | 45 | #[macro_export] 46 | macro_rules! var { 47 | ($str: expr) => { Term::Var(Symbol($str.into())) } 48 | } 49 | 50 | #[macro_export] 51 | macro_rules! app { 52 | ($left: expr, $right: expr) => { Term::App(Box::new($left), Box::new($right)) } 53 | } 54 | 55 | #[macro_export] 56 | macro_rules! lam { 57 | ($bound: expr, $ty: expr, $inner: expr) => { 58 | Term::Lam(Symbol($bound.into()), Box::new($ty), Box::new($inner)) 59 | } 60 | } 61 | 62 | #[macro_export] 63 | macro_rules! pi { 64 | ($bound: expr, $left: expr, $right: expr) => { 65 | Term::Pi(Symbol($bound.into()), Box::new($left), Box::new($right)) 66 | } 67 | } 68 | 69 | // A degenerating case of the `Pi` constructor. 70 | #[macro_export] 71 | macro_rules! arrow { 72 | ($left: expr, $right: expr) => { 73 | pi!("x", $left, $right) 74 | } 75 | } 76 | 77 | #[macro_export] 78 | macro_rules! sort { 79 | ($sort: expr) => { 80 | Term::Sort($sort) 81 | } 82 | } 83 | 84 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 85 | pub enum StarAndBox { 86 | Star, 87 | Box, 88 | } 89 | 90 | impl Display for StarAndBox { 91 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 92 | use self::StarAndBox::*; 93 | match *self { 94 | Star => write!(f, "*"), 95 | Box => write!(f, "[]"), 96 | } 97 | } 98 | } 99 | 100 | // The trait classifying a pure type system. 101 | // It consists of 3 things, respectively: 102 | // 1. Its sort, this is represent as an associated type. 103 | // 2. `axiom`, which is a function from any sort to its super-sort. 104 | // It returns an `Option` because some sort may not belong to any other sorts, 105 | // i.e. it's already the largest sort in the system. 106 | // 3. `rule`, the purpose of this function is to specify the type of a function from the type of 107 | // its argument and its return type. 108 | // If a function has type T1 -> T2, the type of T1 is s1 and the type of T2 is s2, 109 | // then the type of the whole function is rule(s1, s2). 110 | // Again, `rule` returns an `Option` because the function type isn't always well-formed. 111 | pub trait PureTypeSystem: Clone + Debug { 112 | type Sort: Copy + Clone + Debug + Display + Eq + Hash; 113 | fn axiom(sort: Self::Sort) -> Option; 114 | fn rule(s1: Self::Sort, s2: Self::Sort) -> Option; 115 | } 116 | 117 | // A private macro for generating pure type systems in the lambda cube. 118 | macro_rules! lambda_cube { 119 | ($name: ident; 120 | $($rule_s1: pat, $rule_s2: pat => $rule_s3: expr),*) => { 121 | 122 | // The name of the pure type system. 123 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 124 | pub struct $name; 125 | 126 | impl PureTypeSystem for $name { 127 | 128 | // In a system in the lambda cube, the only sorts available are always `Star` and `Box` 129 | type Sort = StarAndBox; 130 | 131 | // The type of `Star` is `Box`, and the `Box` is of no type. 132 | fn axiom(sort: StarAndBox) -> Option { 133 | use self::StarAndBox::*; 134 | match sort { 135 | Star => Some(Box), 136 | Box => None, 137 | } 138 | } 139 | 140 | // Everything the user of this macro needs to supply are the `rule`s. 141 | fn rule(s1: StarAndBox, s2: StarAndBox) -> Option { 142 | use self::StarAndBox::*; 143 | match (s1, s2) { 144 | // The `if true` here is a small hack. 145 | // Removing it makes `_` an unreachable pattern in `Coc`, 146 | // making the program not to compile. 147 | $(($rule_s1, $rule_s2) if true => Some($rule_s3),)* 148 | _ => None, 149 | } 150 | } 151 | } 152 | 153 | } 154 | } 155 | 156 | // Below are the definitions of several systems in the lambda cube. 157 | 158 | lambda_cube! { 159 | Stlc; 160 | Star, Star => Star 161 | } 162 | 163 | lambda_cube! { 164 | SystemF; 165 | Star, Star => Star, 166 | Box, Star => Star 167 | } 168 | 169 | lambda_cube! { 170 | SystemFOmega; 171 | Star, Star => Star, 172 | Box, Star => Star, 173 | Box, Box => Box 174 | } 175 | 176 | lambda_cube! { 177 | Coc; 178 | Star, Star => Star, 179 | Box, Star => Star, 180 | Box, Box => Box, 181 | Star, Box => Box 182 | } 183 | 184 | impl Term { 185 | 186 | // The starting point of type checking. 187 | pub fn type_check(self) -> Result, String> { 188 | debug!("Start type checking."); 189 | self.type_check_with_context(HashMap::new()) 190 | } 191 | 192 | // And the real implementation of the type checking. 193 | // We need to store typing information in a map called `context`. 194 | pub fn type_check_with_context(self, context: HashMap>) 195 | -> Result, String> { 196 | use self::Term::*; 197 | match self { 198 | // Simply lookup the context if I hit a variable. 199 | Var(v) => { 200 | match context.get(&v) { 201 | Some(ty) => Ok(ty.clone()), 202 | None => Err(format!("Cannot find variable {}.", &v.0)) 203 | } 204 | } 205 | // If I hit an application ... 206 | App(left, right) => { 207 | // First see if the left hand side type checks. 208 | let left_ty = left.type_check_with_context(context.clone())?; 209 | // If `left_ty` isn't a function in its `whnf` form, output an error.-------------+ 210 | match left_ty.whnf() { // | 211 | Pi(bound, ty_in, ty_out) => { // | 212 | // Let's then type check the right hand side. | 213 | let right_ty = right.clone().type_check_with_context(context.clone())?; //| 214 | // If the type of the right hand side matches the type of the argument of | 215 | // the `Pi` type, substitute the return type with the right hand side. | 216 | // The return type can have free occurences of the bound variable because | 217 | // now we are working with dependent types. | 218 | if right_ty.beta_eq(&ty_in) { // | 219 | Ok(ty_out.substitute(&bound, &right)) // | 220 | } else { // | 221 | // If the types doesn't match, return an error. | 222 | Err( // | 223 | format!( // | 224 | "Expected something of type {}, found that of type {}.", // | 225 | ty_in, right_ty // | 226 | ) // | 227 | ) // | 228 | } // | 229 | } // | 230 | left_ty => // | 231 | Err(format!("Expected lambda, found value of type {}.", left_ty)) // <----+ 232 | } 233 | } 234 | // If I hit a lambda ... 235 | Lam(bound, ty, inner) => { 236 | // Check if the type of the argument is well-formed, if it is, proceed ... 237 | ty.clone().type_check_with_context(context.clone())?; 238 | let mut new_context = context; 239 | // Insert the bound variable into the new context. 240 | new_context.insert(bound.clone(), *ty.clone()); 241 | // And type check the right hand side of the lambda with the new context. 242 | let inner_type = inner.type_check_with_context(new_context)?; 243 | Ok(Pi(bound, ty, Box::new(inner_type))) 244 | } 245 | // If I hit a `Pi` ... 246 | Pi(bound, left, right) => { 247 | // First, type check the type of the bound variable. 248 | // It must be a `Sort`, otherwise output an error. 249 | if let Sort(left_sort) = left.clone() 250 | .type_check_with_context(context.clone()) 251 | .map(Term::whnf)? { 252 | // Create a new context, the same as what we did in the case of `Lam`. 253 | let mut new_context = context; 254 | // Insert the bound variable. 255 | new_context.insert(bound, *left); 256 | // type check the right hand side of the `Pi` with the new context. 257 | let right_kind = right.clone() 258 | .type_check_with_context(new_context) 259 | .map(Term::whnf)?; 260 | // Again, check if the type of the return type is a `Sort`. 261 | if let Sort(right_sort) = right_kind { 262 | // Call `rule` to get the type of the whole function type. 263 | let new_sort = System::rule(left_sort.clone(), right_sort.clone()); 264 | match new_sort { 265 | Some(sort) => return Ok(Sort(sort.clone())), 266 | // If such rule doesn't exist, output an error. 267 | None => { 268 | let error_message = format!("Rule ({}, {}, _) doesn't exist.", 269 | left_sort, right_sort); 270 | Err(error_message) 271 | } 272 | } 273 | } else { 274 | Err(format!("Type {} isn't inhabited.", right)) 275 | } 276 | } else { 277 | Err(format!("Type {} isn't inhabited.", left)) 278 | } 279 | } 280 | // Finally, type check the sorts. It's an easy case. We just need to call `axiom`. 281 | Sort(sort) => { 282 | match System::axiom(sort) { 283 | Some(new_sort) => Ok(Sort(new_sort.clone())), 284 | None => Err(format!("Sort {} doesn't have a super-sort.", sort)), 285 | } 286 | } 287 | } 288 | } 289 | 290 | // This function returns the set of free variables in a term and is used during substitution. 291 | pub fn free_vars(&self) -> HashSet<&Symbol> { 292 | use self::Term::*; 293 | let mut set; 294 | match *self { 295 | // If what we get is a variable ... 296 | Var(ref v) => { 297 | set = HashSet::new(); 298 | // Then the only free variable is itself. 299 | set.insert(v); 300 | } 301 | // If it's an application, just merge the free variables in both sides of the term. 302 | App(ref left, ref right) => set = left.free_vars() 303 | .union(&right.free_vars()) 304 | .cloned() 305 | .collect(), 306 | // If it's a lambda ... 307 | Lam(ref bound, ref ty, ref inner) => { 308 | // Get the free variables from the right hand side. 309 | let mut tmp = inner.free_vars(); 310 | // And remove the bound variable (because it is bound). 311 | tmp.remove(&bound); 312 | // The type of the bound variable could also contain free variables. 313 | set = tmp.union(&ty.free_vars()).cloned().collect(); 314 | } 315 | // If it's a `Pi`, we do exactly the same as we did in a lambda! 316 | Pi(ref bound, ref left, ref right) => { 317 | let mut tmp = right.free_vars(); 318 | tmp.remove(&bound); 319 | set = tmp.union(&left.free_vars()).cloned().collect(); 320 | } 321 | // `Sort`s have no free variables. 322 | Sort(_) => { set = HashSet::new() } 323 | } 324 | debug!("{} has free variables {:?}.", self, set); 325 | set 326 | } 327 | 328 | // This function substitutes all occurences of the variable `from` into the term `to`. 329 | pub fn substitute(self, from: &Symbol, to: &Term) -> Term { 330 | use self::Term::*; 331 | match self { 332 | // If the term going to be substituted is a variable, there are 2 possibilities: 333 | // 1. `v == from`, then we just return `to`. 334 | // 2. `v != from`, then we return the variable untouched. 335 | Var(v) => if v == *from { to.clone() } else { Var(v) }, 336 | // If we hit an application, recursively substitute both sides. 337 | App(left, right) => app!(left.substitute(from, to), right.substitute(from, to)), 338 | // If we hit a lambda, hmmmmm, it's a hard case. 339 | Lam(ref bound, ref ty, ref inner) => { 340 | // If the bound variable coincide with `from`, we just need to substitite in its 341 | // type. 342 | if bound == from { 343 | Lam(bound.clone(), Box::new(ty.clone().substitute(from, to)), inner.clone()) 344 | } 345 | // If it doesn't ... 346 | else { 347 | // If the bound variable doesn't occur in `to`, then we simply go on 348 | // recursively. 349 | if !to.free_vars().contains(bound) { 350 | Lam(bound.clone(), Box::new(ty.clone().substitute(from, to)), 351 | Box::new(inner.clone().substitute(from, to))) 352 | } 353 | // And now the hardest part about substitution. 354 | else { 355 | // We create a mutable variable which should eventually be unused in both 356 | // the right hand side of the lambda and `to` 357 | let mut should_be_unused: Symbol = bound.clone(); 358 | should_be_unused.0.push_str("'"); 359 | loop { 360 | let used: HashSet<&Symbol> = inner.free_vars() 361 | .union(&to.free_vars()) 362 | .cloned() 363 | .collect(); 364 | // If `should_be_unused` actually is used, append the name of the 365 | // variable with an apostrophe. 366 | // Notice we're in a loop, so apostrophes will be appended indefinitely 367 | if used.contains(&should_be_unused) { 368 | should_be_unused.0.push_str("'") 369 | } 370 | // If `should_be_unused` literally isn't used ... 371 | else { 372 | // We change the symbols of the lambda from the clashed ones to the 373 | // unused ones. 374 | let renamed = 375 | Lam(should_be_unused.clone(), 376 | Box::new(ty.clone() 377 | .substitute(bound, 378 | &Var(should_be_unused.clone()))), 379 | Box::new(inner.clone() 380 | .substitute(bound, &Var(should_be_unused)))); 381 | // And then we do the real substitution. 382 | return renamed.substitute(from, to) 383 | } 384 | } 385 | } 386 | } 387 | } 388 | // `Pi` types are dealt with very similar to lambdas are. 389 | // I copy-pasted the code for the sake of not overengineering. 390 | Pi(ref bound, ref left, ref right) => { 391 | if bound == from { 392 | Pi(bound.clone(), Box::new(left.clone().substitute(from, to)), right.clone()) 393 | } else { 394 | if !to.free_vars().contains(bound) { 395 | Pi(bound.clone(), Box::new(left.clone().substitute(from, to)), 396 | Box::new(right.clone().substitute(from, to))) 397 | } else { 398 | let mut should_be_unused: Symbol = bound.clone(); 399 | should_be_unused.0.push_str("'"); 400 | loop { 401 | let used: HashSet<&Symbol> = right.free_vars() 402 | .union(&to.free_vars()) 403 | .cloned() 404 | .collect(); 405 | if used.contains(&should_be_unused) { 406 | should_be_unused.0.push_str("'") 407 | } else { 408 | let renamed = 409 | Pi(should_be_unused.clone(), 410 | Box::new(left.clone() 411 | .substitute(bound, 412 | &Var(should_be_unused.clone()))), 413 | Box::new(right.clone() 414 | .substitute(bound, &Var(should_be_unused)))); 415 | return renamed.substitute(from, to) 416 | } 417 | } 418 | } 419 | } 420 | } 421 | // If it's a sort, we don't need to do anything. 422 | this @ Sort(_) => this, 423 | } 424 | } 425 | 426 | // The purpose of this function is to get the *Weak Head Normal Form* of a term. 427 | pub fn whnf(self) -> Term { 428 | use self::Term::*; 429 | // Basically, the **spine** of the syntax tree will be evaluated in this function. 430 | fn spine(leftmost: Term, stack: &[Term]) -> Term { 431 | match (leftmost, stack) { 432 | // If we hit an application ... 433 | (App(left, right), _) => { 434 | let mut new_stack: Vec> = stack.into(); 435 | // Push the right hand side onto the stack ... 436 | new_stack.push(*right); 437 | // And then recurse. 438 | spine(*left, &new_stack) 439 | } 440 | // If we hit a lambda and the stack isn't empty ... 441 | (Lam(ref from, _, ref inner), ref stack) if !stack.is_empty() => { 442 | let mut new_stack: Vec> = (*stack).into(); 443 | // Unwrapping here after popping is safe because `stack` isn't empty. 444 | let right = new_stack.pop().unwrap(); 445 | // We just need to substitite and go forward. 446 | spine(inner.clone().substitute(&from, &right), &new_stack) 447 | } 448 | // We simply build the term again if we encounter anything else. 449 | (leftmost, _) => 450 | stack.iter() 451 | .fold(leftmost, |l, r| app!(l, r.clone())), 452 | } 453 | } 454 | spine(self, &[]) 455 | } 456 | 457 | // In comparison with `whnf`, we evaluate every reducible expressions in the term. 458 | // The definition of the function `nf` is very similar to that of `whnf`, 459 | // but merging them into 1 function also seems like overengineering right now. 460 | pub fn nf(self) -> Term { 461 | use self::Term::*; 462 | fn spine(leftmost: Term, stack: &[Term]) -> Term { 463 | match (leftmost, stack) { 464 | // The same as above. 465 | (App(left, right), _) => { 466 | let mut new_stack: Vec> = stack.into(); 467 | new_stack.push(*right); 468 | spine(*left, &new_stack) 469 | } 470 | // If the stack is empty, just recurse everywhere. 471 | (Lam(ref from, ref ty, ref inner), ref stack) if stack.is_empty() => { 472 | Lam(from.clone(), Box::new(ty.clone().nf()), Box::new(inner.clone().nf())) 473 | } 474 | // If the stack isn't empty, we do the same as above. 475 | (Lam(ref from, _, ref inner), ref stack) => { 476 | let mut new_stack: Vec> = (*stack).into(); 477 | // Unwrapping here after popping is safe because `stack` isn't empty. 478 | let right = new_stack.pop().unwrap(); 479 | spine(inner.clone().substitute(&from, &right), &new_stack) 480 | } 481 | // If we hit a `Pi`, we recurse everywhere and build the term again. 482 | (Pi(ref bound, ref left, ref inner), ref stack) => 483 | stack.iter() 484 | .fold(Pi(bound.clone(), Box::new(left.clone().nf()), 485 | Box::new(inner.clone().nf())), 486 | |l, r| app!(l, r.clone().nf())), 487 | // We simply build the term again if we encounter anything else. 488 | // Oh, now we also recurse on the right hand side. 489 | (leftmost, _) => 490 | stack.iter() 491 | .fold(leftmost, |l, r| app!(l, r.clone().nf())), 492 | } 493 | } 494 | spine(self, &[]) 495 | } 496 | 497 | // Alpha equality between types. 498 | pub fn alpha_eq(&self, another: &Term) -> bool { 499 | use self::Term::*; 500 | match (self, another) { 501 | (&Var(ref v1), &Var(ref v2)) => v1 == v2, 502 | (&App(ref left1, ref right1), &App(ref left2, ref right2)) => 503 | left1.alpha_eq(&left2) && right1.alpha_eq(&right2), 504 | (&Lam(ref bound1, ref ty1, ref inner1), &Lam(ref bound2, ref ty2, ref inner2)) => 505 | ty1.alpha_eq(ty2) && 506 | inner1.alpha_eq(&inner2.clone().substitute(&bound2, &Var(bound1.clone()))), 507 | (&Pi(ref bound1, ref left1, ref right1), &Pi(ref bound2, ref left2, ref right2)) => 508 | left1.alpha_eq(left2) && 509 | right1.alpha_eq(&right2.clone().substitute(&bound2, &Var(bound1.clone()))), 510 | (&Sort(ref sort1), &Sort(ref sort2)) => sort1 == sort2, 511 | _ => false 512 | } 513 | } 514 | 515 | // Beta equality between types. 516 | // To know 2 terms are beta equal, all you have to do is to make sure their `nf`s are alpha 517 | // equal. 518 | pub fn beta_eq(&self, another: &Term) -> bool { 519 | self.clone().nf().alpha_eq(&another.clone().nf()) 520 | } 521 | 522 | } 523 | 524 | #[cfg(test)] 525 | mod tests { 526 | 527 | use super::*; 528 | use super::StarAndBox::Star; 529 | 530 | // x == x 531 | #[test] 532 | fn alpha_eq_var() { 533 | let input: Term = var!("x"); 534 | assert!(input.alpha_eq(&var!("x"))) 535 | } 536 | 537 | // x != y 538 | #[test] 539 | #[should_panic] 540 | fn alpha_neq_var() { 541 | let input: Term = var!("x"); 542 | assert!(input.alpha_eq(&var!("y"))) 543 | } 544 | 545 | // \x: y. x == \a: y. a 546 | #[test] 547 | fn alpha_eq_lam() { 548 | let left: Term = lam!("x", var!("y"), var!("x")); 549 | let right = lam!("a", var!("y"), var!("a")); 550 | assert!(left.alpha_eq(&right)) 551 | } 552 | 553 | // (\x: y -> y. x)(\x: y. x) == (\a: y -> y. a)(\b: y. b) 554 | #[test] 555 | fn alpha_eq_app() { 556 | let left: Term = 557 | app!( 558 | lam!( 559 | "x", arrow!(var!("y"), var!("y")), 560 | var!("x") 561 | ), 562 | lam!( 563 | "x", var!("y"), 564 | var!("x") 565 | ) 566 | ); 567 | let right = 568 | app!( 569 | lam!( 570 | "a", arrow!(var!("y"), var!("y")), 571 | var!("a") 572 | ), 573 | lam!( 574 | "b", var!("y"), 575 | var!("b") 576 | ) 577 | ); 578 | assert!(left.alpha_eq(&right)) 579 | } 580 | 581 | // (a: *) -> a == (b: *) -> b 582 | #[test] 583 | fn alpha_eq_pi() { 584 | let left: Term = 585 | pi!( 586 | "a", sort!(Star), 587 | var!("a") 588 | ); 589 | let right = 590 | pi!( 591 | "b", sort!(Star), 592 | var!("b") 593 | ); 594 | assert!(left.clone().alpha_eq(&right)) 595 | } 596 | 597 | #[test] 598 | fn substitute_type() { 599 | let left: Term = 600 | app!( 601 | lam!( 602 | "x", sort!(Star), 603 | lam!( 604 | "x", var!("x"), 605 | var!("x") 606 | ) 607 | ), 608 | var!("y") 609 | ); 610 | let right: Term = 611 | lam!( 612 | "x", var!("y"), 613 | var!("x") 614 | ); 615 | assert!(left.beta_eq(&right)); 616 | } 617 | 618 | // id = \a: *. \x: a. x 619 | fn id() -> Term { 620 | lam!( 621 | "a", sort!(Star), 622 | lam!( 623 | "x", var!("a"), 624 | var!("x") 625 | ) 626 | ) 627 | } 628 | 629 | // id: (a: *) -> a -> a 630 | #[test] 631 | fn id_type_checks() { 632 | let ty: Term = 633 | pi!( 634 | "a", sort!(Star), 635 | arrow!( 636 | var!("a"), 637 | var!("a") 638 | ) 639 | ); 640 | assert_eq!(id().type_check().unwrap(), ty); 641 | } 642 | 643 | // This function turns a unsigned number into a Church numeral. 644 | fn church_nat(n: u32) -> Term { 645 | let mut onion = var!("x"); 646 | for _ in 0..n { 647 | onion = app!(var!("f"), onion) 648 | } 649 | lam!("t", sort!(Star), 650 | lam!( 651 | "f", arrow!(var!("t"), var!("t")), 652 | lam!( 653 | "x", var!("t"), 654 | onion 655 | ) 656 | ) 657 | ) 658 | } 659 | 660 | // The type of church numerals. 661 | fn nat_type() -> Term { 662 | pi!("t", sort!(Star), 663 | arrow!( 664 | arrow!(var!("t"), var!("t")), 665 | arrow!(var!("t"), var!("t")) 666 | ) 667 | ) 668 | } 669 | 670 | // `nat_type()` actually **is** the type of a church numeral 671 | #[test] 672 | fn church_nat_type_checks() { 673 | let meaning_of_life: Term = church_nat(42).type_check().unwrap(); 674 | assert!(meaning_of_life.beta_eq(&nat_type())); 675 | } 676 | 677 | // Summing up 2 church numerals. 678 | // plus(l, r) = \t: *. \f: t -> t. \x: t. l t f (r t f x) 679 | fn plus(l: Term, r: Term) -> Term { 680 | lam!( 681 | "t", sort!(Star), 682 | lam!( 683 | "f", arrow!(var!("t"), var!("t")), 684 | lam!( 685 | "x", var!("t"), 686 | app!( 687 | app!( 688 | app!( 689 | l, 690 | var!("t") 691 | ), 692 | var!("f") 693 | ), 694 | app!( 695 | app!( 696 | app!( 697 | r, 698 | var!("t") 699 | ), 700 | var!("f") 701 | ), 702 | var!("x") 703 | ) 704 | ) 705 | ) 706 | ) 707 | ) 708 | } 709 | 710 | // Check that plus(2, 3) equals 5. 711 | #[test] 712 | fn plus_check() { 713 | let two: Term = church_nat(2); 714 | let three = church_nat(3); 715 | assert!(plus(two, three).beta_eq(&church_nat(5))); 716 | } 717 | 718 | } 719 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | extern crate env_logger; 4 | 5 | #[macro_use] 6 | extern crate pts; 7 | 8 | // use pts::*; 9 | 10 | fn main() { 11 | env_logger::init().unwrap(); 12 | println!("Not implemented."); 13 | } 14 | --------------------------------------------------------------------------------