diff --git a/src/func.rs b/src/func.rs index 0372d0f..0be33e1 100644 --- a/src/func.rs +++ b/src/func.rs @@ -7,7 +7,7 @@ pub mod test_suite; pub mod tests; pub trait WeakFunctor { - type F<'a, A>; + type F<'a, A: 'a> : 'a; } /// Rust-specific implementation of [Functor], respecting `move` semantics. diff --git a/src/func/classes.rs b/src/func/classes.rs index a745fa3..cee538c 100644 --- a/src/func/classes.rs +++ b/src/func/classes.rs @@ -1,6 +1,7 @@ -pub mod compositionclass; -pub mod futureclass; -pub mod lazyclass; -pub mod optionclass; -pub mod resultclass; -pub mod soloclass; +pub mod composition; +pub mod future; +pub mod lazy; +pub mod option; +pub mod result; +pub mod solo; +pub mod stackless; diff --git a/src/func/classes/compositionclass.rs b/src/func/classes/composition.rs similarity index 96% rename from src/func/classes/compositionclass.rs rename to src/func/classes/composition.rs index 10bbb45..2c81f7a 100644 --- a/src/func/classes/compositionclass.rs +++ b/src/func/classes/composition.rs @@ -3,7 +3,7 @@ use crate::func::*; struct CompositionClass(U, V); impl WeakFunctor for CompositionClass { - type F<'a, A> = U::F<'a, V::F<'a, A>>; + type F<'a, A: 'a> = U::F<'a, V::F<'a, A>>; } impl Functor for CompositionClass { diff --git a/src/func/classes/futureclass.rs b/src/func/classes/future.rs similarity index 96% rename from src/func/classes/futureclass.rs rename to src/func/classes/future.rs index cfc83d1..9b2b1a6 100644 --- a/src/func/classes/futureclass.rs +++ b/src/func/classes/future.rs @@ -7,7 +7,7 @@ use crate::func::*; pub struct FutureClass; impl WeakFunctor for FutureClass { - type F<'a, A> = Pin>>; + type F<'a, A: 'a> = Pin>>; } impl Functor for FutureClass { diff --git a/src/func/classes/lazyclass.rs b/src/func/classes/lazy.rs similarity index 96% rename from src/func/classes/lazyclass.rs rename to src/func/classes/lazy.rs index 124e1d0..ace391b 100644 --- a/src/func/classes/lazyclass.rs +++ b/src/func/classes/lazy.rs @@ -3,7 +3,7 @@ use crate::func::*; struct LazyClass; impl WeakFunctor for LazyClass { - type F<'a, A> = Box A>; + type F<'a, A: 'a> = Box A>; } impl Functor for LazyClass { diff --git a/src/func/classes/optionclass.rs b/src/func/classes/option.rs similarity index 93% rename from src/func/classes/optionclass.rs rename to src/func/classes/option.rs index 2e1988c..3b6c751 100644 --- a/src/func/classes/optionclass.rs +++ b/src/func/classes/option.rs @@ -3,7 +3,7 @@ use crate::func::*; pub struct OptionClass; impl WeakFunctor for OptionClass { - type F<'a, A> = Option; + type F<'a, A: 'a> = Option; } impl Functor for OptionClass { @@ -66,12 +66,12 @@ impl Monad for OptionClass { #[cfg(test)] mod option_tests { - use super::{test_suite, tests}; + use super::{test_suite, tests, Functor}; - use super::{Functor, OptionClass as T}; + use super::OptionClass as T; impl tests::Eqr for T { - fn eqr<'a, A: PartialEq + std::fmt::Debug>( + fn eqr<'a, A: PartialEq + std::fmt::Debug + 'a>( name: &'a str, left: Self::F<'a, A>, right: Self::F<'a, A>, diff --git a/src/func/classes/resultclass.rs b/src/func/classes/result.rs similarity index 94% rename from src/func/classes/resultclass.rs rename to src/func/classes/result.rs index 1fcf4e3..08e0d74 100644 --- a/src/func/classes/resultclass.rs +++ b/src/func/classes/result.rs @@ -1,9 +1,9 @@ use crate::func::*; -pub struct ResultClass(E); +pub struct ResultClass(E); impl WeakFunctor for ResultClass { - type F<'a, A> = Result; + type F<'a, A: 'a> = Result; } impl Functor for ResultClass { diff --git a/src/func/classes/soloclass.rs b/src/func/classes/solo.rs similarity index 98% rename from src/func/classes/soloclass.rs rename to src/func/classes/solo.rs index 95776a9..07f0ae2 100644 --- a/src/func/classes/soloclass.rs +++ b/src/func/classes/solo.rs @@ -3,7 +3,7 @@ use crate::func::*; pub struct SoloClass; impl WeakFunctor for SoloClass { - type F<'a, A> = A; + type F<'a, A: 'a> = A; } impl Functor for SoloClass { diff --git a/src/func/classes/stackless.rs b/src/func/classes/stackless.rs new file mode 100644 index 0000000..a9918e8 --- /dev/null +++ b/src/func/classes/stackless.rs @@ -0,0 +1,278 @@ +use std::{cell::Cell, rc::Rc}; + +use crate::func::*; + +enum EvalTree<'a> { + Atom(Box Option>>), + Composite(Box>, Box>), +} + +impl<'a> EvalTree<'a> { + fn next(self) -> Option> { + match self { + EvalTree::Atom(f) => f(), + EvalTree::Composite(left, right) => match *left { + EvalTree::Atom(f) => match f() { + Some(newleft) => Some(EvalTree::Composite(Box::new(newleft), right)), + None => Some(*right), + }, + EvalTree::Composite(leftleft, leftright) => Some(EvalTree::Composite( + leftleft, + Box::new(EvalTree::Composite(leftright, right)), + )), + }, + } + } +} + +pub struct Stackless<'a, A: 'a>( + Box) -> Option>>, +); + +fn set_cell(cell: Rc>>, a: A) -> () { + if cell.replace(Some(a)).is_some() { + panic!("MITM overwritten") + } +} + +fn get_cell(cell: Rc>>) -> A { + match cell.replace(None) { + Some(val) => val, + None => panic!("MITM not set"), + } +} + +impl<'a, A: 'a> Stackless<'a, A> { + fn call(self, f: impl 'a + FnOnce(A)) -> Option> { + self.0(Box::new(f)) + } + + fn bind(self, f: impl 'a + FnOnce(A) -> Stackless<'a, B>) -> Stackless<'a, B> { + Stackless(Box::new(|takesb| { + let lcell = Rc::new(Cell::new(None)); + let rcell = lcell.clone(); + Some(EvalTree::Composite( + Box::new(EvalTree::Atom(Box::new(move || { + self.call(move |a| set_cell(lcell, a)) + }))), + Box::new(EvalTree::Atom(Box::new(move || { + let stackless = f(get_cell(rcell)); + Some(EvalTree::Atom(Box::new(|| stackless.0(takesb)))) + }))), + )) + })) + } + + fn map(self, f: impl 'a + FnOnce(A) -> B) -> Stackless<'a, B> { + Stackless(Box::new(|takesb| { + let lcell = Rc::new(Cell::new(None)); + let rcell = lcell.clone(); + Some(EvalTree::Composite( + Box::new(EvalTree::Atom(Box::new(move || { + self.call(move |a| set_cell(lcell, a)) + }))), + Box::new(EvalTree::Atom(Box::new(move || { + let b = f(get_cell(rcell)); + Some(EvalTree::Atom(Box::new(|| { + takesb(b); + None + }))) + }))), + )) + })) + } + + pub fn evaluate(self) -> A { + let ocell = Rc::new(Cell::new(None)); + let icell = ocell.clone(); + let mut eval = self.call(|a| set_cell(icell, a)); + while let Some(tree) = eval { + eval = tree.next() + } + get_cell(ocell) + } +} + +impl<'a, A: 'a> From for Stackless<'a, A> { + fn from(value: A) -> Self { + Stackless(Box::new(|takesa| { + Some(EvalTree::Atom(Box::new(|| { + takesa(value); + None + }))) + })) + } +} + +struct StacklessClass; + +impl WeakFunctor for StacklessClass { + type F<'a, A: 'a> = Stackless<'a, A>; +} + +impl Functor for StacklessClass { + fn fmap<'a, A: 'a, B: 'a>(f: impl 'a + FnOnce(A) -> B, fa: Self::F<'a, A>) -> Self::F<'a, B> + where + Self: 'a, + { + fa.map(f) + } + + fn replace<'a, A: 'a, B: 'a>(fa: Self::F<'a, A>, b: B) -> Self::F<'a, B> + where + Self: 'a, + { + Stackless(Box::new(|takesb| { + Some(EvalTree::Composite( + Box::new(EvalTree::Atom(Box::new(move || fa.call(drop)))), + Box::new(EvalTree::Atom(Box::new(move || { + takesb(b); + None + }))), + )) + })) + } +} + +impl ApplicativeSeq for StacklessClass { + fn seq<'a, A: 'a, B: 'a>( + ff: Self::F<'a, impl 'a + FnOnce(A) -> B>, + fa: Self::F<'a, A>, + ) -> Self::F<'a, B> + where + Self: 'a, + { + ff.bind(|f| fa.map(f)) + } +} + +impl ApplicativeLA2 for StacklessClass { + fn la2<'a, A: 'a, B: 'a, C: 'a>( + f: impl 'a + FnOnce(A, B) -> C, + fa: Self::F<'a, A>, + fb: Self::F<'a, B>, + ) -> Self::F<'a, C> + where + Self: 'a, + { + Self::_la2(f, fa, fb) + } +} + +impl Applicative for StacklessClass { + fn pure<'a, A: 'a>(a: A) -> Self::F<'a, A> { + Stackless::from(a) + } + + fn discard_first<'a, A: 'a, B: 'a>(fa: Self::F<'a, A>, fb: Self::F<'a, B>) -> Self::F<'a, B> + where + Self: 'a, + { + Stackless(Box::new(|takesb| { + Some(EvalTree::Composite( + Box::new(EvalTree::Atom(Box::new(|| fa.call(drop)))), + Box::new(EvalTree::Atom(Box::new(|| fb.0(takesb)))), + )) + })) + } + + fn discard_second<'a, A: 'a, B: 'a>(fa: Self::F<'a, A>, fb: Self::F<'a, B>) -> Self::F<'a, A> + where + Self: 'a, + { + Stackless(Box::new(|takesa| { + Some(EvalTree::Composite( + Box::new(EvalTree::Atom(Box::new(|| fa.0(takesa)))), + Box::new(EvalTree::Atom(Box::new(|| fb.call(drop)))), + )) + })) + } +} + +impl Monad for StacklessClass { + fn bind<'a, A: 'a, B: 'a>( + fa: Self::F<'a, A>, + f: impl 'a + FnOnce(A) -> Self::F<'a, B>, + ) -> Self::F<'a, B> + where + Self: 'a, + { + fa.bind(f) + } + fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> + where + Self::F<'a, A>: 'a, + Self: 'a, + { + Stackless(Box::new(|takesa| { + let lcell = Rc::new(Cell::new(None)); + let rcell = lcell.clone(); + Some(EvalTree::Composite( + Box::new(EvalTree::Atom(Box::new(move || { + ffa.call(move |a| set_cell(lcell, a)) + }))), + Box::new(EvalTree::Atom(Box::new(move || { + let stackless = get_cell(rcell); + Some(EvalTree::Atom(Box::new(|| stackless.0(takesa)))) + }))), + )) + })) + } +} + +#[cfg(test)] +mod stackless_test { + use super::{test_suite, tests, Stackless}; + + use super::StacklessClass as T; + + impl tests::Eqr for T { + fn eqr<'a, A: PartialEq + std::fmt::Debug + 'a>( + name: &'a str, + left: Self::F<'a, A>, + right: Self::F<'a, A>, + ) -> tests::R { + tests::eqr(name, left.evaluate(), right.evaluate()) + } + } + + impl test_suite::FunctorTestSuite for T { + fn sample<'a, A: 'a, F: FnMut(&'a dyn Fn(A) -> Self::F<'a, A>)>(mut f: F) + where + Self::F<'a, A>: 'a, + { + f(&|a| a.into()); + } + } + + #[test] + fn monad_follows_laws() { + test_suite::monad_follows_laws::().unwrap(); + } + + fn factorial(n: u32) -> Stackless<'static, u32> { + if n > 0 { + Stackless::from(()).bind(move |_| factorial(n - 1).map(move |acc| acc * n)) + } else { + 1.into() + } + } + + fn dumb(n: u32) -> Stackless<'static, u32> { + if n > 0 { + Stackless::from(()).bind(move |_| dumb(n - 1).map(move |acc| acc + 1)) + } else { + 0.into() + } + } + + #[test] + fn test_factorial() { + assert_eq!(factorial(10).evaluate(), 3628800); + } + + #[test] + fn test_dumb() { + assert_eq!(dumb(1000).evaluate(), 1000); + } +} diff --git a/src/func/tests.rs b/src/func/tests.rs index 7b4944f..8f247ba 100644 --- a/src/func/tests.rs +++ b/src/func/tests.rs @@ -24,7 +24,7 @@ impl R { } pub trait Eqr: WeakFunctor { - fn eqr<'a, A: PartialEq + Debug>( + fn eqr<'a, A: PartialEq + Debug + 'a>( name: &'a str, left: Self::F<'a, A>, right: Self::F<'a, A>,