Skip to main content

karpal_core/
applicative.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::apply::Apply;
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::Vec};
11
12/// Applicative: an Apply that can lift a pure value into the functor.
13///
14/// Laws:
15/// - Identity: `ap(pure(id), v) == v`
16/// - Homomorphism: `ap(pure(f), pure(x)) == pure(f(x))`
17/// - Interchange: `ap(u, pure(y)) == ap(pure(|f| f(y)), u)`
18pub trait Applicative: Apply {
19    fn pure<A>(a: A) -> Self::Of<A>;
20}
21
22impl Applicative for OptionF {
23    fn pure<A>(a: A) -> Option<A> {
24        Some(a)
25    }
26}
27
28impl<E> Applicative for ResultF<E> {
29    fn pure<A>(a: A) -> Result<A, E> {
30        Ok(a)
31    }
32}
33
34#[cfg(any(feature = "std", feature = "alloc"))]
35impl Applicative for VecF {
36    fn pure<A>(a: A) -> Vec<A> {
37        vec![a]
38    }
39}
40
41impl Applicative for crate::hkt::IdentityF {
42    fn pure<A>(a: A) -> A {
43        a
44    }
45}
46
47#[cfg(any(feature = "std", feature = "alloc"))]
48impl Applicative for crate::hkt::NonEmptyVecF {
49    fn pure<A>(a: A) -> crate::hkt::NonEmptyVec<A> {
50        crate::hkt::NonEmptyVec::singleton(a)
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn option_pure() {
60        assert_eq!(OptionF::pure(42), Some(42));
61    }
62
63    #[test]
64    fn result_pure() {
65        assert_eq!(ResultF::<String>::pure(42), Ok(42));
66    }
67
68    #[test]
69    fn vec_pure() {
70        assert_eq!(VecF::pure(42), vec![42]);
71    }
72}
73
74#[cfg(test)]
75mod law_tests {
76    use super::*;
77    use proptest::prelude::*;
78
79    proptest! {
80        // Identity: ap(pure(id), v) == v
81        #[test]
82        fn option_identity(x in any::<Option<i32>>()) {
83            let id_fn: Option<fn(i32) -> i32> = OptionF::pure(|a| a);
84            let result = OptionF::ap(id_fn, x.clone());
85            prop_assert_eq!(result, x);
86        }
87
88        // Homomorphism: ap(pure(f), pure(x)) == pure(f(x))
89        #[test]
90        fn option_homomorphism(x in any::<i32>()) {
91            let f = |a: i32| a.wrapping_add(1);
92            let left = OptionF::ap(OptionF::pure(f as fn(i32) -> i32), OptionF::pure(x));
93            let right = OptionF::pure(f(x));
94            prop_assert_eq!(left, right);
95        }
96
97        // Interchange: ap(u, pure(y)) == ap(pure(|f| f(y)), u)
98        #[test]
99        fn option_interchange(y in any::<i16>()) {
100            let u: Option<fn(i16) -> i16> = Some(|a| a.wrapping_mul(2));
101            let left = OptionF::ap(u, OptionF::pure(y));
102            let right = OptionF::ap(
103                OptionF::pure(move |f: fn(i16) -> i16| f(y)),
104                Some(|a: i16| a.wrapping_mul(2) as i16),
105            );
106            prop_assert_eq!(left, right);
107        }
108
109        #[test]
110        fn vec_identity(x in prop::collection::vec(any::<i32>(), 0..10)) {
111            let id_fn: Vec<fn(i32) -> i32> = VecF::pure(|a| a);
112            let result = VecF::ap(id_fn, x.clone());
113            prop_assert_eq!(result, x);
114        }
115
116        #[test]
117        fn vec_homomorphism(x in any::<i32>()) {
118            let f = |a: i32| a.wrapping_add(1);
119            let left = VecF::ap(VecF::pure(f as fn(i32) -> i32), VecF::pure(x));
120            let right = VecF::pure(f(x));
121            prop_assert_eq!(left, right);
122        }
123    }
124}