Skip to main content

karpal_core/
comonad_store.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::hkt::HKT;
5#[cfg(any(feature = "std", feature = "alloc"))]
6use crate::hkt::StoreF;
7#[cfg(all(not(feature = "std"), feature = "alloc"))]
8use alloc::boxed::Box;
9
10/// ComonadStore: a comonad with a notion of position and peeking.
11///
12/// Laws:
13/// - `peek(pos(wa), wa) == extract(wa)` (peeking at current position is extract)
14pub trait ComonadStore<S>: HKT {
15    fn pos<A>(wa: &Self::Of<A>) -> S;
16    fn peek<A>(s: S, wa: &Self::Of<A>) -> A;
17
18    /// Extract the focused value (equivalent to `peek(pos(wa), wa)`).
19    fn extract<A>(wa: &Self::Of<A>) -> A
20    where
21        S: Clone,
22    {
23        Self::peek(Self::pos(wa), wa)
24    }
25}
26
27#[cfg(any(feature = "std", feature = "alloc"))]
28impl<S: Clone + 'static> ComonadStore<S> for StoreF<S> {
29    fn pos<A>(wa: &(Box<dyn Fn(S) -> A>, S)) -> S {
30        wa.1.clone()
31    }
32
33    fn peek<A>(s: S, wa: &(Box<dyn Fn(S) -> A>, S)) -> A {
34        (wa.0)(s)
35    }
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn store_pos() {
44        let w: (Box<dyn Fn(i32) -> String>, i32) = (Box::new(|s| format!("value_{}", s)), 42);
45        assert_eq!(StoreF::<i32>::pos(&w), 42);
46    }
47
48    #[test]
49    fn store_peek() {
50        let w: (Box<dyn Fn(i32) -> String>, i32) = (Box::new(|s| format!("value_{}", s)), 42);
51        assert_eq!(StoreF::<i32>::peek(10, &w), "value_10");
52    }
53
54    #[test]
55    fn store_extract() {
56        let w: (Box<dyn Fn(i32) -> String>, i32) = (Box::new(|s| format!("value_{}", s)), 42);
57        assert_eq!(StoreF::<i32>::extract(&w), "value_42");
58    }
59}
60
61#[cfg(test)]
62mod law_tests {
63    use super::*;
64    use proptest::prelude::*;
65
66    proptest! {
67        // peek(pos(wa), wa) == extract(wa)
68        #[test]
69        fn store_peek_pos_is_extract(s in any::<i16>()) {
70            let w: (Box<dyn Fn(i16) -> i32>, i16) =
71                (Box::new(|s| s as i32 * 2), s);
72            let pos = StoreF::<i16>::pos(&w);
73            let left = StoreF::<i16>::peek(pos, &w);
74            let right = StoreF::<i16>::extract(&w);
75            prop_assert_eq!(left, right);
76        }
77    }
78}