packages feed

circuits-0.2.0.0: src/Circuit/Loop.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeAbstractions #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-orphans #-}

-- | The free traced monoidal category, in existential normal form.
--
-- @Loop t arr a b@ is the free traced monoidal category over a base
-- morphism @arr@ with tensor @t@. The two constructors encode:
--
--   * 'Lift' — a plain base arrow.
--   * 'Knot' — a feedback loop with a hidden feedback channel.
--
-- The laws of traced monoidal categories are performed by the 'Category'
-- and 'Traced' instances, so every value is already in normal form: at most
-- one 'Knot' at the top, over a base-arrow body.
--
-- For example, a @Loop (,) (->)@ is the initial traced monoidal cartesian
-- category over Haskell functions.
--
-- = Introduce / resolve
--
-- The vocabulary in this module follows the introduce/resolve pattern:
--
--   * 'Knot' introduces feedback; 'trace' resolves it. This is the gold
--     type-changing pair; composition fuses 'Knot's.
--
-- The polar channel ends (@Out@, @In@), their counit (@close@), and
-- their unit (@open@) all live in "Circuit.Ends".
--
-- == Interpreting a 'Loop'
--
-- Use 'run' or 'bind' to interpret a 'Loop' into a target category.  The
-- 'Category' and 'Traced' instances of the target discharge the knot; for
-- @(->)@ this is lazy knot-tying, and for 'Either' it is iteration.
--
-- == Core Concepts
--
-- * __Tensor__ (@t@): The bifunctor that pairs a feedback value with a payload.
--   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' body).
--
-- * __Payload__: The component that is transformed and emitted (the second
--   parameter of the tensor inside a 'Knot' body).
--
-- * __Feedback channel__: The hidden type @s@ in a 'Knot'. It is the value
--   the abstraction hides.
module Circuit.Loop
  ( -- * Loop
    Loop (..),

    -- * Layer witness
    FreeLoop,
  )
where

import Circuit.Category (Category (..), Discrete (..), (.>))
import Circuit.Channel (Channel (..), Strength (..), Traced (..))
import Circuit.Layer (Layer (..), run, (:~>))
import Control.Arrow (Kleisli (..))
import Data.Bifunctor (Bifunctor (..))
import Data.Kind (Type)
import Data.Profunctor
import Prelude hiding (id, (.))

-- $setup
-- >>> import Circuit.Layer (run)

-- | The free traced monoidal category over base morphism @arr@ and tensor @t@,
-- in existential normal form.
--
-- Two constructors:
--
--   * 'Lift' — a plain base arrow.
--   * 'Knot' — a feedback loop with hidden channel @s@.
data Loop (t :: Type -> Type -> Type) arr a b where
  -- | A plain base arrow.
  Lift :: arr a b -> Loop t arr a b
  -- | Tie a feedback loop. The tensor @t@ carries the hidden channel type @s@.
  --
  -- The argument is the base arrow itself, /not/ a 'Lift'-wrapped stage.
  -- The constructor carries the 'Ob' evidence for the feedback channel in
  -- the /source/ category.  Folding into a different target still needs
  -- 'Discrete' to manufacture the corresponding 'Ob' evidence there.
  Knot :: (Ob arr s) => arr (t s a) (t s b) -> Loop t arr a b

-- $examples
--
-- >>> run (Lift (+1) :: Loop (,) (->) Int Int) 5
-- 6
--
-- >>> run (Knot (\(acc, x) -> (x, acc)) :: Loop (,) (->) Int Int) 42
-- 42
--
-- For the @(,)@ tensor the channel value is self-referential, so the body
-- must use an irrefutable pattern or otherwise avoid forcing the channel
-- before producing its constructor:
--
-- >>> run (Knot (\ ~(ns, ()) -> (0 : ns, take 3 ns)) :: Loop (,) (->) () [Int]) ()
-- [0,0,0]

