use std::fmt::Debug;
#[allow(clippy::type_complexity)]
pub struct Algebra<A, F> {
f: Box<dyn Fn(&A, &[F]) -> F>,
}
impl<A: 'static, F: 'static> Algebra<A, F> {
pub fn new(f: impl Fn(&A, &[F]) -> F + 'static) -> Self {
Self { f: Box::new(f) }
}
pub fn apply(&self, node: &A, children: &[F]) -> F {
(self.f)(node, children)
}
}
#[allow(clippy::type_complexity)]
pub struct Coalgebra<S, A> {
f: Box<dyn Fn(&S) -> (A, Vec<S>)>,
}
impl<S: 'static, A: 'static> Coalgebra<S, A> {
pub fn new(f: impl Fn(&S) -> (A, Vec<S>) + 'static) -> Self {
Self { f: Box::new(f) }
}
pub fn apply(&self, seed: &S) -> (A, Vec<S>) {
(self.f)(seed)
}
}
pub fn cata<A: Clone + Debug + 'static, F: 'static>(
alg: &Algebra<A, F>,
tree: &super::comonad::Cofree<A>,
) -> F {
let children: Vec<F> = tree.tail.iter().map(|c| cata(alg, c)).collect();
alg.apply(&tree.head, &children)
}
pub fn ana<S: Clone + 'static, A: Clone + Debug + 'static>(
coalg: &Coalgebra<S, A>,
seed: &S,
) -> super::comonad::Cofree<A> {
let (value, children) = coalg.apply(seed);
super::comonad::Cofree {
head: value,
tail: children.iter().map(|s| ana(coalg, s)).collect(),
}
}
pub fn hylo<S: Clone + 'static, A: Clone + Debug + 'static, F: 'static>(
alg: &Algebra<A, F>,
coalg: &Coalgebra<S, A>,
seed: &S,
) -> F {
let (value, children) = coalg.apply(seed);
let child_results: Vec<F> = children.iter().map(|s| hylo(alg, coalg, s)).collect();
alg.apply(&value, &child_results)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::category::comonad::Cofree;
#[test]
fn algebra_sum_tree() {
let sum = Algebra::new(|node: &i32, children: &[i32]| node + children.iter().sum::<i32>());
let tree = Cofree::node(1, vec![Cofree::leaf(2), Cofree::leaf(3)]);
assert_eq!(cata(&sum, &tree), 6);
}
#[test]
fn algebra_count_nodes() {
let count =
Algebra::new(|_node: &&str, children: &[usize]| 1 + children.iter().sum::<usize>());
let tree = Cofree::node(
"root",
vec![
Cofree::node("left", vec![Cofree::leaf("ll")]),
Cofree::leaf("right"),
],
);
assert_eq!(cata(&count, &tree), 4);
}
#[test]
fn coalgebra_countdown() {
let countdown = Coalgebra::new(|n: &i32| {
if *n <= 0 {
(*n, vec![])
} else {
(*n, vec![n - 1])
}
});
let tree = ana(&countdown, &3);
assert_eq!(*tree.extract(), 3);
assert_eq!(tree.tail.len(), 1);
assert_eq!(*tree.tail[0].extract(), 2);
assert_eq!(*tree.tail[0].tail[0].extract(), 1);
assert_eq!(*tree.tail[0].tail[0].tail[0].extract(), 0);
}
#[test]
fn coalgebra_binary_tree() {
let binary = Coalgebra::new(|depth: &i32| {
if *depth <= 0 {
(*depth, vec![])
} else {
(*depth, vec![depth - 1, depth - 1])
}
});
let tree = ana(&binary, &2);
assert_eq!(*tree.extract(), 2);
assert_eq!(tree.tail.len(), 2); assert_eq!(tree.tail[0].tail.len(), 2); assert_eq!(tree.tail[0].tail[0].tail.len(), 0); }
#[test]
fn hylomorphism_factorial() {
let coalg = Coalgebra::new(|n: &i32| {
if *n <= 1 {
(*n, vec![])
} else {
(*n, vec![n - 1])
}
});
let alg = Algebra::new(|n: &i32, children: &[i32]| {
if children.is_empty() {
1
} else {
n * children[0]
}
});
assert_eq!(hylo(&alg, &coalg, &5), 120); assert_eq!(hylo(&alg, &coalg, &1), 1);
}
#[test]
fn hylomorphism_fibonacci() {
let coalg = Coalgebra::new(|n: &i32| {
if *n <= 1 {
(*n, vec![])
} else {
(*n, vec![n - 1, n - 2])
}
});
let alg = Algebra::new(|n: &i32, children: &[i32]| {
if children.is_empty() {
*n } else {
children.iter().sum()
}
});
assert_eq!(hylo(&alg, &coalg, &0), 0);
assert_eq!(hylo(&alg, &coalg, &1), 1);
assert_eq!(hylo(&alg, &coalg, &6), 8); assert_eq!(hylo(&alg, &coalg, &10), 55); }
#[test]
fn taxonomy_as_anamorphism() {
let taxonomy: Vec<(&str, &str)> =
vec![("Animal", "Mammal"), ("Mammal", "Dog"), ("Mammal", "Cat")];
let unfold = Coalgebra::new(move |entity: &&str| {
let children: Vec<&str> = taxonomy
.iter()
.filter(|(parent, _)| parent == entity)
.map(|(_, child)| *child)
.collect();
(*entity, children)
});
let tree = ana(&unfold, &"Animal");
assert_eq!(*tree.extract(), "Animal");
assert_eq!(tree.tail.len(), 1); assert_eq!(*tree.tail[0].extract(), "Mammal");
assert_eq!(tree.tail[0].tail.len(), 2); }
}