#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "alloc")]
type ContinuatioFinalis<F, A> = Box<dyn FnOnce(Box<dyn core::any::Any>) -> Liberior<F, A> + Send>;
#[cfg(feature = "alloc")]
type GradusErasus = Box<dyn FnOnce(Box<dyn core::any::Any>) -> Box<dyn core::any::Any> + Send>;
#[cfg(feature = "alloc")]
pub enum Liberior<F, A> {
Purus(A),
Impurus(LiberiorSuspensio<F, A>),
}
#[cfg(feature = "alloc")]
pub struct LiberiorSuspensio<F, A> {
effect: Box<dyn core::any::Any + Send + Sync>,
first_cont: ContinuatioFinalis<F, A>,
extra_conts: Option<Vec<GradusErasus>>,
_f: core::marker::PhantomData<F>,
}
#[cfg(feature = "alloc")]
impl<F: 'static, A: 'static> Liberior<F, A> {
#[inline]
pub fn purus(a: A) -> Self {
Liberior::Purus(a)
}
#[inline]
pub fn est_purus(&self) -> bool {
matches!(self, Liberior::Purus(_))
}
#[inline]
pub fn est_impurus(&self) -> bool {
matches!(self, Liberior::Impurus(_))
}
#[inline]
pub fn map<B: 'static, G>(self, f: G) -> Liberior<F, B>
where
G: FnOnce(A) -> B + Send + 'static,
{
self.flat_map(move |a| Liberior::purus(f(a)))
}
#[inline]
pub fn flat_map<B: 'static, G>(self, f: G) -> Liberior<F, B>
where
G: FnOnce(A) -> Liberior<F, B> + Send + 'static,
{
match self {
Liberior::Purus(a) => f(a),
Liberior::Impurus(suspensio) => {
let old_first: ContinuatioFinalis<F, A> = suspensio.first_cont;
let erased_old: GradusErasus = Box::new(move |x| {
Box::new(old_first(x)) as Box<dyn core::any::Any>
});
let new_first: ContinuatioFinalis<F, B> = Box::new(move |boxed_any| {
let intermediate: Liberior<F, A> = *boxed_any
.downcast::<Liberior<F, A>>()
.expect("Liberior: type mismatch in flat continuation queue");
match intermediate {
Liberior::Purus(a) => f(a),
Liberior::Impurus(inner) => {
Liberior::Impurus(inner).flat_map(f)
}
}
});
let mut extra = suspensio.extra_conts.unwrap_or_default();
extra.push(erased_old);
Liberior::Impurus(LiberiorSuspensio {
effect: suspensio.effect,
first_cont: new_first,
extra_conts: Some(extra),
_f: core::marker::PhantomData,
})
}
}
}
}
#[cfg(feature = "alloc")]
impl<F: 'static, A: 'static> LiberiorSuspensio<F, A> {
#[inline]
pub fn resume(self, effect_result: Box<dyn core::any::Any>) -> Liberior<F, A> {
let mut value: Box<dyn core::any::Any> = effect_result;
if let Some(extras) = self.extra_conts {
for step in extras {
value = step(value);
}
}
(self.first_cont)(value)
}
#[inline]
pub fn effect(&self) -> &(dyn core::any::Any + Send + Sync) {
&*self.effect
}
#[inline]
pub fn take_effect(self) -> (Box<dyn core::any::Any + Send + Sync>, Self) {
let dummy_effect: Box<dyn core::any::Any + Send + Sync> = Box::new(());
let real_effect = self.effect;
let rest = LiberiorSuspensio {
effect: dummy_effect,
first_cont: self.first_cont,
extra_conts: self.extra_conts,
_f: core::marker::PhantomData,
};
(real_effect, rest)
}
}
#[cfg(feature = "alloc")]
#[inline]
pub fn mitto_liberior<F: 'static, X: Send + Sync + 'static>(effect: X) -> Liberior<F, X> {
Liberior::Impurus(LiberiorSuspensio {
effect: Box::new(effect),
first_cont: Box::new(|x| {
let value = x.downcast::<X>().expect("Type mismatch in Liberior");
Liberior::Purus(*value)
}),
extra_conts: None,
_f: core::marker::PhantomData,
})
}
#[cfg(feature = "alloc")]
#[inline]
pub fn curro_purus_liberior<F, A>(liberior: Liberior<F, A>) -> A {
match liberior {
Liberior::Purus(a) => a,
Liberior::Impurus(_) => panic!("Cannot run impure Liberior as pure"),
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
pub enum StatusOperatio<S> {
Lego(core::marker::PhantomData<S>),
Scribo(S),
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
pub enum LectorOperatio<E> {
Lego(core::marker::PhantomData<E>),
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
pub enum ScriptorOperatio<W> {
Dico(W),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_purus() {
let free: Liberior<(), i32> = Liberior::purus(42);
assert!(free.est_purus());
assert!(!free.est_impurus());
}
#[test]
fn test_map_purus() {
let free: Liberior<(), i32> = Liberior::purus(42);
let mapped = free.map(|x| x * 2);
match mapped {
Liberior::Purus(x) => assert_eq!(x, 84),
_ => panic!("Expected Purus"),
}
}
#[test]
fn test_flat_map_purus() {
let free: Liberior<(), i32> = Liberior::purus(42);
let chained = free.flat_map(|x| Liberior::purus(x + 1));
match chained {
Liberior::Purus(x) => assert_eq!(x, 43),
_ => panic!("Expected Purus"),
}
}
#[test]
fn test_chain_operations() {
let free: Liberior<(), i32> = Liberior::purus(10);
let result = free
.flat_map(|x| Liberior::purus(x + 5))
.flat_map(|x| Liberior::purus(x * 2))
.map(|x| x - 10);
match result {
Liberior::Purus(x) => assert_eq!(x, 20), _ => panic!("Expected Purus"),
}
}
#[test]
fn test_monad_left_identity() {
let a = 42;
let f = |x: i32| Liberior::<(), i32>::purus(x * 2);
let left: Liberior<(), i32> = Liberior::purus(a).flat_map(f);
let right: Liberior<(), i32> = f(a);
match (left, right) {
(Liberior::Purus(l), Liberior::Purus(r)) => assert_eq!(l, r),
_ => panic!("Both should be Purus"),
}
}
#[test]
fn test_monad_right_identity() {
let m: Liberior<(), i32> = Liberior::purus(42);
let result = m.flat_map(Liberior::purus);
match result {
Liberior::Purus(x) => assert_eq!(x, 42),
_ => panic!("Expected Purus"),
}
}
#[test]
fn test_curro_purus() {
let free: Liberior<(), i32> = Liberior::purus(42);
let result = curro_purus_liberior(free);
assert_eq!(result, 42);
}
#[test]
#[should_panic(expected = "Cannot run impure")]
fn test_curro_purus_panics_on_impure() {
let free: Liberior<(), i32> = mitto_liberior::<(), i32>(42);
let _ = curro_purus_liberior(free);
}
fn run_i64(mut prog: Liberior<(), i64>) -> i64 {
loop {
match prog {
Liberior::Purus(a) => return a,
Liberior::Impurus(susp) => {
let (effect, rest) = susp.take_effect();
let val = *effect.downcast::<i64>().expect("test effect is always i64");
prog = rest.resume(Box::new(val));
}
}
}
}
#[test]
fn test_deep_bind_chain_interpretation() {
let mut prog: Liberior<(), i64> = mitto_liberior::<(), i64>(0);
for _ in 0..1000 {
prog = prog.flat_map(|x| Liberior::purus(x + 1));
}
assert_eq!(run_i64(prog), 1000);
}
#[test]
fn test_multi_effect_sequencing() {
let prog: Liberior<(), i64> = mitto_liberior::<(), i64>(10)
.flat_map(|x| mitto_liberior::<(), i64>(20).flat_map(move |y| Liberior::purus(x + y)));
assert_eq!(run_i64(prog), 30);
}
}