instance (Strength t arr, Discrete arr) => Category (Loop t arr) where
  type Ob (Loop t arr) a = Ob arr a
  id :: forall a. (Ob arr a) => Loop t arr a a
  id = Lift id
  (.) :: forall a b c. (Ob arr a, Ob arr b, Ob arr c) => Loop t arr b c -> Loop t arr a b -> Loop t arr a c
  Lift f . Lift g = Lift (f . g)
  Knot @_ @s @_ @_ @_ f . Lift g =
    withOb @arr @(t s a) $
      withOb @arr @(t s b) $
        withOb @arr @(t s c) $
          Knot (f . strength g)
  Lift f . Knot @_ @s @_ @_ @_ g =
    withOb @arr @(t s a) $
      withOb @arr @(t s b) $
        withOb @arr @(t s c) $
          Knot (strength f . g)
  Knot @_ @s2 @_ @_ @_ f . Knot @_ @s1 @_ @_ @_ g =
    withOb @arr @(t s2 s1) $
      withOb @arr @(t (t s2 s1) a) $
        withOb @arr @(t s2 (t s1 a)) $
          withOb @arr @(t s2 (t s1 b)) $
            withOb @arr @(t s2 (t s1 c)) $
              withOb @arr @(t s1 a) $
                withOb @arr @(t s1 b) $
                  withOb @arr @(t s1 (t s2 b)) $
                    withOb @arr @(t s2 b) $
                      withOb @arr @(t s2 c) $
                        withOb @arr @(t s1 (t s2 c)) $
                          withOb @arr @(t (t s2 s1) c) $
                            Knot (assoc .> strength g .> slide .> strength f .> slide .> assoc')

-- | A discrete base yields a discrete free traced category.
instance (Strength t arr, Discrete arr) => Discrete (Loop t arr) where
  withOb @a x = withOb @arr @a x

instance (Profunctor arr, Bifunctor t) => Profunctor (Loop t arr) where
  dimap f g (Lift h) = Lift (dimap f g h)
  dimap f g (Knot h) = Knot (dimap (second f) (second g) h)
  lmap f (Lift h) = Lift (lmap f h)
  lmap f (Knot h) = Knot (lmap (second f) h)
  rmap g (Lift h) = Lift (rmap g h)
  rmap g (Knot h) = Knot (rmap (second g) h)

instance (Bifunctor t) => Functor (Loop t (->) a) where
  fmap f (Lift g) = Lift (f . g)
  fmap f (Knot g) = Knot (second f . g)

-- | Lift the 'Channel' structure of the base arrow into 'Loop t arr'.
--
-- The 'withOb' ladder is written out inline rather than using
-- 'Circuit.Discrete.assocD' / @Circuit.Discrete.braidD@ because importing that kit here would
-- create a cycle: 'Circuit.Discrete' needs 'Traced' (and hence this module).
instance (Strength t arr, Discrete arr) => Channel t (Loop t arr) where
  assoc :: forall a b c. Loop t arr (t (t a b) c) (t a (t b c))
  assoc =
    Lift $
      withOb @arr @a $
        withOb @arr @b $
          withOb @arr @c $
            withOb @arr @(t a b) $
              withOb @arr @(t b c) $
                withOb @arr @(t (t a b) c) $
                  withOb @arr @(t a (t b c)) $
                    assoc
  assoc' :: forall a b c. Loop t arr (t a (t b c)) (t (t a b) c)
  assoc' =
    Lift $
      withOb @arr @a $
        withOb @arr @b $
          withOb @arr @c $
            withOb @arr @(t a b) $
              withOb @arr @(t b c) $
                withOb @arr @(t a (t b c)) $
                  withOb @arr @(t (t a b) c) $
                    assoc'
  slide :: forall a b c. Loop t arr (t a (t b c)) (t b (t a c))
  slide =
    Lift $
      withOb @arr @a $
        withOb @arr @b $
          withOb @arr @c $
            withOb @arr @(t b c) $
              withOb @arr @(t a c) $
                withOb @arr @(t a (t b c)) $
                  withOb @arr @(t b (t a c)) $
                    slide

-- | Lift the 'Strength' class through 'Loop t'.
instance (Strength t arr, Discrete arr) => Strength t (Loop t arr) where
  strength :: forall a b c. (Ob arr a, Ob arr b, Ob arr c, Ob arr (t a b), Ob arr (t a c)) => Loop t arr b c -> Loop t arr (t a b) (t a c)
  strength (Lift f) =
    Lift $
      withOb @arr @a $
        withOb @arr @b $
          withOb @arr @c $
            withOb @arr @(t a b) $
              withOb @arr @(t a c) $
                strength f
  strength (Knot @_ @s @_ @_ @_ f) =
    withOb @arr @(t s (t a b)) $
      withOb @arr @(t a (t s b)) $
        withOb @arr @(t a (t s c)) $
          withOb @arr @(t s b) $
            withOb @arr @(t s c) $
              withOb @arr @(t s (t a c)) $
                Knot (slide .> strength f .> slide)

-- | Lift the 'Traced' class through 'Loop t'.
--
-- 'trace' hides a wire as a 'Knot'.
instance (Traced t arr, Discrete arr) => Traced t (Loop t arr) where
  trace ::
    forall a b c.
    (Ob arr a) =>
    Loop t arr (t a b) (t a c) ->
    Loop t arr b c
  trace (Lift f) = Knot f
  trace (Knot @_ @s @_ @_ @_ f) =
    withOb @arr @(t (t s a) b) $
      withOb @arr @(t s (t a b)) $
        withOb @arr @(t s (t a c)) $
          withOb @arr @(t (t s a) c) $
            withOb @arr @(t s a) $
              Knot (assoc .> f .> assoc')

-- | 'Traced' plus 'Discrete' — required to fold free 'Loop'
-- (existential feedback channels need trivial 'Ob' on every type).
class (Traced t arr, Discrete arr) => FreeLoop t arr

instance (Traced t arr, Discrete arr) => FreeLoop t arr

-- | Free traced monoidal category.
instance Layer (Loop t) where
  type Law (Loop t) arr' = FreeLoop t arr'
  type Run (Loop t) arr = (Traced t arr, Discrete arr)
  type Bind (Loop t) arr = ()
  unit = Lift
  run :: forall arr a b. (Run (Loop t) arr, Ob arr a, Ob arr b) => Loop t arr a b -> arr a b
  run (Lift f) = f
  run (Knot @_ @s @_ @_ @_ f) =
    withOb @arr @(t s a) $
      withOb @arr @(t s b) $
        trace f
  bind :: forall arr arr' a b. (Law (Loop t) arr', Ob arr' a, Ob arr' b) => (arr :~> arr') -> Loop t arr a b -> arr' a b
  bind h (Lift f) = h f
  bind h (Knot @_ @s @_ @_ @_ f) =
    withOb @arr' @s $
      withOb @arr' @(t s a) $
        withOb @arr' @(t s b) $
          trace (h f)