Skip to main content

karpal_core/
alt.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::functor::Functor;
5use crate::hkt::OptionF;
6use crate::hkt::ResultF;
7#[cfg(any(feature = "std", feature = "alloc"))]
8use crate::hkt::VecF;
9#[cfg(all(not(feature = "std"), feature = "alloc"))]
10use alloc::vec::Vec;
11
12/// Alt: a Functor with an associative choice operation.
13///
14/// Laws:
15/// - Associativity: `alt(alt(a, b), c) == alt(a, alt(b, c))`
16/// - Distributivity: `fmap(f, alt(a, b)) == alt(fmap(f, a), fmap(f, b))`
17pub trait Alt: Functor {
18    fn alt<A>(fa1: Self::Of<A>, fa2: Self::Of<A>) -> Self::Of<A>;
19}
20
21impl Alt for OptionF {
22    fn alt<A>(fa1: Option<A>, fa2: Option<A>) -> Option<A> {
23        fa1.or(fa2)
24    }
25}
26
27impl<E> Alt for ResultF<E> {
28    fn alt<A>(fa1: Result<A, E>, fa2: Result<A, E>) -> Result<A, E> {
29        fa1.or(fa2)
30    }
31}
32
33#[cfg(any(feature = "std", feature = "alloc"))]
34impl Alt for VecF {
35    fn alt<A>(mut fa1: Vec<A>, fa2: Vec<A>) -> Vec<A> {
36        fa1.extend(fa2);
37        fa1
38    }
39}
40
41#[cfg(any(feature = "std", feature = "alloc"))]
42impl Alt for crate::hkt::NonEmptyVecF {
43    fn alt<A>(
44        mut fa1: crate::hkt::NonEmptyVec<A>,
45        fa2: crate::hkt::NonEmptyVec<A>,
46    ) -> crate::hkt::NonEmptyVec<A> {
47        fa1.tail.push(fa2.head);
48        fa1.tail.extend(fa2.tail);
49        fa1
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn option_alt_some_some() {
59        assert_eq!(OptionF::alt(Some(1), Some(2)), Some(1));
60    }
61
62    #[test]
63    fn option_alt_none_some() {
64        assert_eq!(OptionF::alt(None, Some(2)), Some(2));
65    }
66
67    #[test]
68    fn option_alt_none_none() {
69        assert_eq!(OptionF::alt(None::<i32>, None), None);
70    }
71
72    #[test]
73    fn result_alt() {
74        assert_eq!(ResultF::<&str>::alt(Err("a"), Ok(2)), Ok(2));
75        assert_eq!(ResultF::<&str>::alt(Ok(1), Ok(2)), Ok(1));
76    }
77
78    #[test]
79    fn vec_alt() {
80        assert_eq!(VecF::alt(vec![1, 2], vec![3, 4]), vec![1, 2, 3, 4]);
81    }
82}
83
84#[cfg(test)]
85mod law_tests {
86    use super::*;
87    use crate::functor::Functor;
88    use proptest::prelude::*;
89
90    proptest! {
91        // Associativity: alt(alt(a, b), c) == alt(a, alt(b, c))
92        #[test]
93        fn option_associativity(
94            a in any::<Option<i32>>(),
95            b in any::<Option<i32>>(),
96            c in any::<Option<i32>>()
97        ) {
98            let left = OptionF::alt(OptionF::alt(a, b), c);
99            let right = OptionF::alt(a, OptionF::alt(b, c));
100            prop_assert_eq!(left, right);
101        }
102
103        // Distributivity: fmap(f, alt(a, b)) == alt(fmap(f, a), fmap(f, b))
104        #[test]
105        fn option_distributivity(
106            a in any::<Option<i32>>(),
107            b in any::<Option<i32>>()
108        ) {
109            let f = |x: i32| x.wrapping_add(1);
110            let left = OptionF::fmap(OptionF::alt(a, b), f);
111            let right = OptionF::alt(OptionF::fmap(a, f), OptionF::fmap(b, f));
112            prop_assert_eq!(left, right);
113        }
114
115        #[test]
116        fn vec_associativity(
117            a in prop::collection::vec(any::<i32>(), 0..5),
118            b in prop::collection::vec(any::<i32>(), 0..5),
119            c in prop::collection::vec(any::<i32>(), 0..5)
120        ) {
121            let left = VecF::alt(VecF::alt(a.clone(), b.clone()), c.clone());
122            let right = VecF::alt(a, VecF::alt(b, c));
123            prop_assert_eq!(left, right);
124        }
125    }
126}