Skip to main content

karpal_arrow/
arrow.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::category::Category;
5
6/// Arrow: a Category that can lift pure functions and operate on products.
7///
8/// Laws:
9/// - arr(id) == id()
10/// - arr(|a| g(f(a))) == compose(arr(g), arr(f))
11/// - first(arr(f)) == arr(|(a, c)| (f(a), c))
12/// - first(compose(f, g)) == compose(first(f), first(g))
13pub trait Arrow: Category {
14    /// Lift a pure function into an arrow.
15    fn arr<A: Clone + 'static, B: Clone + 'static>(f: impl Fn(A) -> B + 'static) -> Self::P<A, B>;
16
17    /// Apply an arrow to the first component of a pair, passing the second through.
18    fn first<A: Clone + 'static, B: Clone + 'static, C: Clone + 'static>(
19        pab: Self::P<A, B>,
20    ) -> Self::P<(A, C), (B, C)>;
21
22    /// Apply an arrow to the second component of a pair.
23    fn second<A: Clone + 'static, B: Clone + 'static, C: Clone + 'static>(
24        pab: Self::P<A, B>,
25    ) -> Self::P<(C, A), (C, B)> {
26        let swap_in = Self::arr(|(c, a): (C, A)| (a, c));
27        let swap_out = Self::arr(|(b, c): (B, C)| (c, b));
28        Self::compose(swap_out, Self::compose(Self::first(pab), swap_in))
29    }
30
31    /// `***`: apply two arrows in parallel on a product.
32    fn split<A: Clone + 'static, B: Clone + 'static, C: Clone + 'static, D: Clone + 'static>(
33        f: Self::P<A, B>,
34        g: Self::P<C, D>,
35    ) -> Self::P<(A, C), (B, D)> {
36        Self::compose(Self::second(g), Self::first(f))
37    }
38
39    /// `&&&`: feed input to two arrows and collect results as a pair.
40    fn fanout<A: Clone + 'static, B: Clone + 'static, C: Clone + 'static>(
41        f: Self::P<A, B>,
42        g: Self::P<A, C>,
43    ) -> Self::P<A, (B, C)> {
44        let dup = Self::arr(move |a: A| {
45            let a2 = a.clone();
46            (a, a2)
47        });
48        Self::compose(Self::split(f, g), dup)
49    }
50}