use karpal_core::functor::Functor;
use karpal_core::hkt::HKT;
use karpal_free::cofree::Cofree;
use karpal_free::free::Free;
use crate::either::Either;
use crate::fix::Fix;
pub fn cata<F: HKT + Functor, A>(alg: impl Fn(F::Of<A>) -> A, fix: Fix<F>) -> A
where
F::Of<Fix<F>>: Clone,
{
cata_inner(&alg, fix)
}
fn cata_inner<F: HKT + Functor, A>(alg: &dyn Fn(F::Of<A>) -> A, fix: Fix<F>) -> A
where
F::Of<Fix<F>>: Clone,
{
let layer = fix.unfix();
let mapped = F::fmap(layer, |child| cata_inner(alg, child));
alg(mapped)
}
pub fn ana<F: HKT + Functor, A>(coalg: impl Fn(A) -> F::Of<A>, seed: A) -> Fix<F> {
ana_inner(&coalg, seed)
}
fn ana_inner<F: HKT + Functor, A>(coalg: &dyn Fn(A) -> F::Of<A>, seed: A) -> Fix<F> {
let layer = coalg(seed);
let mapped = F::fmap(layer, |child_seed| ana_inner(coalg, child_seed));
Fix::new(mapped)
}
pub fn hylo<F: HKT + Functor, A, B>(
alg: impl Fn(F::Of<B>) -> B,
coalg: impl Fn(A) -> F::Of<A>,
seed: A,
) -> B {
hylo_inner::<F, A, B>(&alg, &coalg, seed)
}
fn hylo_inner<F: HKT + Functor, A, B>(
alg: &dyn Fn(F::Of<B>) -> B,
coalg: &dyn Fn(A) -> F::Of<A>,
seed: A,
) -> B {
let layer = coalg(seed);
let mapped = F::fmap(layer, |child_seed| {
hylo_inner::<F, A, B>(alg, coalg, child_seed)
});
alg(mapped)
}
pub fn para<F: HKT + Functor, A>(alg: impl Fn(F::Of<(Fix<F>, A)>) -> A, fix: Fix<F>) -> A
where
F::Of<Fix<F>>: Clone,
{
para_inner(&alg, fix)
}
fn para_inner<F: HKT + Functor, A>(alg: &dyn Fn(F::Of<(Fix<F>, A)>) -> A, fix: Fix<F>) -> A
where
F::Of<Fix<F>>: Clone,
{
let layer = fix.unfix();
let paired = F::fmap(layer, |child: Fix<F>| {
let original = child.clone(); let folded = para_inner(alg, child);
(original, folded)
});
alg(paired)
}
pub fn apo<F: HKT + Functor, A>(coalg: impl Fn(A) -> F::Of<Either<Fix<F>, A>>, seed: A) -> Fix<F> {
apo_inner(&coalg, seed)
}
fn apo_inner<F: HKT + Functor, A>(
coalg: &dyn Fn(A) -> F::Of<Either<Fix<F>, A>>,
seed: A,
) -> Fix<F> {
let layer = coalg(seed);
let mapped = F::fmap(layer, |e| match e {
Either::Left(fix) => fix,
Either::Right(s) => apo_inner(coalg, s),
});
Fix::new(mapped)
}
pub fn histo<F: HKT + Functor, A>(alg: impl Fn(&F::Of<Cofree<F, A>>) -> A, fix: Fix<F>) -> A
where
F::Of<Fix<F>>: Clone,
{
histo_inner(&alg, fix).head
}
fn histo_inner<F: HKT + Functor, A>(
alg: &dyn Fn(&F::Of<Cofree<F, A>>) -> A,
fix: Fix<F>,
) -> Cofree<F, A>
where
F::Of<Fix<F>>: Clone,
{
let layer = fix.unfix();
let mapped: F::Of<Cofree<F, A>> = F::fmap(layer, |child| histo_inner(alg, child));
let head = alg(&mapped);
Cofree::new(head, mapped)
}
pub fn futu<F: HKT + Functor, A>(coalg: impl Fn(A) -> F::Of<Free<F, A>>, seed: A) -> Fix<F> {
futu_inner(&coalg, seed)
}
fn futu_inner<F: HKT + Functor, A>(coalg: &dyn Fn(A) -> F::Of<Free<F, A>>, seed: A) -> Fix<F> {
let layer = coalg(seed);
let mapped = F::fmap(layer, |free| free_to_fix(coalg, free));
Fix::new(mapped)
}
fn free_to_fix<F: HKT + Functor, A>(
coalg: &dyn Fn(A) -> F::Of<Free<F, A>>,
free: Free<F, A>,
) -> Fix<F> {
match free {
Free::Pure(a) => futu_inner(coalg, a),
Free::Roll(ff) => {
let mapped = F::fmap(*ff, |child| free_to_fix(coalg, child));
Fix::new(mapped)
}
}
}
pub fn zygo<F: HKT + Functor, A, B>(
aux: impl Fn(F::Of<B>) -> B,
alg: impl Fn(F::Of<(B, A)>) -> A,
fix: Fix<F>,
) -> A
where
F::Of<Fix<F>>: Clone,
F::Of<(B, A)>: Clone,
{
zygo_inner(&aux, &alg, fix).1
}
fn zygo_inner<F: HKT + Functor, A, B>(
aux: &dyn Fn(F::Of<B>) -> B,
alg: &dyn Fn(F::Of<(B, A)>) -> A,
fix: Fix<F>,
) -> (B, A)
where
F::Of<Fix<F>>: Clone,
F::Of<(B, A)>: Clone,
{
let layer = fix.unfix();
let mapped: F::Of<(B, A)> = F::fmap(layer, |child| zygo_inner(aux, alg, child));
let for_aux = mapped.clone();
let bs = F::fmap(for_aux, |(b, _a): (B, A)| b);
let b = aux(bs);
let a = alg(mapped);
(b, a)
}
pub fn chrono<F: HKT + Functor, A, B>(
alg: impl Fn(&F::Of<Cofree<F, B>>) -> B,
coalg: impl Fn(A) -> F::Of<Free<F, A>>,
seed: A,
) -> B {
chrono_inner::<F, A, B>(&alg, &coalg, seed).head
}
fn chrono_inner<F: HKT + Functor, A, B>(
alg: &dyn Fn(&F::Of<Cofree<F, B>>) -> B,
coalg: &dyn Fn(A) -> F::Of<Free<F, A>>,
seed: A,
) -> Cofree<F, B> {
let layer = coalg(seed);
let mapped: F::Of<Cofree<F, B>> = F::fmap(layer, |free| free_to_cofree(alg, coalg, free));
let head = alg(&mapped);
Cofree::new(head, mapped)
}
fn free_to_cofree<F: HKT + Functor, A, B>(
alg: &dyn Fn(&F::Of<Cofree<F, B>>) -> B,
coalg: &dyn Fn(A) -> F::Of<Free<F, A>>,
free: Free<F, A>,
) -> Cofree<F, B> {
match free {
Free::Pure(a) => chrono_inner::<F, A, B>(alg, coalg, a),
Free::Roll(ff) => {
let mapped: F::Of<Cofree<F, B>> =
F::fmap(*ff, |child| free_to_cofree(alg, coalg, child));
let head = alg(&mapped);
Cofree::new(head, mapped)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use karpal_core::hkt::OptionF;
fn zero() -> Fix<OptionF> {
Fix::new(None)
}
fn succ(n: Fix<OptionF>) -> Fix<OptionF> {
Fix::new(Some(n))
}
fn nat(n: u32) -> Fix<OptionF> {
let mut result = zero();
for _ in 0..n {
result = succ(result);
}
result
}
fn to_u32(n: &Fix<OptionF>) -> u32 {
match n.unfix_ref() {
None => 0,
Some(pred) => 1 + to_u32(pred),
}
}
#[test]
fn cata_count_nat() {
let result = cata::<OptionF, u32>(
|layer| match layer {
None => 0,
Some(n) => n + 1,
},
nat(5),
);
assert_eq!(result, 5);
}
#[test]
fn cata_zero() {
let result = cata::<OptionF, u32>(
|layer| match layer {
None => 0,
Some(n) => n + 1,
},
zero(),
);
assert_eq!(result, 0);
}
#[test]
fn cata_to_string() {
let result = cata::<OptionF, String>(
|layer| match layer {
None => "Z".to_string(),
Some(s) => format!("S({})", s),
},
nat(3),
);
assert_eq!(result, "S(S(S(Z)))");
}
#[test]
fn cata_identity_law() {
let rebuilt: Fix<OptionF> = cata(Fix::new, nat(4));
assert_eq!(to_u32(&rebuilt), 4);
}
#[test]
fn ana_build_nat() {
let n: Fix<OptionF> = ana(
|seed: u32| {
if seed == 0 { None } else { Some(seed - 1) }
},
5,
);
assert_eq!(to_u32(&n), 5);
}
#[test]
fn ana_zero() {
let n: Fix<OptionF> = ana(|_: u32| None, 0);
assert_eq!(to_u32(&n), 0);
}
#[test]
fn ana_step_by_two() {
let n: Fix<OptionF> = ana(
|seed: u32| {
if seed == 0 {
None
} else {
Some(seed.saturating_sub(2))
}
},
10,
);
assert_eq!(to_u32(&n), 5);
}
#[test]
fn hylo_count() {
let result = hylo::<OptionF, u32, u32>(
|layer| match layer {
None => 0,
Some(acc) => acc + 1,
},
|seed| {
if seed == 0 { None } else { Some(seed - 1) }
},
5,
);
assert_eq!(result, 5);
}
#[test]
fn hylo_equals_cata_ana() {
let alg = |layer: Option<u32>| match layer {
None => 0u32,
Some(n) => n + 1,
};
let coalg = |seed: u32| -> Option<u32> { if seed == 0 { None } else { Some(seed - 1) } };
for n in 0..10 {
let via_hylo = hylo::<OptionF, u32, u32>(alg, coalg, n);
let via_cata_ana = cata::<OptionF, u32>(alg, ana(coalg, n));
assert_eq!(via_hylo, via_cata_ana, "failed for n={}", n);
}
}
#[test]
fn para_factorial() {
let result = para::<OptionF, u64>(
|layer| match layer {
None => 1,
Some((sub, acc)) => {
let n = to_u32(&sub) + 1;
(n as u64) * acc
}
},
nat(5),
);
assert_eq!(result, 120);
}
#[test]
fn para_degenerates_to_cata() {
let via_para = para::<OptionF, u32>(
|layer| match layer {
None => 0,
Some((_sub, acc)) => acc + 1,
},
nat(7),
);
let via_cata = cata::<OptionF, u32>(
|layer| match layer {
None => 0,
Some(acc) => acc + 1,
},
nat(7),
);
assert_eq!(via_para, via_cata);
}
#[test]
fn para_zero() {
let result = para::<OptionF, u64>(
|layer| match layer {
None => 1,
Some((sub, acc)) => {
let n = to_u32(&sub) + 1;
(n as u64) * acc
}
},
zero(),
);
assert_eq!(result, 1);
}
#[test]
fn apo_build_nat() {
let n: Fix<OptionF> = apo(
|seed: u32| {
if seed == 0 {
None
} else {
Some(Either::Right(seed - 1))
}
},
3,
);
assert_eq!(to_u32(&n), 3);
}
#[test]
fn apo_early_stop() {
let n: Fix<OptionF> = apo(
|seed: u32| {
if seed == 0 {
None
} else if seed <= 2 {
Some(Either::Left(nat(seed - 1)))
} else {
Some(Either::Right(seed - 1))
}
},
5,
);
assert_eq!(to_u32(&n), 5);
}
#[test]
fn apo_degenerates_to_ana() {
let coalg_apo = |seed: u32| -> Option<Either<Fix<OptionF>, u32>> {
if seed == 0 {
None
} else {
Some(Either::Right(seed - 1))
}
};
let coalg_ana =
|seed: u32| -> Option<u32> { if seed == 0 { None } else { Some(seed - 1) } };
for n in 0..8 {
assert_eq!(to_u32(&apo(coalg_apo, n)), to_u32(&ana(coalg_ana, n)));
}
}
#[test]
fn histo_fibonacci() {
let result = histo::<OptionF, u64>(
|layer| match layer {
None => 0,
Some(cofree) => {
let fib_prev = cofree.head;
match cofree.tail.as_ref() {
None => 1,
Some(grandchild) => fib_prev + grandchild.head,
}
}
},
nat(10),
);
assert_eq!(result, 55);
}
#[test]
fn histo_degenerates_to_cata() {
let via_histo = histo::<OptionF, u32>(
|layer| match layer {
None => 0,
Some(cofree) => cofree.head + 1,
},
nat(5),
);
let via_cata = cata::<OptionF, u32>(
|layer| match layer {
None => 0,
Some(n) => n + 1,
},
nat(5),
);
assert_eq!(via_histo, via_cata);
}
#[test]
fn histo_zero() {
let result = histo::<OptionF, u64>(
|layer| match layer {
None => 0,
Some(cofree) => {
let prev = cofree.head;
match cofree.tail.as_ref() {
None => 1,
Some(gc) => prev + gc.head,
}
}
},
zero(),
);
assert_eq!(result, 0);
}
#[test]
fn futu_build_nat() {
let n: Fix<OptionF> = futu(
|seed: u32| -> Option<Free<OptionF, u32>> {
if seed == 0 {
None
} else {
Some(Free::Pure(seed - 1))
}
},
3,
);
assert_eq!(to_u32(&n), 3);
}
#[test]
fn futu_multi_step() {
let n: Fix<OptionF> = futu(
|seed: u32| -> Option<Free<OptionF, u32>> {
if seed == 0 {
None
} else if seed == 1 {
Some(Free::Roll(Box::new(None)))
} else {
Some(Free::Roll(Box::new(Some(Free::Pure(seed - 2)))))
}
},
4,
);
assert_eq!(to_u32(&n), 4);
}
#[test]
fn futu_degenerates_to_ana() {
let coalg_futu = |seed: u32| -> Option<Free<OptionF, u32>> {
if seed == 0 {
None
} else {
Some(Free::Pure(seed - 1))
}
};
let coalg_ana =
|seed: u32| -> Option<u32> { if seed == 0 { None } else { Some(seed - 1) } };
for n in 0..8 {
assert_eq!(to_u32(&futu(coalg_futu, n)), to_u32(&ana(coalg_ana, n)));
}
}
#[test]
fn zygo_parity_and_count() {
let result = zygo::<OptionF, u32, u32>(
|layer| match layer {
None => 0,
Some(n) => n + 1,
},
|layer| match layer {
None => 0,
Some((b, _a)) => b + 1,
},
nat(5),
);
assert_eq!(result, 5);
}
#[test]
fn zygo_degenerates_to_cata() {
let via_zygo = zygo::<OptionF, u32, u32>(
|layer| match layer {
None => 0u32,
Some(n) => n,
},
|layer| match layer {
None => 0,
Some((_b, a)) => a + 1,
},
nat(5),
);
let via_cata = cata::<OptionF, u32>(
|layer| match layer {
None => 0,
Some(n) => n + 1,
},
nat(5),
);
assert_eq!(via_zygo, via_cata);
}
#[test]
fn chrono_fibonacci() {
let result = chrono::<OptionF, u32, u64>(
|layer| match layer {
None => 0,
Some(cofree) => {
let prev = cofree.head;
match cofree.tail.as_ref() {
None => 1,
Some(gc) => prev + gc.head,
}
}
},
|seed: u32| -> Option<Free<OptionF, u32>> {
if seed == 0 {
None
} else {
Some(Free::Pure(seed - 1))
}
},
10,
);
assert_eq!(result, 55);
}
}
#[cfg(test)]
mod law_tests {
use super::*;
use karpal_core::hkt::OptionF;
use proptest::prelude::*;
fn nat(n: u32) -> Fix<OptionF> {
let mut result = Fix::new(None);
for _ in 0..n {
result = Fix::new(Some(result));
}
result
}
fn to_u32(n: &Fix<OptionF>) -> u32 {
match n.unfix_ref() {
None => 0,
Some(pred) => 1 + to_u32(pred),
}
}
proptest! {
#[test]
fn cata_fix_identity(n in 0u32..20) {
let rebuilt: Fix<OptionF> = cata(Fix::new, nat(n));
prop_assert_eq!(to_u32(&rebuilt), n);
}
#[test]
fn hylo_is_cata_ana(n in 0u32..20) {
let alg = |layer: Option<u32>| match layer {
None => 0u32,
Some(x) => x + 1,
};
let coalg = |seed: u32| -> Option<u32> {
if seed == 0 { None } else { Some(seed - 1) }
};
let via_hylo = hylo::<OptionF, u32, u32>(alg, coalg, n);
let via_cata_ana = cata::<OptionF, u32>(alg, ana(coalg, n));
prop_assert_eq!(via_hylo, via_cata_ana);
}
#[test]
fn ana_cata_roundtrip(n in 0u32..20) {
let coalg = |seed: u32| -> Option<u32> {
if seed == 0 { None } else { Some(seed - 1) }
};
let alg = |layer: Option<u32>| match layer {
None => 0u32,
Some(x) => x + 1,
};
prop_assert_eq!(cata::<OptionF, u32>(alg, ana(coalg, n)), n);
}
#[test]
fn apo_always_right_is_ana(n in 0u32..15) {
let coalg_ana = |seed: u32| -> Option<u32> {
if seed == 0 { None } else { Some(seed - 1) }
};
let coalg_apo = |seed: u32| -> Option<Either<Fix<OptionF>, u32>> {
if seed == 0 { None } else { Some(Either::Right(seed - 1)) }
};
prop_assert_eq!(to_u32(&ana(coalg_ana, n)), to_u32(&apo(coalg_apo, n)));
}
#[test]
fn futu_always_pure_is_ana(n in 0u32..15) {
let coalg_ana = |seed: u32| -> Option<u32> {
if seed == 0 { None } else { Some(seed - 1) }
};
let coalg_futu = |seed: u32| -> Option<Free<OptionF, u32>> {
if seed == 0 { None } else { Some(Free::Pure(seed - 1)) }
};
prop_assert_eq!(to_u32(&ana(coalg_ana, n)), to_u32(&futu(coalg_futu, n)));
}
#[test]
fn para_ignoring_subterms_is_cata(n in 0u32..15) {
let via_para = para::<OptionF, u32>(
|layer| match layer {
None => 0,
Some((_sub, acc)) => acc + 1,
},
nat(n),
);
let via_cata = cata::<OptionF, u32>(
|layer| match layer {
None => 0,
Some(acc) => acc + 1,
},
nat(n),
);
prop_assert_eq!(via_para, via_cata);
}
#[test]
fn histo_ignoring_history_is_cata(n in 0u32..15) {
let via_histo = histo::<OptionF, u32>(
|layer| match layer {
None => 0,
Some(cofree) => cofree.head + 1,
},
nat(n),
);
let via_cata = cata::<OptionF, u32>(
|layer| match layer {
None => 0,
Some(n) => n + 1,
},
nat(n),
);
prop_assert_eq!(via_histo, via_cata);
}
}
}