circuits
Safe HaskellNone
LanguageGHC2024

Circuit.Poles

Description

Free channel poles over a base arrow, plus concrete box helpers.

A channel has exactly two poles:

  • Out — the companion (read / emit pole), covariant in the payload.
  • In — the conjoint (write / commit pole), contravariant in the payload.

Poles is the record that pairs one In with one Out. The poles are defined purely in terms of the base arrow arr.

open produces a matched pair; close plugs the pair back together by feeding the Out into the In.

A symmetric pole Poles arr a a with close (conjoint p) (companion p) = id is the copycat strategy for the multiplicative excluded middle A ⅋ A⊥: it routes traffic between the two poles without ever deciding which side is true. For the unit object use open (also exported as copycat).

Relationship to Channel

Poles is the bi-polar / effectful API: it is the right tool for K IO/STM process plumbing where the channel is a write pole paired with a read pole. For pure (->) Moore-style channels indexed by a polynomial, prefer Channel.

There is no deprecation shim yet: the relationship between the bi-polar and polynomial views is still being settled. This module stays unchanged until the polynomial Channel gains an equivalent effectful story.

Synopsis

Channel poles (bi-polar contract)

newtype Out (arr :: k -> k1 -> Type) (a :: k1) Source #

Out is the companion of the identity functor. Covariant in a (sits in the output position).

Constructors

Out 

Fields

  • emit :: forall (x :: k). In arr x -> arr x a

    Emit through the companion, supplying the other pole.

newtype In (arr :: k -> k1 -> Type) (a :: k) Source #

In is the conjoint of the identity functor. Contravariant in a (sits in the input position).

Constructors

In 

Fields

  • commit :: forall (x :: k1). Out arr x -> arr a x

    Commit through the conjoint, supplying the other pole.

Matched pair

data Poles (arr :: k -> k1 -> Type) (a :: k) (b :: k1) Source #

A matched pair of channel poles: one In and one Out.

This is the bi-polar communication contract. The conjoint (In) consumes payloads of type a; the companion (Out) produces payloads of type b. For symmetric channels such as queues a = b.

Together with prefixIn and suffixOut, Poles carries an enriched profunctor structure over the base category arr: prefixIn is the left action of arr on In poles, and suffixOut is the right action of arr on Out poles.

Constructors

Poles 

Fields

  • conjoint :: In arr a

    Write pole (producer), the conjoint.

  • companion :: Out arr b

    Read pole (consumer), the companion.

Counit

close :: forall {k} arr (a :: k). In arr a -> Out arr a -> arr a a Source #

Plug an In and an Out of the same payload type together.

close feeds the Out into the In pole, producing a morphism arr a a from the paired payload type.

Yanking: for the unit poles from open, close (conjoint p) (companion p) = id.

Prefixing an action to an In

prefixIn :: forall {k} arr (a :: k) (b :: k). Category arr => arr a b -> In arr b -> In arr a Source #

Precompose an arr-morphism with an In pole.

Given f :: arr a b and an In pole at type b, produce an In pole at type a. Running the resulting pole first executes f and then commits through the original pole.

This is the left (contravariant) action of the base category on In poles. Specialised to unit poles it is the canonical way to build effectful write poles.

>>> let polesU = open :: Poles (->) () ()
>>> let inA = prefixIn (const ()) (conjoint polesU) :: In (->) Int
>>> commit inA (companion polesU) 42
()

Suffixing an action to an Out

suffixOut :: forall {k} arr (a :: k) (b :: k). Category arr => Out arr a -> arr a b -> Out arr b Source #

Postcompose an arr-morphism with an Out pole.

Given an Out pole at type a and g :: arr a b, produce an Out pole at type b. Running the resulting pole first emits through the original pole and then executes g on the emitted value.

This is the right (covariant) action of the base category on Out poles. Specialised to unit poles it is the canonical way to build effectful read poles.

>>> let polesU = open :: Poles (->) () ()
>>> let outA = suffixOut (companion polesU) (const 42) :: Out (->) Int
>>> emit outA (conjoint polesU) ()
42

Build a Poles from primitive actions

poles :: forall {k} arr (a :: k) (b :: k) (bot :: k). HasDual bot arr => arr a bot -> arr bot b -> Poles arr a b Source #

