This commit is contained in:
mehbark 2024-11-17 16:23:22 -05:00
parent e9b352bc0a
commit 16fa54cc51
4 changed files with 168 additions and 27 deletions

58
Cargo.lock generated
View file

@ -5,3 +5,61 @@ version = 3
[[package]] [[package]]
name = "lc-to-iota" name = "lc-to-iota"
version = "0.1.0" version = "0.1.0"
dependencies = [
"thiserror",
]
[[package]]
name = "proc-macro2"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"

View file

@ -4,3 +4,4 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
thiserror = "2.0.3"

View file

@ -3,8 +3,12 @@
//! to a tree of [Iota (ι) combinators](https://en.wikipedia.org/wiki/Iota_and_Jot), //! to a tree of [Iota (ι) combinators](https://en.wikipedia.org/wiki/Iota_and_Jot),
//! represented by the [`Iota`] type. //! represented by the [`Iota`] type.
#[cfg(test)]
mod test;
use core::fmt; use core::fmt;
use std::{collections::HashSet, rc::Rc}; use std::rc::Rc;
use thiserror::Error;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Iota { pub enum Iota {
@ -12,42 +16,96 @@ pub enum Iota {
Iota, Iota,
} }
#[derive(Debug, Error, PartialEq, Eq)]
pub enum IotaCompileError {
#[error("LC term has a free variable")]
FreeVar,
}
impl TryFrom<LC> for Iota {
type Error = IotaCompileError;
fn try_from(value: LC) -> Result<Self, Self::Error> {
todo!()
}
}
/// A representation of the [lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus), with
/// [de Bruijn indices](https://en.wikipedia.org/wiki/De_Bruijn_index).
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum LC { pub enum LC {
/// A lambda abstraction; binds a new variable.
Abs(Rc<LC>), Abs(Rc<LC>),
/// An application of a function to an argument.
App(Rc<LC>, Rc<LC>), App(Rc<LC>, Rc<LC>),
/// A variable represented by a
/// [de Bruijn index](https://en.wikipedia.org/wiki/De_Bruijn_index).
///
/// Zero refers to the closest abstraction, and each successive number refers to a
/// nested abstraction, as shown in this diagram:
/// ```text
/// ┌─────┐
/// │ ┌─┐ │
/// λ λ λ 0 1 2
/// └─────────┘
/// ```
Var(usize), Var(usize),
} }
impl LC { impl LC {
/// Applies this term to the given term.
///
/// Often more ergonomic than [`LC::App`].
/// # examples
/// ```
/// # use lc_to_iota::LC::{Abs, App, Var};
/// assert_eq!(Var(0).app(1), App(Var(0).into(), Var(1).into()));
/// ```
#[must_use]
pub fn app(self, x: impl Into<Self>) -> Self { pub fn app(self, x: impl Into<Self>) -> Self {
Self::App(self.into(), x.into().into()) Self::App(self.into(), x.into().into())
} }
/// Adds an abstration around this term.
///
/// Often more ergonomic than [`LC::Abs`], but is somewhat backwards.
/// # examples
/// ```
/// # use lc_to_iota::LC::{Abs, App, Var};
/// assert_eq!(Var(0).abs(), Abs(Var(0).into()));
/// ```
#[must_use]
pub fn abs(self) -> Self { pub fn abs(self) -> Self {
Self::Abs(self.into()) Self::Abs(self.into())
} }
pub fn has_free_vars(&self) -> bool { /// Returns whether any variables are
// TODO: faster /// [free](https://en.wikipedia.org/wiki/Free_variables_and_bound_variables) in self;
!self.free_vars().is_empty() /// that is, whether any variables occur that are not bound by an abstraction.
///
/// # Examples
/// ```
/// # use lc_to_iota::LC::{Abs, App, Var};
/// // x
/// let x = Var(0);
/// assert!(x.has_free());
/// // λx.x
/// assert!(!x.abs().has_free());
/// // λfx.f x
/// let apply = Var(1).app(0).abs().abs();
/// assert!(!apply.has_free());
/// ```
#[must_use]
pub fn has_free(&self) -> bool {
self.has_free_past(0)
} }
// contextless free vars aren't that useful? fn has_free_past(&self, bound: usize) -> bool {
pub fn free_vars(&self) -> HashSet<usize> {
self.free_vars_past(0)
}
fn free_vars_past(&self, bound: usize) -> HashSet<usize> {
// TODO: faster
match self { match self {
LC::Abs(x) => x.free_vars_past(bound + 1), LC::Abs(x) => x.has_free_past(bound + 1),
LC::App(f, x) => { LC::App(f, x) => f.has_free_past(bound) || x.has_free_past(bound),
let f_vars = f.free_vars_past(bound); // if nothing is bound (bound = 0), x is always free
let x_vars = x.free_vars_past(bound); LC::Var(x) => *x >= bound,
&f_vars | &x_vars
}
LC::Var(_) => todo!(),
} }
} }
} }
@ -67,12 +125,3 @@ impl fmt::Display for LC {
} }
} }
} }
#[test]
fn lc_display() {
let identity = LC::Var(0).abs();
assert_eq!(identity.to_string(), "(λ 0)");
let apply = LC::Var(1).app(0).abs().abs();
assert_eq!(apply.to_string(), "(λ (λ (1 0)))");
}

33
src/test.rs Normal file
View file

@ -0,0 +1,33 @@
use crate::*;
#[test]
fn lc_display() {
use LC::*;
let identity = Var(0).abs();
assert_eq!(identity.to_string(), "(λ 0)");
let apply = Var(1).app(0).abs().abs();
assert_eq!(apply.to_string(), "(λ (λ (1 0)))");
assert_eq!(apply.to_string(), "(λ (λ (1 0)))");
}
#[test]
fn lc_free() {
use LC::*;
assert!(Var(0).has_free());
assert!(!Var(0).abs().has_free());
assert!(Var(1).app(0).abs().has_free());
assert!(!Var(1).app(0).abs().abs().has_free());
assert!(Var(0).app(1).abs().has_free());
for i in 0..10 {
assert!(Var(i).has_free());
let mut term = Var(i);
for _ in 0..=i {
term = term.abs();
}
// var `n` with `n+1` abstractions is bound.
assert!(!term.has_free());
}
}