#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use super::liber::Liber;
use crate::hints::likely;
use crate::typeclasses::hkt::FunctorHKT;
pub const INLINE_CAPACITY: usize = 4;
#[cfg(feature = "alloc")]
pub struct AcervusParvus<T> {
storage: AcervusStorage<T>,
}
#[cfg(feature = "alloc")]
enum AcervusStorage<T> {
Inline {
data: [Option<T>; INLINE_CAPACITY],
len: usize,
},
Heap(Vec<T>),
}
#[cfg(feature = "alloc")]
impl<T> AcervusParvus<T> {
#[inline(always)]
pub const fn empty() -> Self {
AcervusParvus {
storage: AcervusStorage::Inline {
data: [const { None }; INLINE_CAPACITY],
len: 0,
},
}
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline(always)]
pub fn len(&self) -> usize {
match &self.storage {
AcervusStorage::Inline { len, .. } => *len,
AcervusStorage::Heap(vec) => vec.len(),
}
}
#[inline(always)]
pub fn is_inline(&self) -> bool {
matches!(self.storage, AcervusStorage::Inline { .. })
}
#[inline(always)]
pub fn push(&mut self, item: T) {
match &mut self.storage {
AcervusStorage::Inline { data, len } => {
if likely(*len < INLINE_CAPACITY) {
data[*len] = Some(item);
*len += 1;
} else {
self.spill_to_heap(item);
}
}
AcervusStorage::Heap(vec) => {
vec.push(item);
}
}
}
#[cold]
#[inline(never)]
fn spill_to_heap(&mut self, item: T) {
if let AcervusStorage::Inline { data, .. } = &mut self.storage {
let mut vec = Vec::with_capacity(INLINE_CAPACITY * 2);
for slot in data.iter_mut() {
if let Some(val) = slot.take() {
vec.push(val);
}
}
vec.push(item);
self.storage = AcervusStorage::Heap(vec);
}
}
#[inline(always)]
pub fn pop(&mut self) -> Option<T> {
match &mut self.storage {
AcervusStorage::Inline { data, len } => {
if likely(*len > 0) {
*len -= 1;
data[*len].take()
} else {
None
}
}
AcervusStorage::Heap(vec) => vec.pop(),
}
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &T> {
AcervusIter {
stack: self,
index: 0,
}
}
}
#[cfg(feature = "alloc")]
impl<T> IntoIterator for AcervusParvus<T> {
type Item = T;
type IntoIter = AcervusIntoIter<T>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
AcervusIntoIter {
stack: self,
inline_cursor: 0,
}
}
}
#[cfg(feature = "alloc")]
impl<T> Default for AcervusParvus<T> {
#[inline(always)]
fn default() -> Self {
Self::empty()
}
}
#[cfg(feature = "alloc")]
struct AcervusIter<'a, T> {
stack: &'a AcervusParvus<T>,
index: usize,
}
#[cfg(feature = "alloc")]
impl<'a, T> Iterator for AcervusIter<'a, T> {
type Item = &'a T;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
match &self.stack.storage {
AcervusStorage::Inline { data, len } => {
if self.index < *len {
let item = data[self.index].as_ref();
self.index += 1;
item
} else {
None
}
}
AcervusStorage::Heap(vec) => {
if self.index < vec.len() {
let item = &vec[self.index];
self.index += 1;
Some(item)
} else {
None
}
}
}
}
}
#[cfg(feature = "alloc")]
pub struct AcervusIntoIter<T> {
stack: AcervusParvus<T>,
inline_cursor: usize,
}
#[cfg(feature = "alloc")]
impl<T> Iterator for AcervusIntoIter<T> {
type Item = T;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
match &mut self.stack.storage {
AcervusStorage::Inline { data, len } => {
if self.inline_cursor >= *len {
return None;
}
let item = data[self.inline_cursor].take();
self.inline_cursor += 1;
item
}
AcervusStorage::Heap(vec) => {
if vec.is_empty() {
None
} else {
if self.inline_cursor == 0 {
vec.reverse();
self.inline_cursor = 1;
}
vec.pop()
}
}
}
}
}
#[cfg(feature = "alloc")]
pub enum LiberEcclesia<F: FunctorHKT + 'static, A: 'static> {
Purus(A),
Suspensus {
effect: Box<dyn core::any::Any + Send + Sync>,
stack: ContinuatioStack<F>,
},
}
#[cfg(feature = "alloc")]
pub struct ContinuatioStack<F: FunctorHKT + 'static> {
steps: AcervusParvus<Box<dyn ContinuatioStep<F> + Send + Sync>>,
}
#[cfg(feature = "alloc")]
pub trait ContinuatioStep<F: FunctorHKT>: Send + Sync {
fn applica(&self, value: Box<dyn core::any::Any + Send + Sync>) -> LiberEcclesiaErased<F>;
}
#[cfg(feature = "alloc")]
pub enum LiberEcclesiaErased<F: FunctorHKT + 'static> {
Purus(Box<dyn core::any::Any + Send + Sync>),
Suspensus {
effect: Box<dyn core::any::Any + Send + Sync>,
stack: ContinuatioStack<F>,
},
}
#[cfg(feature = "alloc")]
impl<F: FunctorHKT + 'static> ContinuatioStack<F> {
#[inline(always)]
pub fn empty() -> Self {
ContinuatioStack {
steps: AcervusParvus::empty(),
}
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.steps.is_empty()
}
#[inline(always)]
pub fn len(&self) -> usize {
self.steps.len()
}
#[inline(always)]
pub fn is_inline(&self) -> bool {
self.steps.is_inline()
}
#[inline(always)]
fn push(&mut self, step: Box<dyn ContinuatioStep<F> + Send + Sync>) {
self.steps.push(step);
}
}
#[cfg(feature = "alloc")]
struct ContinuatioStepImpl<F, A, B, G>
where
F: FunctorHKT + 'static,
A: Send + Sync + 'static,
B: Send + Sync + 'static,
G: Fn(A) -> LiberEcclesia<F, B> + Send + Sync,
{
f: G,
_phantom: core::marker::PhantomData<fn(A) -> B>,
}
#[cfg(feature = "alloc")]
impl<F, A, B, G> ContinuatioStep<F> for ContinuatioStepImpl<F, A, B, G>
where
F: FunctorHKT + 'static,
A: Send + Sync + 'static,
B: Send + Sync + 'static,
G: Fn(A) -> LiberEcclesia<F, B> + Send + Sync,
{
fn applica(&self, value: Box<dyn core::any::Any + Send + Sync>) -> LiberEcclesiaErased<F> {
let a: A = *value.downcast().expect("Type mismatch in continuation");
let result = (self.f)(a);
result.erase()
}
}
#[cfg(feature = "alloc")]
impl<F: FunctorHKT + 'static, A: Send + Sync + 'static> LiberEcclesia<F, A> {
#[inline]
fn erase(self) -> LiberEcclesiaErased<F> {
match self {
LiberEcclesia::Purus(a) => LiberEcclesiaErased::Purus(Box::new(a)),
LiberEcclesia::Suspensus { effect, stack } => {
LiberEcclesiaErased::Suspensus { effect, stack }
}
}
}
}
#[cfg(feature = "alloc")]
impl<F: FunctorHKT + 'static, A: 'static> LiberEcclesia<F, A> {
#[inline(always)]
pub fn purus(a: A) -> Self {
LiberEcclesia::Purus(a)
}
#[inline(always)]
pub fn est_purus(&self) -> bool {
matches!(self, LiberEcclesia::Purus(_))
}
#[inline(always)]
pub fn est_suspensus(&self) -> bool {
matches!(self, LiberEcclesia::Suspensus { .. })
}
#[inline(always)]
pub fn extract_pure(self) -> Option<A> {
match self {
LiberEcclesia::Purus(a) => Some(a),
LiberEcclesia::Suspensus { .. } => None,
}
}
#[inline(always)]
pub fn run_pure(self) -> A {
match self {
LiberEcclesia::Purus(a) => a,
LiberEcclesia::Suspensus { .. } => {
panic!("Cannot run impure LiberEcclesia as pure")
}
}
}
}
#[cfg(feature = "alloc")]
impl<F: FunctorHKT + 'static, A: Send + Sync + 'static> LiberEcclesia<F, A> {
#[inline(always)]
pub fn map<B: Send + Sync + 'static, G>(self, f: G) -> LiberEcclesia<F, B>
where
G: Fn(A) -> B + Send + Sync + 'static,
{
self.flat_map(move |a| LiberEcclesia::purus(f(a)))
}
#[inline(always)]
pub fn flat_map<B: Send + Sync + 'static, G>(self, f: G) -> LiberEcclesia<F, B>
where
G: Fn(A) -> LiberEcclesia<F, B> + Send + Sync + 'static,
{
match self {
LiberEcclesia::Purus(a) => f(a),
LiberEcclesia::Suspensus { effect, mut stack } => {
stack.push(Box::new(ContinuatioStepImpl {
f,
_phantom: core::marker::PhantomData,
}));
LiberEcclesia::Suspensus { effect, stack }
}
}
}
#[inline(always)]
pub fn lift_f<X: Send + Sync + 'static>(fx: F::Target<X>) -> LiberEcclesia<F, X>
where
F::Target<X>: Send + Sync,
{
LiberEcclesia::Suspensus {
effect: Box::new(fx),
stack: ContinuatioStack::empty(),
}
}
}
#[cfg(feature = "alloc")]
pub struct CodOption<A> {
inner: CodOptionInner<A>,
}
#[cfg(feature = "alloc")]
type CodOptionKont<'a> = dyn Fn(Box<dyn core::any::Any>) -> Option<Box<dyn core::any::Any>> + 'a;
#[cfg(feature = "alloc")]
type CodOptionRun =
Box<dyn for<'a> FnOnce(&'a CodOptionKont<'a>) -> Option<Box<dyn core::any::Any>> + Send>;
#[cfg(feature = "alloc")]
enum CodOptionInner<A> {
Pure(A),
Composed(CodOptionRun),
}
#[cfg(feature = "alloc")]
impl<A: Clone + Send + 'static> CodOption<A> {
#[inline(always)]
pub fn purus(a: A) -> Self {
CodOption {
inner: CodOptionInner::Pure(a),
}
}
#[inline(always)]
pub fn flat_map<B: Clone + Send + 'static, G>(self, f: G) -> CodOption<B>
where
G: Fn(A) -> CodOption<B> + Send + 'static,
{
match self.inner {
CodOptionInner::Pure(a) => f(a),
CodOptionInner::Composed(run) => CodOption {
inner: CodOptionInner::Composed(Box::new(move |k| {
run(&|any_val| {
let val: A = *any_val.downcast().ok()?;
let next = f(val);
match next.inner {
CodOptionInner::Pure(b) => k(Box::new(b)),
CodOptionInner::Composed(next_run) => next_run(k),
}
})
})),
},
}
}
#[inline(always)]
pub fn map<B: Clone + Send + 'static, G>(self, f: G) -> CodOption<B>
where
G: Fn(A) -> B + Send + 'static,
{
self.flat_map(move |a| CodOption::purus(f(a)))
}
#[inline(always)]
pub fn lower(self) -> Option<A> {
match self.inner {
CodOptionInner::Pure(a) => Some(a),
CodOptionInner::Composed(run) => run(&|any_val| Some(any_val))
.and_then(|boxed| boxed.downcast::<A>().ok())
.map(|boxed| *boxed),
}
}
#[inline(always)]
pub fn none() -> Self {
CodOption {
inner: CodOptionInner::Composed(Box::new(|_k| None)),
}
}
#[inline(always)]
pub fn from_option(opt: Option<A>) -> Self {
match opt {
Some(a) => Self::purus(a),
None => Self::none(),
}
}
#[inline(always)]
pub fn filter<P>(self, predicate: P) -> Self
where
P: Fn(&A) -> bool + Send + 'static,
{
self.flat_map(move |a| {
if predicate(&a) {
CodOption::purus(a)
} else {
CodOption::none()
}
})
}
#[inline(always)]
pub fn get_or_else<F>(self, default: F) -> A
where
F: FnOnce() -> A,
{
self.lower().unwrap_or_else(default)
}
#[inline(always)]
pub fn map2<B, C, F>(self, other: CodOption<B>, f: F) -> CodOption<C>
where
B: Clone + Send + 'static,
C: Clone + Send + 'static,
F: Fn(A, B) -> C + Send + 'static + Clone,
{
self.flat_map(move |a| {
let f = f.clone();
other.clone().map(move |b| f(a.clone(), b))
})
}
}
#[cfg(feature = "alloc")]
impl<A: Clone + Send + 'static> Clone for CodOption<A> {
fn clone(&self) -> Self {
match &self.inner {
CodOptionInner::Pure(a) => CodOption::purus(a.clone()),
CodOptionInner::Composed(_) => {
panic!("Cannot clone composed CodOption")
}
}
}
}
#[cfg(feature = "alloc")]
pub struct CodResult<A, E> {
inner: CodResultInner<A, E>,
}
#[cfg(feature = "alloc")]
enum CodResultInner<A, E> {
Pure(A),
Error(E),
}
#[cfg(feature = "alloc")]
impl<A: Clone + Send + 'static, E: Clone + Send + 'static> CodResult<A, E> {
#[inline(always)]
pub fn ok(a: A) -> Self {
CodResult {
inner: CodResultInner::Pure(a),
}
}
#[inline(always)]
pub fn err(e: E) -> Self {
CodResult {
inner: CodResultInner::Error(e),
}
}
#[inline(always)]
pub fn flat_map<B: Clone + Send + 'static, G>(self, f: G) -> CodResult<B, E>
where
G: Fn(A) -> CodResult<B, E> + Send + 'static,
{
match self.inner {
CodResultInner::Pure(a) => f(a),
CodResultInner::Error(e) => CodResult {
inner: CodResultInner::Error(e),
},
}
}
#[inline(always)]
pub fn map<B: Clone + Send + 'static, G>(self, f: G) -> CodResult<B, E>
where
G: Fn(A) -> B + Send + 'static,
{
self.flat_map(move |a| CodResult::ok(f(a)))
}
#[inline(always)]
pub fn lower(self) -> Result<A, E> {
match self.inner {
CodResultInner::Pure(a) => Ok(a),
CodResultInner::Error(e) => Err(e),
}
}
#[inline(always)]
pub fn from_result(result: Result<A, E>) -> Self {
match result {
Ok(a) => Self::ok(a),
Err(e) => Self::err(e),
}
}
#[inline(always)]
pub fn map_err<E2: Clone + Send + 'static, G>(self, f: G) -> CodResult<A, E2>
where
G: Fn(E) -> E2 + Send + 'static,
{
match self.inner {
CodResultInner::Pure(a) => CodResult::ok(a),
CodResultInner::Error(e) => CodResult::err(f(e)),
}
}
#[inline(always)]
pub fn unwrap_or_else<F>(self, f: F) -> A
where
F: FnOnce(E) -> A,
{
self.lower().unwrap_or_else(f)
}
#[inline(always)]
pub fn and_then<B: Clone + Send + 'static, F>(self, f: F) -> CodResult<B, E>
where
F: Fn(A) -> CodResult<B, E> + Send + 'static,
{
self.flat_map(f)
}
}
#[cfg(feature = "alloc")]
pub struct CodIdentity<A> {
value: A,
}
#[cfg(feature = "alloc")]
impl<A: Clone + 'static> CodIdentity<A> {
#[inline(always)]
pub fn purus(a: A) -> Self {
CodIdentity { value: a }
}
#[inline(always)]
pub fn flat_map<B: Clone + 'static, G>(self, f: G) -> CodIdentity<B>
where
G: FnOnce(A) -> CodIdentity<B>,
{
f(self.value)
}
#[inline(always)]
pub fn map<B: Clone + 'static, G>(self, f: G) -> CodIdentity<B>
where
G: FnOnce(A) -> B,
{
CodIdentity {
value: f(self.value),
}
}
#[inline(always)]
pub fn run(self) -> A {
self.value
}
}
#[cfg(feature = "alloc")]
pub fn ad_ecclesiam<F, A>(liber: Liber<F, A>) -> LiberEcclesia<F, A>
where
F: FunctorHKT + 'static,
A: Send + Sync + 'static,
F::Target<LiberEcclesia<F, A>>: Send + Sync,
LiberEcclesia<F, A>: Send + Sync,
{
match liber {
Liber::Purus(a) => LiberEcclesia::purus(a),
Liber::Suspensus(fa) => {
LiberEcclesia::<F, A>::lift_f(F::map(*fa, ad_ecclesiam)).flat_map(|inner| inner)
}
}
}
#[cfg(test)]
mod tests {
use super::super::nat::OptionFWitness;
use super::*;
use alloc::vec;
use alloc::vec::Vec;
#[test]
fn test_purus() {
let free: LiberEcclesia<OptionFWitness, i32> = LiberEcclesia::purus(42);
assert!(free.est_purus());
let extracted = free.extract_pure();
assert_eq!(extracted, Some(42));
}
#[test]
fn test_flat_map_purus() {
let free: LiberEcclesia<OptionFWitness, i32> = LiberEcclesia::purus(42);
let chained = free.flat_map(|x| LiberEcclesia::purus(x + 1));
let extracted = chained.extract_pure();
assert_eq!(extracted, Some(43));
}
#[test]
fn test_map_purus() {
let free: LiberEcclesia<OptionFWitness, i32> = LiberEcclesia::purus(42);
let mapped = free.map(|x| x * 2);
let extracted = mapped.extract_pure();
assert_eq!(extracted, Some(84));
}
#[test]
fn test_chain_many_operations() {
let free: LiberEcclesia<OptionFWitness, i32> = LiberEcclesia::purus(0);
let result = (0..100).fold(free, |acc, i| {
acc.flat_map(move |x| LiberEcclesia::purus(x + i))
});
let extracted = result.extract_pure();
assert_eq!(extracted, Some(4950));
}
#[test]
fn test_monad_left_identity() {
let a = 42;
let f = |x: i32| LiberEcclesia::<OptionFWitness, i32>::purus(x * 2);
let left = LiberEcclesia::purus(a).flat_map(f);
let right = f(a);
assert_eq!(left.extract_pure(), right.extract_pure());
}
#[test]
fn test_monad_right_identity() {
let m: LiberEcclesia<OptionFWitness, i32> = LiberEcclesia::purus(42);
let result = m.flat_map(LiberEcclesia::purus);
assert_eq!(result.extract_pure(), Some(42));
}
#[test]
fn test_cod_option_purus() {
let cod: CodOption<i32> = CodOption::purus(42);
let result = cod.lower();
assert_eq!(result, Some(42));
}
#[test]
fn test_cod_option_flat_map() {
let cod: CodOption<i32> = CodOption::purus(42);
let chained = cod.flat_map(|x| CodOption::purus(x + 1));
let result = chained.lower();
assert_eq!(result, Some(43));
}
#[test]
fn test_cod_option_map() {
let cod: CodOption<i32> = CodOption::purus(42);
let mapped = cod.map(|x| x * 2);
let result = mapped.lower();
assert_eq!(result, Some(84));
}
#[test]
fn test_cod_option_chain_many() {
let cod: CodOption<i32> = CodOption::purus(0);
let result = (0..100).fold(cod, |acc, i| acc.flat_map(move |x| CodOption::purus(x + i)));
let lowered = result.lower();
assert_eq!(lowered, Some(4950));
}
#[test]
fn test_cod_result_ok() {
let cod: CodResult<i32, &str> = CodResult::ok(42);
let result = cod.lower();
assert_eq!(result, Ok(42));
}
#[test]
fn test_cod_result_err() {
let cod: CodResult<i32, &str> = CodResult::err("error");
let result = cod.lower();
assert_eq!(result, Err("error"));
}
#[test]
fn test_cod_result_flat_map() {
let cod: CodResult<i32, &str> = CodResult::ok(42);
let chained = cod.flat_map(|x| CodResult::ok(x + 1));
let result = chained.lower();
assert_eq!(result, Ok(43));
}
#[test]
fn test_cod_result_chain_many() {
let cod: CodResult<i32, &str> = CodResult::ok(0);
let result = (0..100).fold(cod, |acc, i| acc.flat_map(move |x| CodResult::ok(x + i)));
let lowered = result.lower();
assert_eq!(lowered, Ok(4950));
}
#[test]
fn test_cod_identity_purus() {
let cod: CodIdentity<i32> = CodIdentity::purus(42);
let result = cod.run();
assert_eq!(result, 42);
}
#[test]
fn test_cod_identity_flat_map() {
let cod: CodIdentity<i32> = CodIdentity::purus(42);
let chained = cod.flat_map(|x| CodIdentity::purus(x + 1));
let result = chained.run();
assert_eq!(result, 43);
}
#[test]
fn test_cod_identity_chain_many() {
let cod: CodIdentity<i32> = CodIdentity::purus(0);
let result = (0..100).fold(cod, |acc, i| {
acc.flat_map(move |x| CodIdentity::purus(x + i))
});
let lowered = result.run();
assert_eq!(lowered, 4950);
}
#[test]
fn test_cod_identity_map() {
let cod: CodIdentity<i32> = CodIdentity::purus(42);
let result = cod
.flat_map(|x| CodIdentity::purus(x + 8))
.map(|x| x * 2)
.run();
assert_eq!(result, 100);
}
#[test]
fn test_acervus_parvus_empty() {
let stack: AcervusParvus<i32> = AcervusParvus::empty();
assert!(stack.is_empty());
assert_eq!(stack.len(), 0);
assert!(stack.is_inline());
}
#[test]
fn test_acervus_parvus_push_inline() {
let mut stack: AcervusParvus<i32> = AcervusParvus::empty();
for i in 0..INLINE_CAPACITY {
stack.push(i as i32);
assert!(stack.is_inline(), "Should remain inline at {i}");
}
assert_eq!(stack.len(), INLINE_CAPACITY);
assert!(stack.is_inline());
}
#[test]
fn test_acervus_parvus_push_spill() {
let mut stack: AcervusParvus<i32> = AcervusParvus::empty();
for i in 0..=INLINE_CAPACITY {
stack.push(i as i32);
}
assert_eq!(stack.len(), INLINE_CAPACITY + 1);
assert!(!stack.is_inline(), "Should have spilled to heap");
}
#[test]
fn test_acervus_parvus_pop() {
let mut stack: AcervusParvus<i32> = AcervusParvus::empty();
stack.push(1);
stack.push(2);
stack.push(3);
assert_eq!(stack.pop(), Some(3));
assert_eq!(stack.pop(), Some(2));
assert_eq!(stack.pop(), Some(1));
assert_eq!(stack.pop(), None);
}
#[test]
fn test_acervus_parvus_iter() {
let mut stack: AcervusParvus<i32> = AcervusParvus::empty();
stack.push(1);
stack.push(2);
stack.push(3);
let collected: Vec<i32> = stack.iter().copied().collect();
assert_eq!(collected, vec![1, 2, 3]);
}
#[test]
fn test_acervus_parvus_into_iter() {
let mut stack: AcervusParvus<i32> = AcervusParvus::empty();
stack.push(1);
stack.push(2);
stack.push(3);
let collected: Vec<i32> = stack.into_iter().collect();
assert_eq!(collected, vec![1, 2, 3]);
}
#[test]
fn test_continuation_stack_inline() {
let stack: ContinuatioStack<OptionFWitness> = ContinuatioStack::empty();
assert!(stack.is_empty());
assert!(stack.is_inline());
}
#[test]
fn test_liber_ecclesia_inline_storage() {
let free: LiberEcclesia<OptionFWitness, i32> = LiberEcclesia::purus(0);
let result = (0..INLINE_CAPACITY).fold(free, |acc, i| {
acc.flat_map(move |x| LiberEcclesia::purus(x + i as i32))
});
assert_eq!(
result.extract_pure(),
Some((0..INLINE_CAPACITY as i32).sum())
);
}
#[test]
fn test_ad_ecclesiam_purus() {
let liber: Liber<OptionFWitness, i32> = Liber::purus(42);
assert_eq!(ad_ecclesiam(liber).run_pure(), 42);
}
#[test]
fn test_ad_ecclesiam_suspensus_roundtrip() {
let liber: Liber<OptionFWitness, i32> = Liber::suspensus(Some(Liber::purus(41)));
let ecclesia = ad_ecclesiam(liber);
match ecclesia {
LiberEcclesia::Suspensus { effect, stack } => {
assert_eq!(stack.len(), 1);
let inner: Option<LiberEcclesia<OptionFWitness, i32>> = *effect
.downcast()
.expect("effect must hold the mapped functor layer");
let inner = inner.expect("the Some layer is preserved");
assert_eq!(inner.run_pure(), 41);
}
LiberEcclesia::Purus(_) => panic!("suspended input must stay suspended"),
}
}
#[test]
fn test_ad_ecclesiam_nested_suspensus() {
let liber: Liber<OptionFWitness, i32> =
Liber::suspensus(Some(Liber::suspensus(Some(Liber::purus(7)))));
let LiberEcclesia::Suspensus { effect, .. } = ad_ecclesiam(liber) else {
panic!("layer 1 must be suspended");
};
let inner1: Option<LiberEcclesia<OptionFWitness, i32>> =
*effect.downcast().expect("layer 1 effect type");
let LiberEcclesia::Suspensus { effect, .. } = inner1.expect("Some preserved") else {
panic!("layer 2 must be suspended");
};
let inner2: Option<LiberEcclesia<OptionFWitness, i32>> =
*effect.downcast().expect("layer 2 effect type");
assert_eq!(inner2.expect("Some preserved").run_pure(), 7);
}
}