use super::row_v2::{EffectRow, EffectSet, assert_subrow};
use alloc::boxed::Box;
use core::marker::PhantomData;
pub struct Computatio<R: EffectRow, A> {
inner: Box<dyn FnOnce() -> A + Send>,
_row: PhantomData<R>,
}
impl<R: EffectRow, A> Computatio<R, A> {
pub fn from_thunk<F>(f: F) -> Self
where
F: FnOnce() -> A + Send + 'static,
{
Computatio {
inner: Box::new(f),
_row: PhantomData,
}
}
#[inline]
pub fn run(self) -> A {
(self.inner)()
}
}
impl<A: Send + 'static> Computatio<EffectSet<0>, A> {
#[inline]
pub fn purus(value: A) -> Self {
Computatio {
inner: Box::new(move || value),
_row: PhantomData,
}
}
}
impl<const MASK: u128, A: Send + 'static> Computatio<EffectSet<MASK>, A> {
pub fn effectus<F>(f: F) -> Self
where
F: FnOnce() -> A + Send + 'static,
{
Computatio {
inner: Box::new(f),
_row: PhantomData,
}
}
}
impl<R: EffectRow, A: Send + 'static> Computatio<R, A> {
pub fn map<B: Send + 'static, F>(self, f: F) -> Computatio<R, B>
where
F: FnOnce(A) -> B + Send + 'static,
{
let inner = self.inner;
Computatio {
inner: Box::new(move || f((inner)())),
_row: PhantomData,
}
}
pub fn ap<B: Send + 'static, F: FnOnce(A) -> B + Send + 'static>(
self,
func: Computatio<R, F>,
) -> Computatio<R, B> {
let inner_self = self.inner;
let inner_func = func.inner;
Computatio {
inner: Box::new(move || {
let f = (inner_func)();
let a = (inner_self)();
f(a)
}),
_row: PhantomData,
}
}
pub fn then<B: Send + 'static>(self, other: Computatio<R, B>) -> Computatio<R, B> {
let inner_self = self.inner;
let inner_other = other.inner;
Computatio {
inner: Box::new(move || {
let _ = (inner_self)();
(inner_other)()
}),
_row: PhantomData,
}
}
pub fn before<B: Send + 'static>(self, other: Computatio<R, B>) -> Computatio<R, A> {
let inner_self = self.inner;
let inner_other = other.inner;
Computatio {
inner: Box::new(move || {
let a = (inner_self)();
let _ = (inner_other)();
a
}),
_row: PhantomData,
}
}
pub fn bind<B: Send + 'static, F>(self, f: F) -> Computatio<R, B>
where
F: FnOnce(A) -> Computatio<R, B> + Send + 'static,
{
let inner = self.inner;
Computatio {
inner: Box::new(move || {
let a = (inner)();
f(a).run()
}),
_row: PhantomData,
}
}
pub fn flatten(self) -> Computatio<R, A>
where
A: Into<Computatio<R, A>>,
{
self.bind(core::convert::Into::into)
}
pub fn lift<const SUPER: u128>(self) -> Computatio<EffectSet<SUPER>, A> {
assert_subrow::<R, SUPER>();
Computatio {
inner: self.inner,
_row: PhantomData,
}
}
pub fn zip<B: Send + 'static>(self, other: Computatio<R, B>) -> Computatio<R, (A, B)> {
let inner_self = self.inner;
let inner_other = other.inner;
Computatio {
inner: Box::new(move || {
let a = (inner_self)();
let b = (inner_other)();
(a, b)
}),
_row: PhantomData,
}
}
pub fn zip_with<B: Send + 'static, C: Send + 'static, F>(
self,
other: Computatio<R, B>,
f: F,
) -> Computatio<R, C>
where
F: FnOnce(A, B) -> C + Send + 'static,
{
self.zip(other).map(|(a, b)| f(a, b))
}
}
pub fn combine_effectus<
const M1: u128,
const M2: u128,
const MOUT: u128,
A: Send + 'static,
B: Send + 'static,
>(
comp1: Computatio<EffectSet<M1>, A>,
comp2: Computatio<EffectSet<M2>, B>,
) -> Computatio<EffectSet<MOUT>, (A, B)> {
assert_subrow::<EffectSet<M1>, MOUT>();
assert_subrow::<EffectSet<M2>, MOUT>();
let inner1 = comp1.inner;
let inner2 = comp2.inner;
Computatio {
inner: Box::new(move || {
let a = (inner1)();
let b = (inner2)();
(a, b)
}),
_row: PhantomData,
}
}
pub fn sequence<R: EffectRow, A: Send + 'static>(
computations: alloc::vec::Vec<Computatio<R, A>>,
) -> Computatio<R, alloc::vec::Vec<A>> {
Computatio {
inner: Box::new(move || {
let mut results = alloc::vec::Vec::with_capacity(computations.len());
for c in computations {
results.push(c.run());
}
results
}),
_row: PhantomData,
}
}
pub fn traverse<R: EffectRow, A, B: Send + 'static, F>(
items: alloc::vec::Vec<A>,
f: F,
) -> Computatio<R, alloc::vec::Vec<B>>
where
F: Fn(A) -> Computatio<R, B> + Send + 'static,
A: Send + 'static,
{
let computations: alloc::vec::Vec<Computatio<R, B>> = items.into_iter().map(&f).collect();
sequence(computations)
}
#[inline]
pub fn perform<const MASK: u128, A: Send + 'static, F>(f: F) -> Computatio<EffectSet<MASK>, A>
where
F: FnOnce() -> A + Send + 'static,
{
Computatio::from_thunk(f)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::effects::row_v2::{IoRow, IoStateRow, StateRow};
use alloc::string::{String, ToString};
use alloc::vec;
#[test]
fn test_purus() {
let comp = Computatio::purus(42);
assert_eq!(comp.run(), 42);
}
#[test]
fn test_map() {
let comp = Computatio::purus(21);
let doubled = comp.map(|x| x * 2);
assert_eq!(doubled.run(), 42);
}
#[test]
fn test_bind() {
let comp = Computatio::purus(21);
let result = comp.bind(|x| Computatio::purus(x * 2));
assert_eq!(result.run(), 42);
}
#[test]
fn test_then() {
let first = Computatio::purus("ignored");
let second = Computatio::purus(42);
let result = first.then(second);
assert_eq!(result.run(), 42);
}
#[test]
fn test_before() {
let first = Computatio::purus(42);
let second = Computatio::purus("ignored");
let result = first.before(second);
assert_eq!(result.run(), 42);
}
#[test]
fn test_zip() {
let a = Computatio::purus(1);
let b = Computatio::purus(2);
let zipped = a.zip(b);
assert_eq!(zipped.run(), (1, 2));
}
#[test]
fn test_zip_with() {
let a = Computatio::purus(10);
let b = Computatio::purus(20);
let sum = a.zip_with(b, |x, y| x + y);
assert_eq!(sum.run(), 30);
}
#[test]
fn test_sequence() {
let comps = vec![
Computatio::purus(1),
Computatio::purus(2),
Computatio::purus(3),
];
let sequenced = sequence(comps);
assert_eq!(sequenced.run(), vec![1, 2, 3]);
}
#[test]
fn test_traverse() {
let values = vec![1, 2, 3];
let result = traverse(values, |x| Computatio::purus(x * 2));
assert_eq!(result.run(), vec![2, 4, 6]);
}
#[test]
fn test_effectus() {
let io: Computatio<IoRow, String> = Computatio::effectus(|| "Hello".to_string());
assert_eq!(io.run(), "Hello");
}
#[test]
fn test_lift() {
let pure: Computatio<EffectSet<0>, i32> = Computatio::purus(42);
let lifted: Computatio<IoRow, i32> = pure.lift();
assert_eq!(lifted.run(), 42);
}
#[test]
fn test_combine_effectus() {
let io: Computatio<IoRow, i32> = Computatio::effectus(|| 1);
let state: Computatio<StateRow, i32> = Computatio::from_thunk(|| 2);
let combined: Computatio<IoStateRow, _> = combine_effectus(io, state);
assert_eq!(combined.run(), (1, 2));
}
#[test]
fn test_monad_laws_left_identity() {
let a = 42;
let f = |x: i32| Computatio::purus(x * 2);
let left = Computatio::purus(a).bind(f);
let right = f(a);
assert_eq!(left.run(), right.run());
}
#[test]
fn test_monad_laws_right_identity() {
let m = Computatio::purus(42);
let m_clone = Computatio::purus(42);
let result = m.bind(Computatio::purus);
assert_eq!(result.run(), m_clone.run());
}
#[test]
fn test_monad_laws_associativity() {
let f = |x: i32| Computatio::purus(x + 1);
let g = |x: i32| Computatio::purus(x * 2);
let m1 = Computatio::purus(5);
let m2 = Computatio::purus(5);
let left = m1.bind(f).bind(g);
let right = m2.bind(move |x| Computatio::purus(x + 1).bind(g));
assert_eq!(left.run(), right.run());
}
#[test]
fn test_chaining() {
let result = Computatio::purus(5)
.bind(|x| Computatio::purus(x + 1))
.bind(|x| Computatio::purus(x * 2))
.map(|x: i32| x.to_string())
.run();
assert_eq!(result, "12");
}
#[test]
fn test_perform() {
let comp: Computatio<IoRow, _> = perform(|| 42);
assert_eq!(comp.run(), 42);
}
}