Skip to main content

karpal_core/
adjunction.rs

1use crate::compose::ComposeF;
2use crate::functor::Functor;
3#[cfg(any(feature = "std", feature = "alloc"))]
4use crate::hkt::EnvF;
5use crate::hkt::{HKT, HKT2, IdentityF};
6
7#[cfg(all(not(feature = "std"), feature = "alloc"))]
8use alloc::rc::Rc;
9#[cfg(feature = "std")]
10use std::rc::Rc;
11
12/// An adjunction F ⊣ U between two type constructors.
13///
14/// This encodes the fundamental relationship where F is left adjoint to U,
15/// meaning there is a natural isomorphism:
16///
17/// ```text
18/// Hom(F(A), B) ≅ Hom(A, U(B))
19/// ```
20///
21/// Expressed via unit/counit:
22/// - `unit: A -> U(F(A))` — the universal arrow from A to U
23/// - `counit: F(U(B)) -> B` — the universal arrow from F to B
24///
25/// Laws (triangle identities):
26/// - `counit(F::fmap(fa, unit)) == fa` for all fa: F::Of<A>
27/// - `U::fmap(unit(a), counit) == a` for all a: U::Of<A>
28///
29/// Every adjunction F ⊣ U gives rise to:
30/// - A monad on `U . F` (via `unit` as pure, derived join)
31/// - A comonad on `F . U` (via `counit` as extract, derived duplicate)
32///
33/// Note: `F` and `U` are bounded by `HKT` rather than `Functor` because
34/// some useful right adjoints (like `ReaderF<E>`) cannot implement `Functor`
35/// due to `'static` limitations on `Box<dyn Fn>`. The `Clone` bound on
36/// `unit` is required because the right adjoint may need to reproduce the
37/// value (e.g., inside a reader function).
38pub trait Adjunction<F: HKT, U: HKT> {
39    /// unit: A -> U(F(A))
40    fn unit<A: Clone + 'static>(a: A) -> U::Of<F::Of<A>>;
41
42    /// counit: F(U(B)) -> B
43    fn counit<B: 'static>(fub: F::Of<U::Of<B>>) -> B;
44}
45
46/// left_adjunct: (F(A) -> B) -> (A -> U(B))
47///
48/// Derived from unit: `left_adjunct(f, a) = U::fmap(unit(a), f)`
49///
50/// Requires `U: Functor` — works for adjunctions where the right adjoint
51/// has a Functor implementation.
52pub fn left_adjunct<Adj, F, U, A, B>(f: impl Fn(F::Of<A>) -> B, a: A) -> U::Of<B>
53where
54    F: HKT,
55    U: Functor,
56    A: Clone + 'static,
57    Adj: Adjunction<F, U>,
58{
59    U::fmap(Adj::unit(a), f)
60}
61
62/// right_adjunct: (A -> U(B)) -> (F(A) -> B)
63///
64/// Derived from counit: `right_adjunct(f, fa) = counit(F::fmap(fa, f))`
65///
66/// Requires `F: Functor` — works for adjunctions where the left adjoint
67/// has a Functor implementation.
68pub fn right_adjunct<Adj, F, U, A, B>(f: impl Fn(A) -> U::Of<B>, fa: F::Of<A>) -> B
69where
70    F: Functor,
71    U: HKT,
72    B: 'static,
73    Adj: Adjunction<F, U>,
74{
75    Adj::counit(F::fmap(fa, f))
76}
77
78// ---------------------------------------------------------------------------
79// Trivial adjunction: IdentityF ⊣ IdentityF
80// ---------------------------------------------------------------------------
81
82/// Witness for the trivial adjunction `IdentityF ⊣ IdentityF`.
83///
84/// unit and counit are both the identity function.
85pub struct IdentityAdj;
86
87impl Adjunction<IdentityF, IdentityF> for IdentityAdj {
88    fn unit<A: Clone + 'static>(a: A) -> A {
89        a
90    }
91
92    fn counit<B: 'static>(b: B) -> B {
93        b
94    }
95}
96
97// ---------------------------------------------------------------------------
98// Product/Exponential adjunction: EnvF<E> ⊣ ReaderF<E>
99// ---------------------------------------------------------------------------
100
101/// Witness for the curry/uncurry adjunction `EnvF<E> ⊣ ReaderF<E>`.
102///
103/// This is the product-exponential adjunction:
104/// - `unit: A -> (E -> (E, A))` — pairs a value with an environment
105/// - `counit: (E, E -> B) -> B` — applies a reader to its environment
106///
107/// The derived monad `ReaderF<E> . EnvF<E>` is the State monad:
108/// `Of<A> = E -> (E, A)`.
109///
110/// The derived comonad `EnvF<E> . ReaderF<E>` is the Store comonad:
111/// `Of<A> = (E, E -> A)`.
112#[cfg(any(feature = "std", feature = "alloc"))]
113pub struct CurryAdj<E>(core::marker::PhantomData<E>);
114
115#[cfg(any(feature = "std", feature = "alloc"))]
116impl<E: Clone + 'static> Adjunction<EnvF<E>, crate::hkt::ReaderF<E>> for CurryAdj<E> {
117    fn unit<A: Clone + 'static>(a: A) -> Box<dyn Fn(E) -> (E, A)> {
118        Box::new(move |e| (e, a.clone()))
119    }
120
121    fn counit<B: 'static>(fub: (E, Box<dyn Fn(E) -> B>)) -> B {
122        let (e, f) = fub;
123        f(e)
124    }
125}
126
127// ---------------------------------------------------------------------------
128// Monad from adjunction: U . F (requires both F and U to be Functor)
129// ---------------------------------------------------------------------------
130
131/// Derive monadic `pure` from an adjunction F ⊣ U.
132///
133/// `pure = unit: A -> U(F(A)) = (U . F)(A)`
134pub fn adjunction_pure<Adj, F, U, A>(a: A) -> U::Of<F::Of<A>>
135where
136    F: HKT,
137    U: HKT,
138    A: Clone + 'static,
139    Adj: Adjunction<F, U>,
140{
141    Adj::unit(a)
142}
143
144#[allow(clippy::type_complexity)]
145/// Derive monadic `join` from an adjunction F ⊣ U.
146///
147/// `join: U(F(U(F(A)))) -> U(F(A))`
148///
149/// Applies `counit` inside the outer U layer to collapse the inner `F(U(...))`.
150///
151/// Requires `U: Functor` to map counit over the outer layer.
152pub fn adjunction_join<Adj, F, U, A>(ufufa: U::Of<F::Of<U::Of<F::Of<A>>>>) -> U::Of<F::Of<A>>
153where
154    F: HKT,
155    U: Functor,
156    A: 'static,
157    Adj: Adjunction<F, U>,
158    F::Of<A>: 'static,
159    U::Of<F::Of<A>>: 'static,
160    F::Of<U::Of<F::Of<A>>>: 'static,
161{
162    U::fmap(ufufa, |fufa: F::Of<U::Of<F::Of<A>>>| Adj::counit(fufa))
163}
164
165#[allow(clippy::type_complexity)]
166/// Derive monadic `chain` (bind) from an adjunction F ⊣ U.
167///
168/// `chain m f = join (fmap f m)` where the monad is `U . F`.
169///
170/// Requires both `U` and `F` to be Functor (for ComposeF::fmap and join).
171pub fn adjunction_chain<Adj, F, U, A, B>(
172    ufa: U::Of<F::Of<A>>,
173    f: impl Fn(A) -> U::Of<F::Of<B>> + 'static,
174) -> U::Of<F::Of<B>>
175where
176    F: Functor,
177    U: Functor,
178    A: 'static,
179    B: 'static,
180    Adj: Adjunction<F, U>,
181    F::Of<B>: 'static,
182    U::Of<F::Of<B>>: 'static,
183    F::Of<U::Of<F::Of<B>>>: 'static,
184{
185    let mapped: U::Of<F::Of<U::Of<F::Of<B>>>> = ComposeF::<U, F>::fmap(ufa, f);
186    adjunction_join::<Adj, F, U, B>(mapped)
187}
188
189// ---------------------------------------------------------------------------
190// Comonad from adjunction: F . U
191// ---------------------------------------------------------------------------
192
193/// Derive comonadic `extract` from an adjunction F ⊣ U.
194///
195/// `extract = counit: F(U(A)) -> A`
196pub fn adjunction_extract<Adj, F, U, A>(fua: F::Of<U::Of<A>>) -> A
197where
198    F: HKT,
199    U: HKT,
200    A: 'static,
201    Adj: Adjunction<F, U>,
202{
203    Adj::counit(fua)
204}
205
206#[allow(clippy::type_complexity)]
207/// Derive comonadic `duplicate` from an adjunction F ⊣ U.
208///
209/// `duplicate: F(U(A)) -> F(U(F(U(A))))`
210///
211/// Applies `unit` inside the outer F layer to expand `U(A)` into `U(F(U(A)))`.
212///
213/// Requires `F: Functor` to map unit over the outer layer.
214pub fn adjunction_duplicate<Adj, F, U, A>(fua: F::Of<U::Of<A>>) -> F::Of<U::Of<F::Of<U::Of<A>>>>
215where
216    F: Functor,
217    U: HKT,
218    A: 'static,
219    Adj: Adjunction<F, U>,
220    U::Of<A>: Clone + 'static,
221{
222    F::fmap(fua, |ua: U::Of<A>| Adj::unit(ua))
223}
224
225/// Derive comonadic `extend` from an adjunction F ⊣ U.
226///
227/// `extend f = fmap f . duplicate`
228///
229/// Requires `F: Functor` (for both duplicate and the outer fmap).
230pub fn adjunction_extend<Adj, F, U, A, B>(
231    fua: F::Of<U::Of<A>>,
232    f: impl Fn(F::Of<U::Of<A>>) -> B + 'static,
233) -> F::Of<U::Of<B>>
234where
235    F: Functor,
236    U: Functor,
237    A: 'static,
238    B: Clone + 'static,
239    Adj: Adjunction<F, U>,
240    U::Of<A>: Clone + 'static,
241    F::Of<U::Of<A>>: 'static,
242{
243    let duplicated = adjunction_duplicate::<Adj, F, U, A>(fua);
244    ComposeF::<F, U>::fmap(duplicated, f)
245}
246
247// ---------------------------------------------------------------------------
248// CurryAdj-specific helpers (ReaderF can't implement Functor)
249// ---------------------------------------------------------------------------
250
251// ---------------------------------------------------------------------------
252// CurryAdj-specific adjunct helpers (using ReaderF's inherent methods)
253// ---------------------------------------------------------------------------
254
255/// CurryAdj left_adjunct: `((E, A) -> B) -> (A -> (E -> B))`
256///
257/// Curries a function that takes a pair into a function returning a reader.
258/// Uses `ReaderF::fmap` internally (the Lan workaround for `'static`).
259#[cfg(any(feature = "std", feature = "alloc"))]
260pub fn curry_left_adjunct<E: Clone + 'static, A: Clone + 'static, B: 'static>(
261    f: impl Fn((E, A)) -> B + 'static,
262    a: A,
263) -> Box<dyn Fn(E) -> B> {
264    crate::hkt::ReaderF::<E>::fmap(CurryAdj::<E>::unit(a), f)
265}
266
267/// CurryAdj right_adjunct: `(A -> (E -> B)) -> ((E, A) -> B)`
268///
269/// Uncurries a function returning a reader into one that takes a pair.
270/// Uses `EnvF::fmap` (which implements `Functor`) + `counit`.
271#[cfg(any(feature = "std", feature = "alloc"))]
272pub fn curry_right_adjunct<E: Clone + 'static, A: 'static, B: 'static>(
273    f: impl Fn(A) -> Box<dyn Fn(E) -> B> + 'static,
274    pair: (E, A),
275) -> B {
276    CurryAdj::<E>::counit(<EnvF<E> as Functor>::fmap(pair, f))
277}
278
279// ---------------------------------------------------------------------------
280// State monad (derived from CurryAdj: ReaderF<E> . EnvF<E>)
281// ---------------------------------------------------------------------------
282
283/// State monad `pure` via CurryAdj: `A -> (E -> (E, A))`
284#[cfg(any(feature = "std", feature = "alloc"))]
285pub fn state_pure<E: Clone + 'static, A: Clone + 'static>(a: A) -> Box<dyn Fn(E) -> (E, A)> {
286    CurryAdj::<E>::unit(a)
287}
288
289/// State monad `fmap` via CurryAdj: post-compose over the value component.
290///
291/// `state_fmap(f, sa) = |e| let (e', a) = sa(e) in (e', f(a))`
292#[cfg(any(feature = "std", feature = "alloc"))]
293pub fn state_fmap<E: Clone + 'static, A: 'static, B: 'static>(
294    f: impl Fn(A) -> B + 'static,
295    sa: Box<dyn Fn(E) -> (E, A)>,
296) -> Box<dyn Fn(E) -> (E, B)> {
297    crate::hkt::ReaderF::<E>::fmap(sa, move |(e, a)| (e, f(a)))
298}
299
300/// State monad `chain` (bind) via CurryAdj:
301/// `(E -> (E, A)) -> (A -> (E -> (E, B))) -> (E -> (E, B))`
302///
303/// Threads the modified state: `|e| let (e', a) = ma(e) in f(a)(e')`
304///
305/// Note: this is NOT `ReaderF::chain` — Reader's bind passes the same
306/// environment to both, while State threads modified state through.
307#[cfg(any(feature = "std", feature = "alloc"))]
308pub fn state_chain<E: Clone + 'static, A: 'static, B: 'static>(
309    ma: Box<dyn Fn(E) -> (E, A)>,
310    f: impl Fn(A) -> Box<dyn Fn(E) -> (E, B)> + 'static,
311) -> Box<dyn Fn(E) -> (E, B)> {
312    Box::new(move |e| {
313        let (e2, a) = ma(e);
314        f(a)(e2)
315    })
316}
317
318/// State monad `get`: `E -> (E, E)`
319#[cfg(any(feature = "std", feature = "alloc"))]
320pub fn state_get<E: Clone + 'static>() -> Box<dyn Fn(E) -> (E, E)> {
321    Box::new(|e: E| {
322        let e2 = e.clone();
323        (e, e2)
324    })
325}
326
327/// State monad `put`: `E -> (E -> (E, ()))`
328#[cfg(any(feature = "std", feature = "alloc"))]
329pub fn state_put<E: Clone + 'static>(e: E) -> Box<dyn Fn(E) -> (E, ())> {
330    Box::new(move |_| (e.clone(), ()))
331}
332
333/// State monad `modify`: `(E -> E) -> (E -> (E, ()))`
334#[cfg(any(feature = "std", feature = "alloc"))]
335pub fn state_modify<E: Clone + 'static>(f: impl Fn(E) -> E + 'static) -> Box<dyn Fn(E) -> (E, ())> {
336    Box::new(move |e| (f(e), ()))
337}
338
339// ---------------------------------------------------------------------------
340// Store comonad (derived from CurryAdj: EnvF<E> . ReaderF<E>)
341// ---------------------------------------------------------------------------
342
343/// Store comonad `extract` via CurryAdj: `(E, E -> A) -> A`
344#[cfg(any(feature = "std", feature = "alloc"))]
345pub fn store_extract<E: Clone + 'static, A: 'static>(store: (E, Box<dyn Fn(E) -> A>)) -> A {
346    CurryAdj::<E>::counit(store)
347}
348
349/// Store comonad `extend` via CurryAdj: maps over all positions.
350///
351/// `extend(f, (pos, peek)) = (pos, |e| f((e, peek)))`
352#[cfg(any(feature = "std", feature = "alloc"))]
353pub fn store_extend<E: Clone + 'static, A: 'static, B: 'static>(
354    store: (E, Box<dyn Fn(E) -> A>),
355    f: impl Fn((E, &dyn Fn(E) -> A)) -> B + 'static,
356) -> (E, Box<dyn Fn(E) -> B>) {
357    let pos = store.0.clone();
358    let peek = store.1;
359    let new_peek: Box<dyn Fn(E) -> B> = Box::new(move |e| f((e, peek.as_ref())));
360    (pos, new_peek)
361}
362
363/// Store comonad `peek`: read at a specific position.
364///
365/// `peek(pos, (_, f)) = f(pos)`
366#[cfg(any(feature = "std", feature = "alloc"))]
367pub fn store_peek<E: Clone + 'static, A: 'static>(pos: E, store: &(E, Box<dyn Fn(E) -> A>)) -> A {
368    (store.1)(pos)
369}
370
371/// Store comonad `pos`: get the current position.
372///
373/// `pos((e, _)) = e`
374#[cfg(any(feature = "std", feature = "alloc"))]
375pub fn store_pos<E: Clone, A>(store: &(E, Box<dyn Fn(E) -> A>)) -> E {
376    store.0.clone()
377}
378
379// ===========================================================================
380// Contravariant Adjunction: Op_R ⊣ Op_R (self-adjoint)
381// ===========================================================================
382
383/// A contravariant functor type constructor: `Of<A> = Box<dyn Fn(A) -> R>`.
384///
385/// This is the "Op" or "Cont" functor, generalizing `PredicateF` (which is `ContF<bool>`).
386/// It is contravariant: given `f: B -> A`, we get `ContF<R>::Of<A> -> ContF<R>::Of<B>`
387/// by pre-composing with `f`.
388///
389/// `ContF<R>` is self-adjoint as a contravariant functor, giving rise to the
390/// continuation monad `(A -> R) -> R` via `ContF<R> . ContF<R>`.
391#[cfg(any(feature = "std", feature = "alloc"))]
392pub struct ContF<R>(core::marker::PhantomData<R>);
393
394#[cfg(any(feature = "std", feature = "alloc"))]
395impl<R: 'static> HKT for ContF<R> {
396    type Of<T> = Box<dyn Fn(T) -> R>;
397}
398
399#[cfg(any(feature = "std", feature = "alloc"))]
400impl<R: 'static> crate::contravariant::Contravariant for ContF<R> {
401    fn contramap<A: 'static, B>(fa: Self::Of<A>, f: impl Fn(B) -> A + 'static) -> Self::Of<B> {
402        Box::new(move |b| fa(f(b)))
403    }
404}
405
406/// An adjunction between contravariant functors F and G.
407///
408/// For contravariant functors, the adjunction is:
409///
410/// ```text
411/// Hom(F(A), B) ≅ Hom(G(B), A)
412/// ```
413///
414/// expressed via unit/counit that reverse variance:
415/// - `unit: A -> G(F(A))` — note G(F(A)) is covariant (two contravariants compose to covariant)
416/// - `counit: B -> F(G(B))` — same structure
417///
418/// The primary instance is the self-adjunction `ContF<R> ⊣ ContF<R>`,
419/// where `unit(a) = |k| k(a)` and `counit(b) = |k| k(b)` are the same operation.
420/// This gives rise to the continuation monad `(A -> R) -> R`.
421#[cfg(any(feature = "std", feature = "alloc"))]
422pub trait ContravariantAdjunction<F: HKT, G: HKT> {
423    /// unit: A -> G(F(A))
424    ///
425    /// For the `ContF<R>` self-adjunction: `unit(a) = |k| k(a)`
426    fn unit<A: Clone + 'static>(a: A) -> G::Of<F::Of<A>>;
427
428    /// counit: B -> F(G(B))
429    ///
430    /// For the `ContF<R>` self-adjunction: `counit(b) = |k| k(b)`
431    fn counit<B: Clone + 'static>(b: B) -> F::Of<G::Of<B>>;
432}
433
434/// Witness for the self-adjunction `ContF<R> ⊣ ContF<R>`.
435///
436/// This is the canonical contravariant adjunction. The composed functor
437/// `ContF<R> . ContF<R>` gives `Of<A> = (A -> R) -> R`, the continuation monad.
438///
439/// - `unit(a) = |k| k(a)` (embed a value into CPS)
440/// - `counit(b) = |k| k(b)` (same operation — self-adjoint!)
441#[cfg(any(feature = "std", feature = "alloc"))]
442pub struct ContAdj<R>(core::marker::PhantomData<R>);
443
444#[cfg(any(feature = "std", feature = "alloc"))]
445impl<R: 'static> ContravariantAdjunction<ContF<R>, ContF<R>> for ContAdj<R> {
446    fn unit<A: Clone + 'static>(a: A) -> Box<dyn Fn(Box<dyn Fn(A) -> R>) -> R> {
447        Box::new(move |k: Box<dyn Fn(A) -> R>| k(a.clone()))
448    }
449
450    fn counit<B: Clone + 'static>(b: B) -> Box<dyn Fn(Box<dyn Fn(B) -> R>) -> R> {
451        Box::new(move |k: Box<dyn Fn(B) -> R>| k(b.clone()))
452    }
453}
454
455/// Continuation monad `pure`: embed a value into CPS.
456///
457/// `cont_pure(a) = |k| k(a)`
458#[cfg(any(feature = "std", feature = "alloc"))]
459#[allow(clippy::type_complexity)]
460pub fn cont_pure<R: 'static, A: Clone + 'static>(a: A) -> Box<dyn Fn(Box<dyn Fn(A) -> R>) -> R> {
461    ContAdj::<R>::unit(a)
462}
463
464/// Continuation monad `fmap`: post-compose inside CPS.
465///
466/// `cont_fmap(f, m) = |k| m(|a| k(f(a)))`
467#[cfg(any(feature = "std", feature = "alloc"))]
468#[allow(clippy::type_complexity)]
469pub fn cont_fmap<R: 'static, A: 'static, B: 'static>(
470    f: impl Fn(A) -> B + 'static,
471    m: Box<dyn Fn(Box<dyn Fn(A) -> R>) -> R>,
472) -> Box<dyn Fn(Box<dyn Fn(B) -> R>) -> R> {
473    let f_rc = Rc::new(f);
474    Box::new(move |k: Box<dyn Fn(B) -> R>| {
475        let f_inner = f_rc.clone();
476        let k_rc = Rc::new(k);
477        let k_composed: Box<dyn Fn(A) -> R> = Box::new(move |a| k_rc(f_inner(a)));
478        m(k_composed)
479    })
480}
481
482/// Continuation monad `chain` (bind): sequence CPS computations.
483///
484/// `cont_chain(m, f) = |k| m(|a| f(a)(k))`
485#[cfg(any(feature = "std", feature = "alloc"))]
486#[allow(clippy::type_complexity)]
487pub fn cont_chain<R: 'static, A: 'static, B: 'static>(
488    m: Box<dyn Fn(Box<dyn Fn(A) -> R>) -> R>,
489    f: impl Fn(A) -> Box<dyn Fn(Box<dyn Fn(B) -> R>) -> R> + 'static,
490) -> Box<dyn Fn(Box<dyn Fn(B) -> R>) -> R> {
491    let f_rc = Rc::new(f);
492    Box::new(move |k: Box<dyn Fn(B) -> R>| {
493        let k_rc = Rc::new(k);
494        let k_inner = k_rc.clone();
495        let f_inner = f_rc.clone();
496        let inner: Box<dyn Fn(A) -> R> = Box::new(move |a| {
497            let cont_b = f_inner(a);
498            let k_ref = k_inner.clone();
499            let k_box: Box<dyn Fn(B) -> R> = Box::new(move |b| k_ref(b));
500            cont_b(k_box)
501        });
502        m(inner)
503    })
504}
505
506/// Continuation monad `call_cc`: call-with-current-continuation.
507///
508/// `call_cc(f) = |k| f(|a| |_| k(a))(k)`
509///
510/// The escape continuation `|a| |_| k(a)` ignores its own continuation
511/// and jumps directly to `k`.
512#[cfg(any(feature = "std", feature = "alloc"))]
513#[allow(clippy::type_complexity)]
514pub fn cont_call_cc<R: 'static, A: Clone + 'static, B: 'static>(
515    f: impl Fn(
516        Box<dyn Fn(A) -> Box<dyn Fn(Box<dyn Fn(B) -> R>) -> R>>,
517    ) -> Box<dyn Fn(Box<dyn Fn(A) -> R>) -> R>
518    + 'static,
519) -> Box<dyn Fn(Box<dyn Fn(A) -> R>) -> R> {
520    Box::new(move |k: Box<dyn Fn(A) -> R>| {
521        let k_rc = Rc::new(k);
522        let k_for_escape = k_rc.clone();
523        let escape: Box<dyn Fn(A) -> Box<dyn Fn(Box<dyn Fn(B) -> R>) -> R>> =
524            Box::new(move |a: A| -> Box<dyn Fn(Box<dyn Fn(B) -> R>) -> R> {
525                let k_esc = k_for_escape.clone();
526                Box::new(move |_: Box<dyn Fn(B) -> R>| k_esc(a.clone()))
527            });
528        let k_box: Box<dyn Fn(A) -> R> = Box::new(move |a| k_rc(a));
529        f(escape)(k_box)
530    })
531}
532
533/// Run a continuation computation by supplying the final continuation.
534///
535/// `cont_run(m, k) = m(k)`
536#[cfg(any(feature = "std", feature = "alloc"))]
537pub fn cont_run<R, A>(m: &dyn Fn(Box<dyn Fn(A) -> R>) -> R, k: impl Fn(A) -> R + 'static) -> R {
538    m(Box::new(k))
539}
540
541// ===========================================================================
542// Profunctor Adjunction
543// ===========================================================================
544
545/// A type-level functor on the category of profunctors.
546///
547/// Maps a profunctor `P` to another profunctor `Applied<P>`.
548/// This serves as an HKT3-like encoding using GATs: the "third parameter"
549/// is the profunctor `P` itself.
550///
551/// Instances include identity (maps P to P) and profunctor transformers
552/// like Tambara, Pastro, etc.
553pub trait ProfunctorFunctor {
554    /// Apply this functor to a profunctor, yielding another profunctor.
555    type Applied<P: HKT2>: HKT2;
556}
557
558/// An adjunction F ⊣ U in the category of profunctors.
559///
560/// ```text
561/// ProfHom(F(P), Q) ≅ ProfHom(P, U(Q))
562/// ```
563///
564/// Expressed via unit/counit natural transformations between profunctors:
565/// - `unit: P<A,B> -> U(F(P))<A,B>` for all profunctors P
566/// - `counit: F(U(Q))<A,B> -> Q<A,B>` for all profunctors Q
567///
568/// The primary instance is the identity adjunction `Id ⊣ Id`.
569/// Non-trivial instances (e.g., `Pastro ⊣ Tambara`) require profunctor
570/// transformer types.
571pub trait ProfunctorAdjunction<F: ProfunctorFunctor, U: ProfunctorFunctor> {
572    /// unit: P<A,B> -> U(F(P))<A,B>
573    fn unit<P: HKT2, A: 'static, B: 'static>(
574        pab: P::P<A, B>,
575    ) -> <U::Applied<F::Applied<P>> as HKT2>::P<A, B>;
576
577    /// counit: F(U(Q))<A,B> -> Q<A,B>
578    fn counit<Q: HKT2, A: 'static, B: 'static>(
579        fuqab: <F::Applied<U::Applied<Q>> as HKT2>::P<A, B>,
580    ) -> Q::P<A, B>;
581}
582
583/// The identity profunctor functor: maps P to P.
584pub struct ProfunctorIdentityF;
585
586impl ProfunctorFunctor for ProfunctorIdentityF {
587    type Applied<P: HKT2> = P;
588}
589
590/// Witness for the identity profunctor adjunction: Id ⊣ Id.
591///
592/// Both unit and counit are the identity: `P<A,B> -> P<A,B>`.
593pub struct ProfunctorIdentityAdj;
594
595impl ProfunctorAdjunction<ProfunctorIdentityF, ProfunctorIdentityF> for ProfunctorIdentityAdj {
596    fn unit<P: HKT2, A: 'static, B: 'static>(pab: P::P<A, B>) -> P::P<A, B> {
597        pab
598    }
599
600    fn counit<Q: HKT2, A: 'static, B: 'static>(fuqab: Q::P<A, B>) -> Q::P<A, B> {
601        fuqab
602    }
603}
604
605#[cfg(test)]
606mod tests {
607    use super::*;
608
609    // --- IdentityAdj tests ---
610
611    #[test]
612    fn identity_adj_unit() {
613        let result = IdentityAdj::unit(42);
614        assert_eq!(result, 42);
615    }
616
617    #[test]
618    fn identity_adj_counit() {
619        let result = IdentityAdj::counit(42);
620        assert_eq!(result, 42);
621    }
622
623    #[test]
624    fn identity_adj_left_adjunct() {
625        let f = |x: i32| x + 1;
626        let result = left_adjunct::<IdentityAdj, IdentityF, IdentityF, _, _>(f, 5);
627        assert_eq!(result, 6);
628    }
629
630    #[test]
631    fn identity_adj_right_adjunct() {
632        let f = |x: i32| x * 2;
633        let result = right_adjunct::<IdentityAdj, IdentityF, IdentityF, _, _>(f, 5);
634        assert_eq!(result, 10);
635    }
636
637    #[test]
638    fn identity_adj_monad_pure() {
639        let result = adjunction_pure::<IdentityAdj, IdentityF, IdentityF, i32>(42);
640        assert_eq!(result, 42);
641    }
642
643    #[test]
644    fn identity_adj_monad_join() {
645        let result = adjunction_join::<IdentityAdj, IdentityF, IdentityF, i32>(42);
646        assert_eq!(result, 42);
647    }
648
649    #[test]
650    fn identity_adj_monad_chain() {
651        let result = adjunction_chain::<IdentityAdj, IdentityF, IdentityF, i32, i32>(5, |x| x + 1);
652        assert_eq!(result, 6);
653    }
654
655    #[test]
656    fn identity_adj_comonad_extract() {
657        let result = adjunction_extract::<IdentityAdj, IdentityF, IdentityF, i32>(42);
658        assert_eq!(result, 42);
659    }
660
661    #[test]
662    fn identity_adj_comonad_duplicate() {
663        let result = adjunction_duplicate::<IdentityAdj, IdentityF, IdentityF, i32>(42);
664        assert_eq!(result, 42);
665    }
666
667    // --- CurryAdj tests ---
668
669    #[cfg(any(feature = "std", feature = "alloc"))]
670    mod curry_adj_tests {
671        use super::*;
672
673        #[test]
674        fn curry_adj_unit() {
675            let reader = CurryAdj::<i32>::unit(42i32);
676            assert_eq!(reader(10), (10, 42));
677            assert_eq!(reader(0), (0, 42));
678        }
679
680        #[test]
681        fn curry_adj_counit() {
682            let env_reader: (i32, Box<dyn Fn(i32) -> String>) =
683                (5, Box::new(|e| format!("env={}", e)));
684            let result = CurryAdj::<i32>::counit(env_reader);
685            assert_eq!(result, "env=5");
686        }
687
688        #[test]
689        fn curry_adj_monad_pure() {
690            let state_fn = state_pure::<i32, String>("hello".to_string());
691            assert_eq!(state_fn(42), (42, "hello".to_string()));
692        }
693
694        #[test]
695        fn curry_adj_state_chain() {
696            // State monad: increment the state and return the old value
697            let get_and_inc = state_chain(
698                state_get::<i32>(),
699                |old: i32| -> Box<dyn Fn(i32) -> (i32, i32)> { Box::new(move |e| (e + 1, old)) },
700            );
701            assert_eq!(get_and_inc(10), (11, 10));
702            assert_eq!(get_and_inc(0), (1, 0));
703        }
704
705        #[test]
706        fn curry_adj_state_get() {
707            let getter = state_get::<i32>();
708            assert_eq!(getter(42), (42, 42));
709        }
710
711        #[test]
712        fn curry_adj_state_put() {
713            let putter = state_put(99i32);
714            assert_eq!(putter(0), (99, ()));
715            assert_eq!(putter(42), (99, ()));
716        }
717
718        #[test]
719        fn curry_adj_store_extract() {
720            let store: (i32, Box<dyn Fn(i32) -> String>) = (5, Box::new(|e| format!("val={}", e)));
721            let result = store_extract(store);
722            assert_eq!(result, "val=5");
723        }
724
725        #[test]
726        fn curry_adj_store_extend() {
727            let store: (i32, Box<dyn Fn(i32) -> i32>) = (3, Box::new(|e| e * 2));
728            let extended = store_extend(store, |(pos, peek)| peek(pos) + 1);
729            assert_eq!(extended.0, 3); // position unchanged
730            assert_eq!((extended.1)(3), 7); // peek(3) + 1 = 6 + 1
731            assert_eq!((extended.1)(5), 11); // peek(5) + 1 = 10 + 1
732        }
733
734        #[test]
735        fn curry_adj_state_monad_left_identity() {
736            // chain(pure(a), f) == f(a)
737            let a = 42i32;
738            let f = |x: i32| -> Box<dyn Fn(i32) -> (i32, i32)> { Box::new(move |e| (e, x + 1)) };
739            let chained = state_chain(state_pure(a), f);
740            let direct = f(a);
741            for e in [0, 1, 10, -5] {
742                assert_eq!(chained(e), direct(e));
743            }
744        }
745
746        #[test]
747        fn curry_adj_state_monad_right_identity() {
748            // chain(m, pure) == m
749            let m_fn = |e: i32| (e + 1, e * 2);
750            let chained = state_chain(
751                Box::new(move |e: i32| (e + 1, e * 2)) as Box<dyn Fn(i32) -> (i32, i32)>,
752                |a| state_pure(a),
753            );
754            for e in [0, 1, 10, -5] {
755                assert_eq!(chained(e), m_fn(e));
756            }
757        }
758
759        // --- ReaderF inherent methods ---
760
761        #[test]
762        fn reader_fmap() {
763            let reader: Box<dyn Fn(i32) -> i32> = Box::new(|e| e * 2);
764            let mapped = crate::hkt::ReaderF::<i32>::fmap(reader, |x| x + 1);
765            assert_eq!(mapped(5), 11); // (5 * 2) + 1
766        }
767
768        #[test]
769        fn reader_pure() {
770            let reader = crate::hkt::ReaderF::<i32>::pure(42);
771            assert_eq!(reader(0), 42);
772            assert_eq!(reader(999), 42);
773        }
774
775        #[test]
776        fn reader_chain() {
777            let reader: Box<dyn Fn(i32) -> i32> = Box::new(|e| e + 1);
778            let chained = crate::hkt::ReaderF::<i32>::chain(reader, |a| {
779                Box::new(move |e| a * e) as Box<dyn Fn(i32) -> i32>
780            });
781            // reader(5) = 6, then |e| 6 * e applied to 5 = 30
782            assert_eq!(chained(5), 30);
783        }
784
785        #[test]
786        fn reader_ask() {
787            let reader = crate::hkt::ReaderF::<String>::ask();
788            assert_eq!(reader("hello".to_string()), "hello".to_string());
789        }
790
791        #[test]
792        fn reader_local() {
793            let reader: Box<dyn Fn(i32) -> i32> = Box::new(|e| e * 2);
794            let localized = crate::hkt::ReaderF::<i32>::local(|e| e + 10, reader);
795            assert_eq!(localized(5), 30); // (5 + 10) * 2
796        }
797
798        // --- CurryAdj adjunct helpers ---
799
800        #[test]
801        fn curry_left_adjunct_test() {
802            let f = |pair: (i32, i32)| pair.0 + pair.1;
803            let reader = curry_left_adjunct(f, 10i32);
804            assert_eq!(reader(5), 15); // 5 + 10
805            assert_eq!(reader(3), 13); // 3 + 10
806        }
807
808        #[test]
809        fn curry_right_adjunct_test() {
810            let f = |a: i32| -> Box<dyn Fn(i32) -> i32> { Box::new(move |e| e * a) };
811            let result = curry_right_adjunct(f, (3i32, 5i32));
812            assert_eq!(result, 15); // 3 * 5
813        }
814
815        // --- State fmap and modify ---
816
817        #[test]
818        fn state_fmap_test() {
819            let sa: Box<dyn Fn(i32) -> (i32, i32)> = Box::new(|e| (e + 1, e * 2));
820            let mapped = state_fmap(|x| x + 100, sa);
821            assert_eq!(mapped(5), (6, 110)); // state=6, value=(5*2)+100
822        }
823
824        #[test]
825        fn state_modify_test() {
826            let modifier = state_modify(|e: i32| e + 10);
827            assert_eq!(modifier(5), (15, ()));
828        }
829
830        // --- Store peek and pos ---
831
832        #[test]
833        fn store_peek_test() {
834            let store: (i32, Box<dyn Fn(i32) -> String>) = (3, Box::new(|e| format!("v{}", e)));
835            assert_eq!(store_peek(3, &store), "v3");
836            assert_eq!(store_peek(7, &store), "v7");
837        }
838
839        #[test]
840        fn store_pos_test() {
841            let store: (i32, Box<dyn Fn(i32) -> i32>) = (42, Box::new(|e| e * 2));
842            assert_eq!(store_pos(&store), 42);
843        }
844    }
845}
846
847#[cfg(test)]
848mod law_tests {
849    use super::*;
850    use proptest::prelude::*;
851
852    proptest! {
853        // Triangle identity 1 (for IdentityAdj):
854        // right_adjunct(unit, fa) == fa
855        #[test]
856        fn identity_adj_triangle_1(x in any::<i32>()) {
857            let result = right_adjunct::<IdentityAdj, IdentityF, IdentityF, _, _>(
858                IdentityAdj::unit, x,
859            );
860            prop_assert_eq!(result, x);
861        }
862
863        // Triangle identity 2 (for IdentityAdj):
864        // left_adjunct(counit, a) == a
865        #[test]
866        fn identity_adj_triangle_2(x in any::<i32>()) {
867            let result = left_adjunct::<IdentityAdj, IdentityF, IdentityF, _, _>(
868                IdentityAdj::counit, x,
869            );
870            prop_assert_eq!(result, x);
871        }
872
873        // left_adjunct and right_adjunct are inverses (IdentityAdj)
874        #[test]
875        fn identity_adj_adjuncts_inverse(x in any::<i16>()) {
876            let f = |a: i16| a.wrapping_add(1);
877            let la = left_adjunct::<IdentityAdj, IdentityF, IdentityF, _, _>(f, x);
878            prop_assert_eq!(la, f(x));
879
880            let g = |a: i16| a.wrapping_mul(2);
881            let ra = right_adjunct::<IdentityAdj, IdentityF, IdentityF, _, _>(g, x);
882            prop_assert_eq!(ra, g(x));
883        }
884
885        // Monad laws for IdentityAdj (U.F = IdentityF):
886        // Left identity: chain(pure(a), f) == f(a)
887        #[test]
888        fn identity_adj_monad_left_identity(x in any::<i32>()) {
889            let f = |a: i32| a.wrapping_add(1);
890            let pure_x = adjunction_pure::<IdentityAdj, IdentityF, IdentityF, i32>(x);
891            let result = adjunction_chain::<IdentityAdj, IdentityF, IdentityF, i32, i32>(
892                pure_x, f,
893            );
894            prop_assert_eq!(result, f(x));
895        }
896
897        // Right identity: chain(m, pure) == m
898        #[test]
899        fn identity_adj_monad_right_identity(x in any::<i32>()) {
900            let result = adjunction_chain::<IdentityAdj, IdentityF, IdentityF, i32, i32>(
901                x,
902                adjunction_pure::<IdentityAdj, IdentityF, IdentityF, i32>,
903            );
904            prop_assert_eq!(result, x);
905        }
906
907        // Comonad laws for IdentityAdj (F.U = IdentityF):
908        // extract(duplicate(w)) == w
909        #[test]
910        fn identity_adj_comonad_extract_duplicate(x in any::<i32>()) {
911            let dup = adjunction_duplicate::<IdentityAdj, IdentityF, IdentityF, i32>(x);
912            let result = adjunction_extract::<IdentityAdj, IdentityF, IdentityF, i32>(dup);
913            prop_assert_eq!(result, x);
914        }
915    }
916
917    #[cfg(any(feature = "std", feature = "alloc"))]
918    mod curry_adj_law_tests {
919        use super::*;
920
921        proptest! {
922            // Triangle identity for CurryAdj:
923            // counit(EnvF::fmap((e, a), unit)) == (e, a)
924            #[test]
925            fn curry_adj_triangle_counit_fmap_unit(e in -100i32..100, a in -100i32..100) {
926                let mapped = <EnvF<i32> as crate::functor::Functor>::fmap(
927                    (e, a),
928                    |x: i32| -> Box<dyn Fn(i32) -> (i32, i32)> {
929                        CurryAdj::<i32>::unit(x)
930                    },
931                );
932                let result = CurryAdj::<i32>::counit(mapped);
933                prop_assert_eq!(result, (e, a));
934            }
935
936            // State monad left identity: chain(pure(a), f) == f(a)
937            #[test]
938            fn curry_adj_state_left_identity(a in -100i32..100, e in -100i32..100) {
939                let f = |x: i32| -> Box<dyn Fn(i32) -> (i32, i32)> {
940                    Box::new(move |env| (env, x.wrapping_add(1)))
941                };
942                let chained = state_chain(state_pure(a), f);
943                let expected = f(a);
944                prop_assert_eq!(chained(e), expected(e));
945            }
946
947            // State monad right identity: chain(m, pure) == m
948            #[test]
949            fn curry_adj_state_right_identity(e in -100i32..100) {
950                let m_fn = move |env: i32| (env.wrapping_add(1), env.wrapping_mul(2));
951                let chained = state_chain(
952                    Box::new(m_fn) as Box<dyn Fn(i32) -> (i32, i32)>,
953                    |a: i32| state_pure(a),
954                );
955                prop_assert_eq!(chained(e), m_fn(e));
956            }
957
958            // State monad associativity: chain(chain(m, f), g) == chain(m, |a| chain(f(a), g))
959            #[test]
960            fn curry_adj_state_associativity(e in -100i32..100) {
961                let _m: Box<dyn Fn(i32) -> (i32, i32)> =
962                    Box::new(|env| (env, 10));
963                let f = |x: i32| -> Box<dyn Fn(i32) -> (i32, i32)> {
964                    Box::new(move |env| (env.wrapping_add(1), x.wrapping_add(1)))
965                };
966                let g = |x: i32| -> Box<dyn Fn(i32) -> (i32, i32)> {
967                    Box::new(move |env| (env, x.wrapping_mul(2)))
968                };
969
970                let left = state_chain(state_chain(
971                    Box::new(|env: i32| (env, 10)) as Box<dyn Fn(i32) -> (i32, i32)>,
972                    f,
973                ), g);
974                let right = state_chain(
975                    Box::new(|env: i32| (env, 10)) as Box<dyn Fn(i32) -> (i32, i32)>,
976                    move |a: i32| {
977                        let inner_f = |x: i32| -> Box<dyn Fn(i32) -> (i32, i32)> {
978                            Box::new(move |env| (env.wrapping_add(1), x.wrapping_add(1)))
979                        };
980                        let inner_g = |x: i32| -> Box<dyn Fn(i32) -> (i32, i32)> {
981                            Box::new(move |env| (env, x.wrapping_mul(2)))
982                        };
983                        state_chain(inner_f(a), inner_g)
984                    },
985                );
986                prop_assert_eq!(left(e), right(e));
987            }
988
989            // Store comonad: extract(store) == peek(pos)
990            #[test]
991            fn curry_adj_store_extract_law(pos in -100i32..100) {
992                let store: (i32, Box<dyn Fn(i32) -> i32>) =
993                    (pos, Box::new(|e| e.wrapping_mul(2)));
994                let result = store_extract(store);
995                prop_assert_eq!(result, pos.wrapping_mul(2));
996            }
997        }
998    }
999
1000    // --- ContravariantAdjunction tests ---
1001
1002    #[cfg(any(feature = "std", feature = "alloc"))]
1003    mod contravariant_adj_tests {
1004        use super::*;
1005
1006        #[test]
1007        fn cont_adj_unit() {
1008            let k: Box<dyn Fn(i32) -> i32> = Box::new(|x| x + 1);
1009            let m = ContAdj::<i32>::unit(42);
1010            assert_eq!(m(k), 43);
1011        }
1012
1013        #[test]
1014        fn cont_adj_counit() {
1015            let k: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
1016            let m = ContAdj::<i32>::counit(10);
1017            assert_eq!(m(k), 20);
1018        }
1019
1020        #[test]
1021        fn cont_adj_self_adjoint() {
1022            // unit and counit should behave identically (self-adjoint)
1023            let k: Box<dyn Fn(i32) -> String> = Box::new(|x| format!("{}", x));
1024            let unit_result = ContAdj::<String>::unit(42);
1025            let counit_result = ContAdj::<String>::counit(42);
1026            let k2: Box<dyn Fn(i32) -> String> = Box::new(|x| format!("{}", x));
1027            assert_eq!(unit_result(k), counit_result(k2));
1028        }
1029
1030        #[test]
1031        fn cont_pure_test() {
1032            let m = cont_pure::<i32, _>(42);
1033            assert_eq!(cont_run(&*m, |x| x + 1), 43);
1034        }
1035
1036        #[test]
1037        fn cont_fmap_test() {
1038            let m = cont_pure::<i32, _>(10);
1039            let mapped = cont_fmap(|x: i32| x * 3, m);
1040            assert_eq!(cont_run(&*mapped, |x| x + 1), 31); // (10 * 3) + 1
1041        }
1042
1043        #[test]
1044        fn cont_chain_test() {
1045            let m = cont_pure::<i32, _>(5);
1046            let chained = cont_chain(m, |x: i32| cont_pure(x + 10));
1047            assert_eq!(cont_run(&*chained, |x| x * 2), 30); // (5 + 10) * 2
1048        }
1049
1050        #[test]
1051        fn cont_chain_sequencing() {
1052            // chain two computations that modify values
1053            let m1 = cont_pure::<i32, _>(3);
1054            let m2 = cont_chain(m1, |x| {
1055                let doubled = x * 2; // 6
1056                cont_chain(cont_pure(doubled), |y| cont_pure(y + 1)) // 7
1057            });
1058            assert_eq!(cont_run(&*m2, |x| x), 7);
1059        }
1060
1061        #[test]
1062        fn cont_call_cc_no_escape() {
1063            // call_cc where we don't use the escape continuation
1064            let m = cont_call_cc::<i32, i32, i32>(|_escape| cont_pure(42));
1065            assert_eq!(cont_run(&*m, |x| x), 42);
1066        }
1067
1068        #[test]
1069        fn cont_call_cc_with_escape() {
1070            // call_cc where we use the escape continuation to short-circuit
1071            let m = cont_call_cc::<i32, i32, i32>(|escape| {
1072                // escape(10) ignores the rest and returns 10
1073                let escaped = escape(10);
1074                // This chain would normally add 100, but escape skips it
1075                cont_chain(escaped, |_| cont_pure(999))
1076            });
1077            assert_eq!(cont_run(&*m, |x| x), 10);
1078        }
1079
1080        #[test]
1081        fn contf_contramap() {
1082            use crate::contravariant::Contravariant;
1083            let f: Box<dyn Fn(i32) -> bool> = Box::new(|x| x > 0);
1084            let g = ContF::<bool>::contramap(f, |s: &str| s.len() as i32);
1085            assert!(g("hello"));
1086            assert!(!g(""));
1087        }
1088    }
1089
1090    #[cfg(any(feature = "std", feature = "alloc"))]
1091    mod contravariant_adj_law_tests {
1092        use super::*;
1093        use proptest::prelude::*;
1094
1095        proptest! {
1096            // Continuation monad left identity: chain(pure(a), f) == f(a)
1097            #[test]
1098            fn cont_monad_left_identity(a in -100i32..100) {
1099                let f = |x: i32| -> Box<dyn Fn(Box<dyn Fn(i32) -> i32>) -> i32> {
1100                    cont_pure(x.wrapping_add(1))
1101                };
1102                let chained = cont_chain(cont_pure(a), f);
1103                let expected = f(a);
1104                // Test with identity continuation
1105                prop_assert_eq!(
1106                    cont_run(&*chained, |x| x),
1107                    cont_run(&*expected, |x| x)
1108                );
1109            }
1110
1111            // Continuation monad right identity: chain(m, pure) == m
1112            #[test]
1113            fn cont_monad_right_identity(a in -100i32..100) {
1114                let m = cont_pure::<i32, _>(a);
1115                let chained = cont_chain(m, |x: i32| cont_pure(x));
1116                let m2 = cont_pure::<i32, _>(a);
1117                prop_assert_eq!(
1118                    cont_run(&*chained, |x| x),
1119                    cont_run(&*m2, |x| x)
1120                );
1121            }
1122
1123            // Functor identity: fmap(id, m) ≡ m
1124            #[test]
1125            fn cont_functor_identity(a in -100i32..100) {
1126                let m = cont_pure::<i32, _>(a);
1127                let mapped = cont_fmap(|x: i32| x, m);
1128                let m2 = cont_pure::<i32, _>(a);
1129                prop_assert_eq!(
1130                    cont_run(&*mapped, |x| x),
1131                    cont_run(&*m2, |x| x)
1132                );
1133            }
1134
1135            // Functor composition: fmap(g . f) ≡ fmap(g) . fmap(f)
1136            #[test]
1137            fn cont_functor_composition(a in -100i32..100) {
1138                let f = |x: i32| x.wrapping_add(1);
1139                let g = |x: i32| x.wrapping_mul(2);
1140
1141                let m1 = cont_pure::<i32, _>(a);
1142                let left = cont_fmap(move |x| g(f(x)), m1);
1143
1144                let m2 = cont_pure::<i32, _>(a);
1145                let right = cont_fmap(g, cont_fmap(f, m2));
1146
1147                prop_assert_eq!(
1148                    cont_run(&*left, |x| x),
1149                    cont_run(&*right, |x| x)
1150                );
1151            }
1152        }
1153    }
1154
1155    // --- ProfunctorAdjunction tests ---
1156
1157    mod profunctor_adj_tests {
1158        use super::*;
1159        use crate::hkt::TupleF;
1160
1161        #[test]
1162        fn profunctor_identity_adj_unit() {
1163            let val: (i32, String) = (42, "hello".to_string());
1164            let result = ProfunctorIdentityAdj::unit::<TupleF, i32, String>(val.clone());
1165            assert_eq!(result, val);
1166        }
1167
1168        #[test]
1169        fn profunctor_identity_adj_counit() {
1170            let val: (i32, String) = (42, "hello".to_string());
1171            let result = ProfunctorIdentityAdj::counit::<TupleF, i32, String>(val.clone());
1172            assert_eq!(result, val);
1173        }
1174
1175        #[test]
1176        fn profunctor_identity_adj_roundtrip() {
1177            // counit(unit(p)) == p (triangle identity for identity adjunction)
1178            let val: (i32, i32) = (1, 2);
1179            let result =
1180                ProfunctorIdentityAdj::counit::<TupleF, i32, i32>(ProfunctorIdentityAdj::unit::<
1181                    TupleF,
1182                    i32,
1183                    i32,
1184                >(val));
1185            assert_eq!(result, (1, 2));
1186        }
1187    }
1188
1189    mod profunctor_adj_law_tests {
1190        use super::*;
1191        use crate::hkt::TupleF;
1192        use proptest::prelude::*;
1193
1194        proptest! {
1195            // unit;counit == id (triangle identity)
1196            #[test]
1197            fn profunctor_identity_adj_triangle(a in any::<i32>(), b in any::<i32>()) {
1198                let val: (i32, i32) = (a, b);
1199                let result = ProfunctorIdentityAdj::counit::<TupleF, i32, i32>(
1200                    ProfunctorIdentityAdj::unit::<TupleF, i32, i32>(val),
1201                );
1202                prop_assert_eq!(result, (a, b));
1203            }
1204        }
1205    }
1206}