Monad ↔ Arrow Bridges

Kleisli, ArrowMonad, plus the kleisli / arrow_monad helpers connecting the two typeclass towers.

bridges

Bridges between the monad and arrow typeclass towers.

Three canonical conversions:

  • kleisli — given a Monad instance m, produce a Kleisli(m) arrow whose hom from A to B is the V-Rel morphism set Hom(A, m(B)).
  • arrow_monad — given an ArrowApply instance a, produce a Monad whose underlying functor is ArrowMonad(a)(α) = a(1, α).
  • cokleisli — comonadic dual of kleisli.

Each bridge is realised at the value level as concrete morphism constructions: compose is Kleisli composition (fmap-then-join), first is the monad's strength applied to the second-factor input, app is the canonical evaluator over the function-space encoding [A → B]. The bridge round-trip arrow_monad ∘ kleisli is the identity on monad theories (Hughes 2000, Theorem 3.1).

Kleisli

Bases: Model

The Kleisli arrow of a monad.

Hom from A to B is the set of morphisms A → m(B) in V-Rel. Composition is Kleisli composition (fmap, then join); identity is Monad.pure. first is realised via the canonical monad strength; app evaluates a function-space embedding through the monad's Monad.apply.

monad is held opaquely so the typeclass-ABC reference survives in-process identity through with_ without panproto schema validation; the field does not round-trip through JSON.

ATTRIBUTE DESCRIPTION
monad

The underlying monad instance.

TYPE: Monad

compose

compose(f: Morphism, g: Morphism) -> Morphism

Kleisli composition f ; g = join ∘ fmap(g) ∘ f.

f : A → m(B), g : B → m(C)g ∘_K f : A → m(C).

Source code in src/quivers/monadic/bridges.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
def compose(self, f: Morphism, g: Morphism) -> Morphism:
    """Kleisli composition ``f ; g = join ∘ fmap(g) ∘ f``.

    ``f : A → m(B)``, ``g : B → m(C)`` ⊢ ``g ∘_K f : A → m(C)``.
    """
    m = self.monad
    if not isinstance(f, Morphism) or not isinstance(g, Morphism):
        raise TypeError(
            "Kleisli.compose requires Morphism arguments; "
            f"got {type(f).__name__}, {type(g).__name__}"
        )
    # Identify the underlying B: f.codomain = m(B), so we recover
    # B by undoing fmap_obj. Since fmap_obj is injective on its
    # representable domain (per the monad's instance contract),
    # we rely on the caller-supplied g.domain to be B.
    B = g.domain
    C = g.codomain  # m's image of the real C
    # The real C is not directly recoverable; we use the
    # monad's fmap to produce m(g) : m(B) → m(m(C')) where
    # C' is whatever B-image the user produced. Then join
    # collapses the double-application.
    fmap_g = m.fmap(B, C, g)
    # Determine the underlying value-codomain by inspecting
    # whether the result already lives inside the monad.
    # For most monads, g.codomain = m(C') for some C'; we
    # recover C' by passing C' = the inverse of fmap_obj.
    # Concretely we call join on the monad applied at C'.
    # Since C is m(C'), we recover C' via the user-supplied
    # signature: if g.codomain.size == m.fmap_obj(C').size for
    # some C', that's our C'. We rely on g being well-typed.
    C_prime = _recover_value_type(m, C)
    join_C = m.join(C_prime)
    return f >> fmap_g >> join_C

first

first(f: Morphism, C: SetObject) -> Morphism

first(f) : (A × C) → m(B × C) via the monad's strength.

Realised as (f × id_C) ; strength_m(B, C).

Source code in src/quivers/monadic/bridges.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
def first(self, f: Morphism, C: SetObject) -> Morphism:
    """``first(f) : (A × C) → m(B × C)`` via the monad's strength.

    Realised as ``(f × id_C) ; strength_m(B, C)``.
    """
    m = self.monad
    mB = f.codomain
    # Recover the underlying B from m(B).
    B = _recover_value_type(m, mB)
    # Build (A × C) → (m(B) × C):
    first_f = parallel(f, id_morph(C))
    # Then strength: m(B) × C → m(B × C) — we use the symmetric
    # form by swapping factors first if the strength is
    # right-strength; here we implement directly with the second-
    # factor strength.
    strength = _monad_strength_second(m, B, C)
    return first_f >> strength

