#[cfg(feature = "std")]
use std::boxed::Box;
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::boxed::Box;
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;
pub enum Free<F: HKT, A> {
Pure(A),
Roll(Box<F::Of<Free<F, A>>>),
}
impl<F: HKT, A> Free<F, A> {
pub fn pure(a: A) -> Self {
Free::Pure(a)
}
}
impl<F: HKT + Functor, A> Free<F, A> {
pub fn lift_f(fa: F::Of<A>) -> Self {
Free::Roll(Box::new(F::fmap(fa, Free::Pure)))
}
pub fn fmap<B>(self, f: impl Fn(A) -> B) -> Free<F, B> {
self.fmap_inner(&f)
}
fn fmap_inner<B>(self, f: &dyn Fn(A) -> B) -> Free<F, B> {
match self {
Free::Pure(a) => Free::Pure(f(a)),
Free::Roll(ff) => Free::Roll(Box::new(F::fmap(*ff, |child| child.fmap_inner(f)))),
}
}
pub fn chain<B>(self, f: impl Fn(A) -> Free<F, B>) -> Free<F, B> {
self.chain_inner(&f)
}
fn chain_inner<B>(self, f: &dyn Fn(A) -> Free<F, B>) -> Free<F, B> {
match self {
Free::Pure(a) => f(a),
Free::Roll(ff) => Free::Roll(Box::new(F::fmap(*ff, |child| child.chain_inner(f)))),
}
}
pub fn fold_map<M, NT>(self) -> M::Of<A>
where
M: Applicative + Chain,
NT: NaturalTransformation<F, M>,
{
match self {
Free::Pure(a) => M::pure(a),
Free::Roll(ff) => {
let mapped = F::fmap(*ff, |child| child.fold_map::<M, NT>());
let m_ma: M::Of<M::Of<A>> = NT::transform(mapped);
M::chain(m_ma, |x| x)
}
}
}
}
pub struct FreeF<F: HKT>(PhantomData<F>);
impl<F: HKT> HKT for FreeF<F> {
type Of<T> = Free<F, T>;
}
impl<F: HKT + Functor> Functor for FreeF<F> {
fn fmap<A, B>(fa: Free<F, A>, f: impl Fn(A) -> B) -> Free<F, B> {
fa.fmap(f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use karpal_core::hkt::OptionF;
#[test]
fn pure_value() {
let free = Free::<OptionF, i32>::pure(42);
match free {
Free::Pure(v) => assert_eq!(v, 42),
Free::Roll(_) => panic!("expected Pure"),
}
}
#[test]
fn lift_f_some() {
let free = Free::<OptionF, i32>::lift_f(Some(1));
match free {
Free::Roll(ff) => match *ff {
Some(Free::Pure(v)) => assert_eq!(v, 1),
_ => panic!("expected Some(Pure(1))"),
},
Free::Pure(_) => panic!("expected Roll"),
}
}
#[test]
fn fmap_pure() {
let free = Free::<OptionF, i32>::pure(2).fmap(|x| x * 3);
match free {
Free::Pure(v) => assert_eq!(v, 6),
Free::Roll(_) => panic!("expected Pure"),
}
}
#[test]
fn fmap_roll() {
let free = Free::<OptionF, i32>::lift_f(Some(5)).fmap(|x| x + 10);
match free {
Free::Roll(ff) => match *ff {
Some(Free::Pure(v)) => assert_eq!(v, 15),
_ => panic!("expected Some(Pure(15))"),
},
Free::Pure(_) => panic!("expected Roll"),
}
}
#[test]
fn chain_pure() {
let free = Free::<OptionF, i32>::pure(1).chain(|x| Free::pure(x + 1));
match free {
Free::Pure(v) => assert_eq!(v, 2),
Free::Roll(_) => panic!("expected Pure"),
}
}
#[test]
fn chain_roll() {
let free = Free::<OptionF, i32>::lift_f(Some(10)).chain(|x| Free::pure(x * 2));
match free {
Free::Roll(ff) => match *ff {
Some(Free::Pure(v)) => assert_eq!(v, 20),
_ => panic!("expected Some(Pure(20))"),
},
Free::Pure(_) => panic!("expected Roll"),
}
}
#[test]
fn chain_associativity() {
let _m = Free::<OptionF, i32>::pure(5);
let _f = |x: i32| Free::<OptionF, i32>::pure(x + 1);
let _g = |x: i32| Free::<OptionF, i32>::pure(x * 2);
let left = Free::<OptionF, i32>::pure(5)
.chain(|x| Free::pure(x + 1))
.chain(|x| Free::pure(x * 2));
let right = Free::<OptionF, i32>::pure(5)
.chain(|x| Free::<OptionF, i32>::pure(x + 1).chain(|y| Free::pure(y * 2)));
match (left, right) {
(Free::Pure(l), Free::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 free = Free::<OptionF, i32>::pure(42);
let result = free.fold_map::<OptionF, OptionId>();
assert_eq!(result, Some(42));
}
#[test]
fn fold_map_roll() {
let free = Free::<OptionF, i32>::lift_f(Some(10));
let result = free.fold_map::<OptionF, OptionId>();
assert_eq!(result, Some(10));
}
#[test]
fn fold_map_chain_then_interpret() {
let free = Free::<OptionF, i32>::lift_f(Some(3)).chain(|x| Free::lift_f(Some(x * 10)));
let result = free.fold_map::<OptionF, OptionId>();
assert_eq!(result, Some(30));
}
#[test]
fn functor_impl_works() {
let free = Free::<OptionF, i32>::pure(5);
let result = <FreeF<OptionF> as Functor>::fmap(free, |x| x + 10);
match result {
Free::Pure(v) => assert_eq!(v, 15),
Free::Roll(_) => panic!("expected Pure"),
}
}
}
#[cfg(test)]
mod law_tests {
use super::*;
use karpal_core::hkt::OptionF;
use proptest::prelude::*;
fn extract_pure<F: HKT, A>(free: Free<F, A>) -> Option<A> {
match free {
Free::Pure(a) => Some(a),
Free::Roll(_) => None,
}
}
proptest! {
#[test]
fn functor_identity(x in any::<i32>()) {
let free = Free::<OptionF, i32>::pure(x);
let result = free.fmap(|a| a);
prop_assert_eq!(extract_pure(result), Some(x));
}
#[test]
fn functor_composition(x in any::<i32>()) {
let f = |a: i32| a.wrapping_add(1);
let g = |a: i32| a.wrapping_mul(2);
let left = Free::<OptionF, i32>::pure(x).fmap(|a| g(f(a)));
let right = Free::<OptionF, i32>::pure(x).fmap(f).fmap(g);
prop_assert_eq!(extract_pure(left), extract_pure(right));
}
#[test]
fn monad_left_identity(x in any::<i32>()) {
let f = |a: i32| Free::<OptionF, i32>::pure(a.wrapping_mul(2));
let left = Free::<OptionF, i32>::pure(x).chain(&f);
let right = f(x);
prop_assert_eq!(extract_pure(left), extract_pure(right));
}
#[test]
fn monad_right_identity(x in any::<i32>()) {
let m = Free::<OptionF, i32>::pure(x);
let result = m.chain(Free::pure);
prop_assert_eq!(extract_pure(result), Some(x));
}
}
}