Skip to main content

karpal_core/
adjunction.rs

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