app

app(A: SetObject, B: SetObject) -> Morphism

app : m([A → B]) × m(A) → m(B) via the monad's apply.

Routes through the Applicative-level apply, which every Monad has by inclusion.

Source code in src/quivers/monadic/bridges.py
133
134
135
136
137
138
139
def app(self, A: SetObject, B: SetObject) -> Morphism:
    """``app : m([A → B]) × m(A) → m(B)`` via the monad's apply.

    Routes through the Applicative-level apply, which every
    Monad has by inclusion.
    """
    return self.monad.apply(A, B)

ArrowMonad

Bases: Model

The monad induced by an ArrowApply arrow.

Hughes 2000 §4: any ArrowApply arrow a gives a monad ArrowMonad(a)(α) = a(1, α), where 1 is the monoidal unit. Realised concretely:

  • fmap_obj(A) = A (the underlying carrier coincides with the target type of the arrow's hom from the unit);
  • pure(A) is the arrow's arr(id_A);
  • join(A) is the arrow's app morphism on the unit-anchored arrow values.

arrow is held opaquely.

ATTRIBUTE DESCRIPTION
arrow

The underlying arrow.

TYPE: ArrowApply

fmap

fmap(A: SetObject, B: SetObject, f: Morphism) -> Morphism

fmap(f) : ArrowMonad(a)(A) → ArrowMonad(a)(B).

Realised as arr(f) on the underlying arrow.

Source code in src/quivers/monadic/bridges.py
230
231
232
233
234
235
def fmap(self, A: SetObject, B: SetObject, f: Morphism) -> Morphism:
    """``fmap(f) : ArrowMonad(a)(A) → ArrowMonad(a)(B)``.

    Realised as ``arr(f)`` on the underlying arrow.
    """
    return self.arrow.arr(A, B, f)

pure

pure(A: SetObject) -> Morphism

pure : A → ArrowMonad(a)(A) = arr(id_A).

Source code in src/quivers/monadic/bridges.py
237
238
239
def pure(self, A: SetObject) -> Morphism:
    """``pure : A → ArrowMonad(a)(A) = arr(id_A)``."""
    return self.arrow.id_arr(A)

apply

apply(A: SetObject, B: SetObject) -> Morphism

apply : ArrowMonad(a)([A → B]) ⊗ ArrowMonad(a)(A) → ArrowMonad(a)(B).

Routes through the underlying arrow's ArrowApply.app.

Source code in src/quivers/monadic/bridges.py
241
242
243
244
245
246
def apply(self, A: SetObject, B: SetObject) -> Morphism:
    """``apply : ArrowMonad(a)([A → B]) ⊗ ArrowMonad(a)(A) → ArrowMonad(a)(B)``.

    Routes through the underlying arrow's `ArrowApply.app`.
    """
    return self.arrow.app(A, B)

join

join(A: SetObject) -> Morphism

join : ArrowMonad(a)(ArrowMonad(a)(A)) → ArrowMonad(a)(A).

ArrowMonad's fmap_obj is identity, so join is also identity.

Source code in src/quivers/monadic/bridges.py
248
249
250
251
252
253
def join(self, A: SetObject) -> Morphism:
    """``join : ArrowMonad(a)(ArrowMonad(a)(A)) → ArrowMonad(a)(A)``.

    ArrowMonad's fmap_obj is identity, so join is also identity.
    """
    return id_morph(A)

lift_a2

lift_a2(A: SetObject, B: SetObject, C: SetObject, f: Morphism) -> Morphism

lift_a2(f) : a(A) × a(B) → a(C), derived via arr(f).

Source code in src/quivers/monadic/bridges.py
259
260
261
262
263
def lift_a2(
    self, A: SetObject, B: SetObject, C: SetObject, f: Morphism
) -> Morphism:
    """``lift_a2(f) : a(A) × a(B) → a(C)``, derived via ``arr(f)``."""
    return self.arrow.arr(ProductSet(components=(A, B)), C, f)

CoKleisli

Bases: Model

The CoKleisli category of a comonad.

Hom from A to B is the set of morphisms W(A) → B. Composition is the comonadic CoKleisli composition f =>> g = g ∘ W(f) ∘ δ_A; identity is the counit ε_A.

CoKleisli is registered as Category_ rather than Arrow because the arrow first derivation requires a comonadic costrength W(A × C) → W(A) × C, which is not canonical for an arbitrary comonad. When the underlying comonad supplies a costrength(A, C) method, that morphism composes with parallel(f, ε_C ∘ W(π_2)) to realise first as the full Arrow primitive; the helper first_via_costrength constructs that morphism given an explicit costrength.

comonad is held opaquely so the typeclass-ABC reference survives in-process identity without panproto schema validation.

ATTRIBUTE DESCRIPTION
comonad

The underlying comonad instance.

TYPE: Comonad

first_via_costrength

first_via_costrength(f: Morphism, C: SetObject, costrength: Morphism) -> Morphism

Realise first(f) given an explicit comonad costrength.

costrength : W(A × C) → W(A) × C is the comonad-specific morphism (analogous to the monad strength). With it, first(f, C) = costrength ; (f × id_C) is a valid CoKleisli arrow W(A × C) ⇝ B × C.

PARAMETER DESCRIPTION
f

A CoKleisli arrow W(A) → B.

TYPE: Morphism

C

The fixed second-factor object.

TYPE: SetObject

costrength

The comonad's costrength W(A × C) → W(A) × C.

TYPE: Morphism

Source code in src/quivers/monadic/bridges.py
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
def first_via_costrength(
    self, f: Morphism, C: SetObject, costrength: Morphism
) -> Morphism:
    """Realise ``first(f)`` given an explicit comonad costrength.

    ``costrength : W(A × C) → W(A) × C`` is the comonad-specific
    morphism (analogous to the monad strength). With it,
    ``first(f, C) = costrength ; (f × id_C)`` is a valid CoKleisli
    arrow ``W(A × C) ⇝ B × C``.

    Parameters
    ----------
    f : Morphism
        A CoKleisli arrow ``W(A) → B``.
    C : SetObject
        The fixed second-factor object.
    costrength : Morphism
        The comonad's costrength ``W(A × C) → W(A) × C``.
    """
    return costrength >> parallel(f, id_morph(C))

kleisli

kleisli(monad) -> Kleisli

Wrap a Monad as a Kleisli arrow.

Source code in src/quivers/monadic/bridges.py
269
270
271
def kleisli(monad) -> Kleisli:
    """Wrap a Monad as a Kleisli arrow."""
    return Kleisli(monad=monad)

arrow_monad

arrow_monad(arrow) -> ArrowMonad

Wrap an ArrowApply arrow as a Monad.

Source code in src/quivers/monadic/bridges.py
274
275
276
def arrow_monad(arrow) -> ArrowMonad:
    """Wrap an `ArrowApply` arrow as a Monad."""
    return ArrowMonad(arrow=arrow)

cokleisli

cokleisli(comonad) -> CoKleisli

Wrap a Comonad as a CoKleisli category.

The result is registered as Category_ (always) but not Arrow; promoting to an Arrow requires the user-supplied costrength routed through CoKleisli.first_via_costrength.

Source code in src/quivers/monadic/bridges.py
338
339
340
341
342
343
344
345
def cokleisli(comonad) -> CoKleisli:
    """Wrap a Comonad as a CoKleisli category.

    The result is registered as `Category_` (always) but not
    `Arrow`; promoting to an Arrow requires the user-supplied
    costrength routed through `CoKleisli.first_via_costrength`.
    """
    return CoKleisli(comonad=comonad)