packages feed

circuits-0.1.0.0: src/Circuit/Circuit.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE UndecidableInstances #-}

-- | The free traced monoidal category.
--
-- @Circuit arr t a b@ is the initial encoding of a traced monoidal category
-- over a base morphism @arr@ with a supplied tensor @t@ for the category. The three constructors encode:
--
--   - `Lift`: embedding of a base arrow (strict monoidal functor)
--   - `Compose`: sequential composition (category structure)
--   - `Knot`: introduces a feedback channel (trace structure)
--
-- For example, a `Circuit (->) (,)` is the initial traced monoidal cartesian category over Haskell functions.
--
-- == Core Concepts
--
-- * __Tensor__ (@t@): The bifunctor that pairs a feedback value with a payload
--   inside a 'Knot'. The two tensors provided are @(,)@ (simultaneous / lazy
--   sharing) and 'Either' (sequential / iteration).
--
-- * __Feedback value__: The component that travels around the loop (the first
--   parameter of the tensor inside a 'Knot').
--
-- * __Payload__: The component that is transformed and emitted by the circuit
--   (the second parameter of the tensor).
--
-- * __Feedback channel__: The path the feedback value takes when it is routed
--   back into the next step of the computation. In a 'Knot' the channel type
--   is carried by the tensor @t@.
--
-- These concepts are independent of any particular base arrow @arr@. They
-- describe the structure of feedback itself.
--
-- The `reify` function interprets any `Circuit` to a plain arrow via
-- the `Trace` instance on @t@. For encoding into 'Circuit.Hyper', see
-- 'Circuit.Hyper.encode' and 'Circuit.Hyper.encodeEither'.
module Circuit.Circuit
  ( -- * Circuit
    Circuit (..),

    -- * Type aliases
    Wire,
    Step,

    -- * Operators
    reify,
  )
where

import Circuit.Traced (Trace (..))
import Prelude hiding (id, (.))

#ifdef __GLASGOW_HASKELL__
import Control.Category
import Data.Bifunctor
import Data.Profunctor
#else
import Circuit.Classes
#endif

-- $setup
-- >>> import Control.Category ((>>>))
-- >>> import Data.Profunctor (dimap)
-- >>> import Prelude hiding (id, (.))

-- | The free traced monoidal category over base morphism @arr@ and tensor @t@.
--
-- Three constructors:
--
--   * 'Lift' — embed a base arrow.
--   * 'Compose' — sequential composition.
--   * 'Knot' — feedback loop via the tensor.
data Circuit arr t a b where
  -- | Lift embeds a base arrow (strict monoidal functor).
  --
  -- >>> reify (Lift (+1) :: Circuit (->) (,) Int Int) 5
  -- 6
  Lift :: arr a b -> Circuit arr t a b
  -- | Compose performs sequential composition (category structure).
  --
  -- >>> reify (Lift (+1) >>> Lift (*2) :: Circuit (->) (,) Int Int) 5
  -- 12
  Compose :: Circuit arr t b c -> Circuit arr t a b -> Circuit arr t a c
  -- | Knot ties a feedback loop. The tensor @t@ carries the channel type.
  --
  -- >>> reify (Knot (\(acc, x) -> (x, acc)) :: Circuit (->) (,) Int Int) 42
  -- 42
  Knot :: arr (t a b) (t a c) -> Circuit arr t b c

-- | A traced circuit over plain functions with the cartesian tensor.
--
-- @Wire a b = Circuit (->) (,) a b@
--
-- The @(,)@ tensor ties a lazy knot: output and feedback are produced
-- simultaneously.
type Wire = Circuit (->) (,)

-- | A traced circuit over plain functions with the cocartesian tensor.
--
-- @Step a b = Circuit (->) Either a b@
--
-- The @Either@ tensor iterates: @Left@ feeds back (continue),
-- @Right@ terminates (exit).
type Step = Circuit (->) Either

instance (Category arr) => Category (Circuit arr t) where
  id = Lift id
  (.) = Compose

instance Functor (Circuit (->) t a) where
  fmap f = Compose (Lift f)

-- | Profunctor instance for Circuit.
--
-- Maps over both ends of the arrow. For @Compose@, the map is applied
-- to the input of the left sub-circuit and the output of the right
-- sub-circuit, leaving the intermediate type aligned.
--
-- >>> reify (dimap (+ 1) (+ 1) (Lift (* 2) :: Circuit (->) (,) Int Int)) 5
-- 13
instance (Profunctor arr, Bifunctor t) => Profunctor (Circuit arr t) where
  dimap f g (Lift h) = Lift (dimap f g h)
  dimap f g (Compose h k) = Compose (dimap id g h) (dimap f id k)
  dimap f g (Knot k) = Knot (dimap (second f) (second g) k)
  lmap f (Lift h) = Lift (lmap f h)
  lmap f (Compose h k) = Compose (lmap id h) (lmap f k)
  lmap f (Knot k) = Knot (lmap (second f) k)
  rmap g (Lift h) = Lift (rmap g h)
  rmap g (Compose h k) = Compose (rmap g h) (rmap id k)
  rmap g (Knot k) = Knot (rmap (second g) k)

-- | Interpret a Circuit to a plain arrow.
--
-- This is the canonical map out of the free (initial) traced monoidal
-- category.  The interesting case is when a @Knot@ appears on the left
-- of a @Compose@: this is exactly where the sliding axiom of traced
-- monoidal categories is enforced (the Mendler case).
--
-- >>> reify (Lift (+1) :: Circuit (->) (,) Int Int) 5
-- 6
reify :: (Category arr, Trace arr t) => Circuit arr t x y -> arr x y
reify (Lift f) = f
reify (Compose (Knot f) g) = trace (f . untrace (reify g))
reify (Compose f g) = reify f . reify g
reify (Knot k) = trace k