extern crate alloc;
use alloc::boxed::Box;
use core::marker::PhantomData;
use super::algebraic::EffectusAlgebraicus;
use super::row_v2::{EffectId, EffectRow, EffectSet, assert_has_effect_type, assert_subrow};
pub struct Sem<R: EffectRow, A> {
run: Box<dyn FnOnce() -> SemResult<R, A> + Send>,
}
pub enum SemResult<R: EffectRow, A> {
Purus(A),
Suspensus(SemSuspension<R, A>),
}
pub struct SemSuspension<R: EffectRow, A> {
operation: Box<dyn core::any::Any + Send>,
continuation: Box<dyn core::any::Any + Send>,
_phantom: PhantomData<(R, A)>,
}
impl<R: EffectRow, A: 'static + Send> Sem<R, A> {
#[inline]
pub fn purus(value: A) -> Self {
Sem {
run: Box::new(move || SemResult::Purus(value)),
}
}
#[inline]
pub fn map<B, F>(self, f: F) -> Sem<R, B>
where
B: 'static + Send,
F: FnOnce(A) -> B + Send + 'static,
{
Sem {
run: Box::new(move || match (self.run)() {
SemResult::Purus(a) => SemResult::Purus(f(a)),
SemResult::Suspensus(susp) => SemResult::Suspensus(SemSuspension {
operation: susp.operation,
continuation: susp.continuation,
_phantom: PhantomData,
}),
}),
}
}
#[inline]
pub fn flat_map<B, F>(self, f: F) -> Sem<R, B>
where
B: 'static + Send,
F: FnOnce(A) -> Sem<R, B> + Send + 'static,
{
Sem {
run: Box::new(move || match (self.run)() {
SemResult::Purus(a) => (f(a).run)(),
SemResult::Suspensus(susp) => SemResult::Suspensus(SemSuspension {
operation: susp.operation,
continuation: susp.continuation,
_phantom: PhantomData,
}),
}),
}
}
#[inline]
pub fn map2<B, C, F>(self, other: Sem<R, B>, f: F) -> Sem<R, C>
where
B: 'static + Send,
C: 'static + Send,
F: FnOnce(A, B) -> C + Send + 'static,
{
self.flat_map(move |a| other.map(move |b| f(a, b)))
}
}
#[inline]
pub fn run_sem<A: 'static + Send>(sem: Sem<EffectSet<0>, A>) -> A {
match (sem.run)() {
SemResult::Purus(a) => a,
SemResult::Suspensus(_) => {
panic!("Pure Sem computation suspended - this is a bug")
}
}
}
pub fn subsume<E, RIn, ROut, A>(sem: Sem<RIn, A>) -> Sem<ROut, A>
where
E: EffectusAlgebraicus + EffectId + Send + 'static,
RIn: EffectRow,
ROut: EffectRow,
A: 'static + Send,
{
assert_has_effect_type::<RIn, E>();
assert_has_effect_type::<ROut, E>();
Sem {
run: Box::new(move || match (sem.run)() {
SemResult::Purus(a) => SemResult::Purus(a),
SemResult::Suspensus(susp) => SemResult::Suspensus(SemSuspension {
operation: susp.operation,
continuation: susp.continuation,
_phantom: PhantomData,
}),
}),
}
}
pub fn embed<R1, const SUPER: u128, A>(sem: Sem<R1, A>) -> Sem<EffectSet<SUPER>, A>
where
R1: EffectRow,
A: 'static + Send,
{
assert_subrow::<R1, SUPER>();
Sem {
run: Box::new(move || match (sem.run)() {
SemResult::Purus(a) => SemResult::Purus(a),
SemResult::Suspensus(susp) => SemResult::Suspensus(SemSuspension {
operation: susp.operation,
continuation: susp.continuation,
_phantom: PhantomData,
}),
}),
}
}
#[inline]
pub fn send_sem<E, R>(op: E) -> Sem<R, E::Result>
where
E: EffectusAlgebraicus + EffectId + Send + 'static,
E::Result: Send + 'static,
R: EffectRow,
{
assert_has_effect_type::<R, E>();
Sem {
run: Box::new(move || {
SemResult::Suspensus(SemSuspension {
operation: Box::new(op),
continuation: Box::new(()),
_phantom: PhantomData,
})
}),
}
}
#[inline]
pub fn pure_sem<R: EffectRow, A: 'static + Send>(a: A) -> Sem<R, A> {
Sem::purus(a)
}
#[inline]
pub fn then_sem<R: EffectRow, A: 'static + Send, B: 'static + Send>(
first: Sem<R, A>,
second: Sem<R, B>,
) -> Sem<R, B> {
first.flat_map(move |_| second)
}
pub fn run_io<A: 'static + Send>(sem: Sem<super::row_v2::IoRow, A>) -> A {
match (sem.run)() {
SemResult::Purus(a) => a,
SemResult::Suspensus(_) => {
panic!("IO effect not handled - requires runtime support")
}
}
}
#[inline]
pub fn raise<E, R>(op: E) -> Sem<R, E::Result>
where
E: EffectusAlgebraicus + EffectId + Send + 'static,
E::Result: Send + 'static,
R: EffectRow,
{
send_sem(op)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sem_purus() {
let sem: Sem<EffectSet<0>, i32> = Sem::purus(42);
let result = run_sem(sem);
assert_eq!(result, 42);
}
#[test]
fn test_sem_map() {
let sem: Sem<EffectSet<0>, i32> = Sem::purus(21);
let doubled = sem.map(|x| x * 2);
assert_eq!(run_sem(doubled), 42);
}
#[test]
fn test_sem_flat_map() {
let sem: Sem<EffectSet<0>, i32> = Sem::purus(20);
let result = sem.flat_map(|x| Sem::purus(x + 22));
assert_eq!(run_sem(result), 42);
}
#[test]
fn test_sem_map2() {
let sem1: Sem<EffectSet<0>, i32> = Sem::purus(20);
let sem2: Sem<EffectSet<0>, i32> = Sem::purus(22);
let sum = sem1.map2(sem2, |a, b| a + b);
assert_eq!(run_sem(sum), 42);
}
#[test]
fn test_pure_sem() {
let sem: Sem<EffectSet<0>, &str> = pure_sem("hello");
assert_eq!(run_sem(sem), "hello");
}
#[test]
fn test_then_sem() {
let first: Sem<EffectSet<0>, i32> = Sem::purus(1);
let second: Sem<EffectSet<0>, &str> = Sem::purus("result");
let result = then_sem(first, second);
assert_eq!(run_sem(result), "result");
}
#[test]
fn test_embed() {
let pure: Sem<EffectSet<0>, i32> = Sem::purus(42);
use super::super::row_v2::IoRow;
let embedded: Sem<IoRow, i32> = embed(pure);
match (embedded.run)() {
SemResult::Purus(a) => assert_eq!(a, 42),
SemResult::Suspensus(_) => panic!("Should be pure"),
}
}
}