Build a Poles from a write morphism and a read morphism.

write :: arr a bot consumes the input payload and produces the dualising object; read :: arr bot b consumes the dualising object and produces the output payload. The dualising-object poles wire the two halves together.

This is the canonical way to turn a pair of primitive channel actions into a matched pair of In and Out poles.

Compositional spelling:

poles write receive =
  Poles (prefixIn write (conjoint open)) (suffixOut (companion open) receive)

poles0 :: HasDual () arr => arr a () -> arr () b -> Poles arr a b Source #

Convenience version of poles when the dualising object is ().

polesK :: Monad m => (a -> m ()) -> m b -> Poles (K m) a b Source #

Specialization of poles for K actions.

write :: a -> m () consumes the input payload; receive :: m b produces the output payload. The dualising-object handling is hidden inside the K wrappers.

Extract primitive actions from a Poles

splay :: forall {k} arr (a :: k) (b :: k) (bot :: k). HasDual bot arr => Poles arr a b -> (arr a bot, arr bot b) Source #

Extract the primitive write and read actions from a Poles by plugging each pole with the dualising-object poles.

For a Poles built with poles, this recovers the original write :: arr a bot and receive :: arr bot b.

>>> let p = poles0 (\() -> ()) (const (42 :: Int)) :: Poles (->) () Int
>>> let (write, receive) = splay0 p
>>> (write (), receive ())
((),42)

splay0 :: HasDual () arr => Poles arr a b -> (arr a (), arr () b) Source #

Convenience version of splay when the dualising object is ().

Sequential composition

compose :: forall {k} (arr :: k -> k -> Type) (a :: k) (b :: k) (c :: k) (bot :: k). HasDual bot arr => Poles arr a b -> Poles arr b c -> Poles arr a c Source #

Sequential composition of Poles.

Given p1 :: Poles arr a b and p2 :: Poles arr b c, produce an Poles arr a c by connecting the b pole of p1 to the b pole of p2. The primitive actions are extracted via splay and reassembled with poles, so box preserves the composition:

box (compose p1 p2) = box p2 . box p1

Identity exists at the chosen unit type: open :: Poles arr u u is the identity for composition.

>>> let p1 = poles0 (const ()) (const 1 :: () -> Int) :: Poles (->) () Int
>>> let p2 = poles0 (const ()) (const 2 :: () -> Int) :: Poles (->) Int Int
>>> box @() (compose0 p1 p2) ()
2

open at the dualising object is the identity for composition:

>>> let r = poles @(->) @Bool @Bool @Bool not not
>>> box @Bool (compose @_ @_ @_ @_ @Bool (open :: Poles (->) Bool Bool) r) True
True

compose0 :: forall (arr :: Type -> Type -> Type) a b c. HasDual () arr => Poles arr a b -> Poles arr b c -> Poles arr a c Source #

Convenience version of compose when the dualising object is ().

(>:>) :: forall {k} (arr :: k -> k -> Type) (a :: k) (b :: k) (c :: k) (bot :: k). HasDual bot arr => Poles arr a b -> Poles arr b c -> Poles arr a c infixr 1 Source #

Forward-composition operator for Poles. p1 >:> p2 = compose p1 p2.

Parallel composition

polesTensor :: forall {k} (t :: k -> k -> k) (arr :: k -> k -> Type) (a :: k) (b :: k) (c :: k) (d :: k) (bot :: k). (Tensor t arr, HasDual bot arr, Unit t ~ bot) => Poles arr a b -> Poles arr c d -> Poles arr (t a c) (t b d) Source #

Parallel composition of Poles.

Pair two Poles side by side on the tensor t. The primitive actions are tensored and then collapsed to and from the dualising object with the tensor unitors. This requires the tensor unit to coincide with the dualising object bot; in practice this is the cartesian (,) tensor with bot = ().

>>> let p1 = poles0 (const ()) (const 1 :: () -> Int) :: Poles (->) () Int
>>> let p2 = poles0 (const ()) (const 2 :: () -> Int) :: Poles (->) () Int
>>> box @() (polesTensor p1 p2) ((), ())
(1,2)

Morphism-level mapping

