#[cfg(feature = "std")]
use std::boxed::Box;
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::boxed::Box;
#[cfg(feature = "std")]
use std::rc::Rc;
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::rc::Rc;
use core::marker::PhantomData;
use karpal_core::applicative::Applicative;
use karpal_core::chain::Chain;
use karpal_core::functor::Functor;
use karpal_core::hkt::HKT;
use karpal_core::natural::NaturalTransformation;
trait FreerStep<F: HKT + 'static, A: 'static> {
fn lower_step(self: Box<Self>) -> F::Of<Freer<F, A>>
where
F: Functor;
}
struct ImpureStep<F: HKT + 'static, A: 'static, B: 'static> {
effect: F::Of<B>,
cont: Box<dyn Fn(B) -> Freer<F, A>>,
}
impl<F: HKT + 'static, A: 'static, B: 'static> FreerStep<F, A> for ImpureStep<F, A, B> {
fn lower_step(self: Box<Self>) -> F::Of<Freer<F, A>>
where
F: Functor,
{
F::fmap(self.effect, self.cont)
}
}
struct ChainedStep<F: HKT + 'static, Src: 'static, A: 'static> {
inner: Box<dyn FreerStep<F, Src>>,
chain_fn: Rc<dyn Fn(Src) -> Freer<F, A>>,
}
impl<F: HKT + 'static, Src: 'static, A: 'static> FreerStep<F, A> for ChainedStep<F, Src, A> {
fn lower_step(self: Box<Self>) -> F::Of<Freer<F, A>>
where
F: Functor,
{
let f_freer_src: F::Of<Freer<F, Src>> = self.inner.lower_step();
let chain_fn = self.chain_fn;
F::fmap(f_freer_src, move |freer_src: Freer<F, Src>| {
freer_src.chain_rc(chain_fn.clone())
})
}
}
#[allow(private_interfaces)]
pub enum Freer<F: HKT + 'static, A: 'static> {
Pure(A),
Impure(Box<dyn FreerStep<F, A>>),
}
impl<F: HKT + 'static, A: 'static> Freer<F, A> {
pub fn pure(a: A) -> Self {
Freer::Pure(a)
}
pub fn lift_f(fa: F::Of<A>) -> Self
where
F::Of<A>: 'static,
{
Freer::Impure(Box::new(ImpureStep {
effect: fa,
cont: Box::new(Freer::Pure),
}))
}
pub fn fmap<B: 'static>(self, f: impl Fn(A) -> B + 'static) -> Freer<F, B> {
self.chain(move |a| Freer::Pure(f(a)))
}
pub fn chain<B: 'static>(self, f: impl Fn(A) -> Freer<F, B> + 'static) -> Freer<F, B> {
let f = Rc::new(f);
self.chain_rc(f)
}
fn chain_rc<B: 'static>(self, f: Rc<dyn Fn(A) -> Freer<F, B>>) -> Freer<F, B> {
match self {
Freer::Pure(a) => f(a),
Freer::Impure(step) => Freer::Impure(Box::new(ChainedStep {
inner: step,
chain_fn: f,
})),
}
}
pub fn fold_map<M, NT>(self) -> M::Of<A>
where
F: Functor,
M: Applicative + Chain,
NT: NaturalTransformation<F, M>,
{
match self {
Freer::Pure(a) => M::pure(a),
Freer::Impure(step) => {
let f_freer: F::Of<Freer<F, A>> = step.lower_step();
let m_freer: M::Of<Freer<F, A>> = NT::transform(f_freer);
M::chain(m_freer, |freer| freer.fold_map::<M, NT>())
}
}
}
}
pub struct FreerF<F: HKT + 'static>(PhantomData<F>);
#[cfg(test)]
mod tests {
use super::*;
use karpal_core::hkt::OptionF;
#[test]
fn pure_value() {
let freer = Freer::<OptionF, i32>::pure(42);
match freer {
Freer::Pure(v) => assert_eq!(v, 42),
Freer::Impure(_) => panic!("expected Pure"),
}
}
#[test]
fn lift_f_some() {
let freer = Freer::<OptionF, i32>::lift_f(Some(10));
match freer {
Freer::Impure(_) => {} Freer::Pure(_) => panic!("expected Impure"),
}
}
#[test]
fn chain_pure() {
let freer = Freer::<OptionF, i32>::pure(1).chain(|x| Freer::pure(x + 1));
match freer {
Freer::Pure(v) => assert_eq!(v, 2),
_ => panic!("expected Pure"),
}
}
#[test]
fn fmap_pure() {
let freer = Freer::<OptionF, i32>::pure(5).fmap(|x| x * 3);
match freer {
Freer::Pure(v) => assert_eq!(v, 15),
_ => panic!("expected Pure"),
}
}
#[test]
fn chain_associativity() {
let left = Freer::<OptionF, i32>::pure(5)
.chain(|x| Freer::pure(x + 1))
.chain(|x| Freer::pure(x * 2));
let right = Freer::<OptionF, i32>::pure(5)
.chain(|x| Freer::<OptionF, i32>::pure(x + 1).chain(|y| Freer::pure(y * 2)));
match (left, right) {
(Freer::Pure(l), Freer::Pure(r)) => assert_eq!(l, r),
_ => panic!("expected both Pure"),
}
}
struct OptionId;
impl NaturalTransformation<OptionF, OptionF> for OptionId {
fn transform<A>(fa: Option<A>) -> Option<A> {
fa
}
}
#[test]
fn fold_map_pure() {
let freer = Freer::<OptionF, i32>::pure(42);
let result = freer.fold_map::<OptionF, OptionId>();
assert_eq!(result, Some(42));
}
#[test]
fn fold_map_lift() {
let freer = Freer::<OptionF, i32>::lift_f(Some(10));
let result = freer.fold_map::<OptionF, OptionId>();
assert_eq!(result, Some(10));
}
#[test]
fn fold_map_chain() {
let freer = Freer::<OptionF, i32>::lift_f(Some(3)).chain(|x| Freer::lift_f(Some(x * 10)));
let result = freer.fold_map::<OptionF, OptionId>();
assert_eq!(result, Some(30));
}
#[test]
fn fold_map_lift_none() {
let freer = Freer::<OptionF, i32>::lift_f(None);
let result = freer.fold_map::<OptionF, OptionId>();
assert_eq!(result, None);
}
#[test]
fn fmap_lift_then_fold() {
let freer = Freer::<OptionF, i32>::lift_f(Some(5)).fmap(|x| x + 10);
let result = freer.fold_map::<OptionF, OptionId>();
assert_eq!(result, Some(15));
}
#[test]
fn chain_lift_multiple() {
let freer = Freer::<OptionF, i32>::lift_f(Some(1))
.chain(|x| Freer::lift_f(Some(x + 1)))
.chain(|x| Freer::lift_f(Some(x * 10)));
let result = freer.fold_map::<OptionF, OptionId>();
assert_eq!(result, Some(20)); }
}
#[cfg(test)]
mod law_tests {
use super::*;
use karpal_core::hkt::OptionF;
use proptest::prelude::*;
fn extract_pure<F: HKT + 'static, A: 'static>(freer: Freer<F, A>) -> Option<A> {
match freer {
Freer::Pure(a) => Some(a),
Freer::Impure(_) => None,
}
}
proptest! {
#[test]
fn monad_left_identity(x in any::<i32>()) {
let left = extract_pure(
Freer::<OptionF, i32>::pure(x)
.chain(|a| Freer::pure(a.wrapping_mul(2))),
);
let right = Some(x.wrapping_mul(2));
prop_assert_eq!(left, right);
}
#[test]
fn monad_right_identity(x in any::<i32>()) {
let result = extract_pure(Freer::<OptionF, i32>::pure(x).chain(Freer::pure));
prop_assert_eq!(result, Some(x));
}
}
}