Skip to main content

karpal_core/
selective.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::applicative::Applicative;
5use crate::hkt::OptionF;
6
7/// Selective: an Applicative that can conditionally apply effects.
8///
9/// Laws:
10/// - Identity: `select(fmap(Right, x), _) == fmap(id, x)`
11///   (where Right means Ok variant)
12pub trait Selective: Applicative {
13    fn select<A, B, F>(fab: Self::Of<Result<A, B>>, ff: Self::Of<F>) -> Self::Of<B>
14    where
15        A: Clone,
16        F: Fn(A) -> B;
17}
18
19impl Selective for OptionF {
20    fn select<A, B, F>(fab: Option<Result<A, B>>, ff: Option<F>) -> Option<B>
21    where
22        A: Clone,
23        F: Fn(A) -> B,
24    {
25        match fab {
26            None => None,
27            Some(Ok(a)) => ff.map(|f| f(a)),
28            Some(Err(b)) => Some(b),
29        }
30    }
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36
37    #[test]
38    fn option_select_right() {
39        let result = OptionF::select(Some(Err(42i32)), Some(|_x: i32| 0));
40        assert_eq!(result, Some(42));
41    }
42
43    #[test]
44    fn option_select_left_with_fn() {
45        let result = OptionF::select(Some(Ok(3i32)), Some(|x: i32| x * 2));
46        assert_eq!(result, Some(6));
47    }
48
49    #[test]
50    fn option_select_left_no_fn() {
51        let result = OptionF::select(Some(Ok(3i32)), None::<fn(i32) -> i32>);
52        assert_eq!(result, None);
53    }
54
55    #[test]
56    fn option_select_none() {
57        let result = OptionF::select(None::<Result<i32, i32>>, Some(|x: i32| x * 2));
58        assert_eq!(result, None);
59    }
60}
61
62#[cfg(test)]
63mod law_tests {
64    use super::*;
65    use crate::functor::Functor;
66    use proptest::prelude::*;
67
68    proptest! {
69        // Identity: select(fmap(Err, x), _) == x
70        // When all values are Err (already resolved), the function is never applied
71        #[test]
72        fn option_identity(x in any::<Option<i32>>()) {
73            let fab = OptionF::fmap(x, |b| Err::<i32, i32>(b));
74            let dummy: Option<fn(i32) -> i32> = Some(|_| panic!("should not be called"));
75            let result = OptionF::select(fab, dummy);
76            prop_assert_eq!(result, x);
77        }
78    }
79}