iomap :: forall {k} arr (a :: k) (a' :: k) (b :: k) (b' :: k). Category arr => arr a' a -> arr b b' -> Poles arr a b -> Poles arr a' b' Source #

Precompose the input and postcompose the output of a Poles.

This is the morphism-level profunctor action: f :: arr a' a shapes what the conjoint sees, and g :: arr b b' shapes what the companion emits.

>>> let p = poles0 (const ()) (const 42 :: () -> Int) :: Poles (->) () Int
>>> let p' = iomap (const ()) ((+1) :: Int -> Int) p :: Poles (->) () Int
>>> box @() p' ()
43

imap :: forall {k} arr (a :: k) (a' :: k) (b :: k). Category arr => arr a' a -> Poles arr a b -> Poles arr a' b Source #

Precompose the input of a Poles.

omap :: forall {k} arr (a :: k) (b :: k) (b' :: k). Category arr => arr b b' -> Poles arr a b -> Poles arr a b' Source #

Postcompose the output of a Poles.

Dualising object / unit poles (requires constant morphisms)

class Category arr => HasDual (bot :: k) (arr :: k -> k -> Type) where Source #

Arrows that have channel poles for a given dualising object bot.

The dualising object is the target of the polar pairing and the object through which the two poles of a Poles are plugged together. In the cartesian case it is the monoidal unit (); for halt-mark / delivery pairings it can be Bool.

The poles are the identity-on-bot morphism split into its two polar halves. The companion is constant; the conjoint delegates to the opposing companion.

These poles require the base arrow to support constant morphisms, so they are captured by this class rather than being definable for all arrows.

Methods

open :: Poles arr bot bot Source #

The dualising object as channel poles.

Yank

>>> let poles = open :: Poles (->) () ()
>>> close (conjoint poles) (companion poles) ()
()

Plug

>>> let polesA = open :: Poles (->) () ()
>>> let polesU = open :: Poles (->) () ()
>>> commit (conjoint polesA) (companion polesU) ()
()
>>> emit (companion polesA) (conjoint polesU) ()
()

Instances

Instances details
Monad m => HasDual () (K m :: Type -> Type -> Type) Source #

Dualising object () for K m.

Same shape as the (->) instance, but the constant companion returns () in the monad.

Instance details

Defined in Circuit.Poles

Methods

open :: Poles (K m) () () Source #

HasDual () (->) Source #

Dualising object () for (->).

The companion is the constant function returning (); the conjoint recursively emits through the supplied companion.

Instance details

Defined in Circuit.Poles

Methods

open :: Poles (->) () () Source #

Monad m => HasDual Bool (K m :: Type -> Type -> Type) Source #

Dualising object Bool for K m.

Same shape as the (->) instance, but the constant companion returns False in the monad.

Instance details

Defined in Circuit.Poles

Methods

open :: Poles (K m) Bool Bool Source #

HasDual Bool (->) Source #

Dualising object Bool for (->).

The companion is the constant function returning False; the conjoint recursively emits through the supplied companion. Because Bool is not terminal, copycat at Bool is the constant function, not the identity.

Instance details

Defined in Circuit.Poles

Methods

open :: Poles (->) Bool Bool Source #

(Monad m, Pointed s) => HasDual Void (Body Either s (K m) :: Type -> Type -> Type) Source #

Unit poles for Body Either s (K m) at Void.

Instance details

Defined in Circuit.Body

Methods

open :: Poles (Body Either s (K m)) Void Void Source #

Pointed s => HasDual Void (Body Either s (->) :: Type -> Type -> Type) Source #

Unit poles for Body Either s (->) at the unit object Void.

The coproduct case needs a distinguished element of the carrier s: on a Right x input the companion must return Left s for some s, and there is no ambient state to use. Pointed captures exactly that, which is weaker than Monoid. This is the structural pointedness requirement that makes Either differ from (,).

Instance details

Defined in Circuit.Body

Methods

open :: Poles (Body Either s (->)) Void Void Source #

Monad m => HasDual () (Body (,) s (K m) :: Type -> Type -> Type) Source #

Unit poles for Body (,) s (K m).

Same shape as the (->) instance, but the companion returns () in the monad and threads the ambient state through unchanged.

Instance details

Defined in Circuit.Body

Methods

open :: Poles (Body (,) s (K m)) () () Source #

HasDual () (Body (,) s (->) :: Type -> Type -> Type) Source #

Unit poles for Body (,) s (->) at the unit object ().

The companion discards its input and returns (); the conjoint delegates to the companion. Yanking recovers the identity on ().

Instance details

Defined in Circuit.Body

Methods

open :: Poles (Body (,) s (->)) () () Source #

Copycat / multiplicative excluded middle

copycat :: forall {k} (arr :: k -> k -> Type) (bot :: k). HasDual bot arr => Poles arr bot bot Source #

The copycat strategy at the dualising object bot.

This is the multiplicative excluded middle bot ⅋ bot⊥ for arrows that have poles at bot: a self-dual channel whose close is the identity on bot. It routes between the two poles without ever deciding which one holds.

The additive excluded middle bot ⊕ bot⊥ — a verdict, now — is not supported; there is no decide :: Either bot bot here, because only the routing witness is provable.

Boxes

box :: forall {k} (bot :: k) arr (a :: k) (b :: k). HasDual bot arr => Poles arr a b -> arr a b Source #

Close a Poles to a plain base-arrow morphism.

A matched pair of free poles (Poles) is a box with one input wire and one output wire. This helper embeds that box into a traced monoidal category by unit-plugging the remaining two slots, giving a plain arr a b: input on the left, output on the right, with the unit plumbing hidden.

>>> let p = poles0 (const ()) (const 42) :: Poles (->) () Int
>>> box @() p ()
42

boxAsymmetric :: forall {k} (bot :: k) (t :: k -> k -> k) arr (a :: k) (b :: k). (HasDual bot arr, Tensor t arr) => Poles arr a b -> arr (t a bot) (t bot b) Source #

Asymmetric box with the dualising object exposed on opposite sides.

Uses tensor at the base arrow level. The input carries the dualising object on the right and the output carries it on the left; most users will prefer the dualising-object-normalised box.

>>> let p = poles0 (const ()) (const 42) :: Poles (->) () Int
>>> boxAsymmetric @() p ((), ())
((),42)

Additive connectives

pair :: forall (arr :: Type -> Type -> Type) a b c. (HasDual () arr, Tensor (,) arr, Copy arr a) => Poles arr a b -> Poles arr a c -> Poles arr a (b, c) Source #

Additive conjunction: both sub-poles receive the same input and their outputs are paired.

This is the & connective / await fragment: every branch sees the input, and the composite emits all of their results.

>>> let p1 = poles0 (const ()) (const 1 :: () -> Int) :: Poles (->) () Int
>>> let p2 = poles0 (const ()) (const 2 :: () -> Int) :: Poles (->) () Int
>>> box @() (pair p1 p2) ()
(1,2)

data Bias Source #

Bias for ordered choice in scheduling and additive disjunction.

LeftFirst and RightFirst are used by shared-medium fusion in Circuit.Shared and by additive disjunction in Circuit.Poles.

Constructors

LeftFirst 
RightFirst 

Instances

Instances details
Eq Bias Source # 
Instance details

Defined in Circuit.Tensor

Methods

(==) :: Bias -> Bias -> Bool #

(/=) :: Bias -> Bias -> Bool #

Show Bias Source # 
Instance details

Defined in Circuit.Tensor

Methods

showsPrec :: Int -> Bias -> ShowS #

show :: Bias -> String #

showList :: [Bias] -> ShowS #

race :: forall (arr :: Type -> Type -> Type) a b. (HasDual () arr, Tensor (,) arr, Copy arr a, FunctionLike arr) => (b -> Bool) -> Bias -> Poles arr a b -> Poles arr a b -> Poles arr a b Source #

Additive disjunction / race: both sub-poles receive the same input, but only the first output satisfying the predicate is emitted.

The predicate selects "silent" values that should be skipped. The bias chooses which side to prefer when both are non-silent. The picking logic is lifted into the base arrow via FunctionLike.

>>> let eL = poles0 (const ()) (const (Just 1)) :: Poles (->) () (Maybe Int)
>>> let eR = poles0 (const ()) (const (Just 2)) :: Poles (->) () (Maybe Int)
>>> box @() (race isNothing LeftFirst eL eR) ()
Just 1
>>> box @() (race isNothing RightFirst eL eR) ()
Just 2