proarrow-0.1.0.0: src/Proarrow/Optic/PowerGrate.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
-- | A __power grate__ is a 'Proarrow.Optic.Grate.Grate' whose exponent is a fixed tensor /power/ of
-- the focus: the witness @'Pow' n@ presents @s@ as @a ** ... ** a@ (@n@ times), the reader
-- applicative for @n@ readers. A fixed finite shape is a decomposition, so a power grate is also a
-- __fixed-arity 'Proarrow.Optic.Traversal.Traversal'__
-- (@'PowerGrateFl' <: 'GrateFl', 'KaleidoFl', 'MonTravFl'@).
--
-- It adds an eliminator: 'powerGrateP' distributes any 'MonoidalProfunctor', using only 'one' and
-- '**'. A traversal needs a 'Proarrow.Category.Monoidal.Distributive.StrongDistributiveProfunctor',
-- and a kaleidoscope needs a traversable carrier. With a fixed arity /any/ @Costar f@ distributes,
-- by unzipping @f (a ** ... ** a)@ into @f a ** ... ** f a@. So 'powerGrateOf' works at any
-- monoidal profunctor carrier: the hom @('~>')@ gives 'Proarrow.Optic.Setter.over', and an
-- applicative @'Proarrow.Profunctor.Instance.Star.Star' f@ combines the foci through @f@.
module Proarrow.Optic.PowerGrate
( PowerGrateFl (..)
, PowerGrate
, PowerGrate'
, powerGrateOf
, zipWithOf
-- * @n@-ary aggregation
, Pow (..)
, CoPow (..)
, powerGrate
) where
import Data.Type.Nat (Nat, Nat2, SNat (..), SNatI, snat)
import Proarrow.Adjunction (Proadjunction (..))
import Proarrow.Category.Monoidal
( Monoidal (..)
, MonoidalProfunctor (..)
, NFold
, SymMonoidal
, swapInner
, withObNFold
, type (**)
)
import Proarrow.Category.Monoidal qualified as M
import Proarrow.Category.Monoidal.Action (CoprodAction)
import Proarrow.Category.Monoidal.Cartesian (Cartesian)
import Proarrow.Category.Monoidal.Closed (Closed (..), mkExponential)
import Proarrow.Category.Monoidal.CopyDiscard (CopyDiscard (..), fst, snd, (&&&))
import Proarrow.Category.Monoidal.Distributive (Traversable (..))
import Proarrow.Category.Monoidal.Strength (Strong (..))
import Proarrow.Colimit.BinaryCoproduct (COPROD (..), Coprod (..), HasBinaryCoproducts (..), HasCoproducts)
import Proarrow.Colimit.Initial (HasInitialObject (..))
import Proarrow.Core (CategoryOf (..), Profunctor (..), Promonad (..), obj, (//), (\\), type (+->))
import Proarrow.Functor (Functor)
import Proarrow.Monoid (fanIn, fanOut)
import Proarrow.Object (pattern Objs)
import Proarrow.Optic
( ExOptic
, FLAVOR
, Optic
, Prostrong (..)
, legs2prof
, withLegs
)
import Proarrow.Optic.Fold (FoldFl (..))
import Proarrow.Optic.Glass (GlassFl (..), Mod, withObSel)
import Proarrow.Optic.Grate (GrateFl (..))
import Proarrow.Optic.Kaleidoscope (CotravFl, KaleidoFl (..), Kaleidoscopic (..), kaleidoscopeOf)
import Proarrow.Optic.Setter (SetterFl (..))
import Proarrow.Optic.Traversal (MonTravFl (..), TravFl (..))
import Proarrow.Profunctor.Instance.Composition ((:.:) (..))
import Proarrow.Profunctor.Instance.Costar (Costar)
import Proarrow.Profunctor.Instance.Identity (Id (..))
import Proarrow.Profunctor.Representable (RepCostar (..), Representable (..))
-- | The power-grate flavor: distribute any 'MonoidalProfunctor' @r@ through the witness
-- pair. 'Proarrow.Optic.Traversal.TravFl' is a superclass: every power-grate witness is a
-- traversal witness (instantiate @r@ at a 'Proarrow.Category.Monoidal.Distributive.StrongDistributiveProfunctor',
-- a special 'MonoidalProfunctor'), so it folds, sets, and traverses. The extra power is
-- distributing the /non/-SDP monoidal profunctors as well. 'Proarrow.Optic.Kaleidoscope.KaleidoFl'
-- is a superclass too: a tensor power is an applicative functor (the reader applicative).
type PowerGrateFl :: forall {k}. FLAVOR k k
class (MonTravFl p q, GrateFl p q) => PowerGrateFl (p :: k +-> k) (q :: k +-> k) where
powerGrateP :: (MonoidalProfunctor r) => p s a -> q b t -> r a b -> r s t
instance (CategoryOf k) => PowerGrateFl (Id :: k +-> k) (Id :: k +-> k) where
powerGrateP (Id l) (Id r) = dimap l r
instance (PowerGrateFl f g, PowerGrateFl f' g') => PowerGrateFl (f :.: f') (g' :.: g) where
powerGrateP (f :.: f') (g' :.: g) = powerGrateP @f @g f g . powerGrateP @f' @g' f' g'
-- | The carrier of the literature's kaleidoscope eliminator (@>-@): @'Costar' f@, i.e. @f a -> b@ for
-- any functor @f@ on a cartesian category. Power grates distribute any 'MonoidalProfunctor', and
-- @'Costar' f@ is one, so this is 'powerGrateP' at that carrier. It is an instance (and not only
-- reachable through 'powerGrateOf') so that a power grate composed with another flavor that also
-- runs at @Costar f@, an algebraic lens say, can be eliminated there directly.
instance (Cartesian k, Functor (f :: k -> k)) => Prostrong PowerGrateFl (Costar f :: k +-> k) where
proact (f :.: c :.: g) = powerGrateP f g c
type PowerGrate (s :: k) (t :: k) a b = Optic (Prostrong PowerGrateFl) s t a b
type PowerGrate' s a = PowerGrate s s a a
-- | Distribute any 'MonoidalProfunctor' through a power grate (or any stronger optic). At the
-- hom @('~>')@ this is 'Proarrow.Optic.Setter.over'; at an applicative @'Proarrow.Profunctor.Instance.Star.Star' f@ the foci are
-- combined through @f@.
--
-- Accepts any encoding (cf. 'Proarrow.Optic.Traversal.traverseOf'), including '(%)'-composites.
powerGrateOf
:: forall {k} c (s :: k) (t :: k) a b r
. (Monoidal k, MonoidalProfunctor r, (Ob a, Ob b) => c (ExOptic PowerGrateFl a b))
=> Optic c s t a b -> r a b -> r s t
powerGrateOf o rab = withLegs @PowerGrateFl o \l r -> powerGrateP l r rab
-- * @n@-ary aggregation via tensor powers
-- | Distribute a 'MonoidalProfunctor' over the @n@-fold tensor power, by combining @n@ copies of
-- the carrier value with 'one' (at 'Z') and '**' (at 'S'). This is the profunctor-general core
-- of the @n@-ary power grate.
powDist :: forall n r a b. (SNatI n, MonoidalProfunctor r) => r a b -> r (NFold n a) (NFold n b)
powDist rab = case snat @n of
SZ -> one
SS @m -> rab ** powDist @m rab
-- | Distribute the internal hom over the tensor power: split @x ~~> aⁿ@ into @(x ~~> a)ⁿ@ using
-- 'CopyDiscard' projections. This makes an @n@-ary power grate a 'Proarrow.Optic.Grate.Grate'.
splitPow
:: forall n k (x :: k) a. (SNatI n, Closed k, CopyDiscard k, Ob x, Ob a) => (x ~~> NFold n a) ~> NFold n (x ~~> a)
splitPow = case snat @n of
SZ -> withObExp @k @x @Unit (discard @k @(x ~~> Unit))
SS @m ->
withObNFold @m @a
((fst @a @(NFold m a) ^^^ obj @x) &&& (splitPow @m @k @x @a . (snd @a @(NFold m a) ^^^ obj @x)))
-- | Zip two tensor powers into the tensor power of the tensor: the @<*>@ of the reader
-- applicative @NFold n@.
powZip
:: forall n k (a :: k) c. (SNatI n, SymMonoidal k, Ob a, Ob c) => (NFold n a ** NFold n c) ~> NFold n (a ** c)
powZip = case snat @n of
SZ -> leftUnitor @k @Unit
SS @m ->
withObNFold @m @a
(withObNFold @m @c (((obj @a ** obj @c) ** powZip @m @k @a @c) . swapInner @a @(NFold m a) @c @(NFold m c)))
-- | The tensor power of the unit is (isomorphic to) the unit.
powUnit :: forall n k. (SNatI n, Monoidal k) => Unit ~> NFold n (Unit :: k)
powUnit = case snat @n of
SZ -> id
SS @m -> (obj @(Unit :: k) ** powUnit @m @k) . leftUnitorInv @k @Unit
-- | The arity-@n@ aggregation witness: @s@ presents @n@ foci via the tensor power.
--
-- @'Pow' n@ is the representable profunctor of the tensor power @NFold n@, which is the reader
-- applicative for @n@ readers. Its instances make it a
-- 'Proarrow.Category.Monoidal.Distributive.StrongDistributiveProfunctor', hence a kaleidoscope
-- witness.
type Pow :: forall {k}. Nat -> k +-> k
data Pow n s a where
Pow :: forall (n :: Nat) {k} (s :: k) (a :: k). (Ob a) => (s ~> NFold n a) -> Pow n s a
-- | The dual of 'Pow': @t@ is rebuilt from @n@ foci.
type CoPow :: forall {k}. Nat -> k +-> k
data CoPow n b t where
CoPow :: forall (n :: Nat) {k} (b :: k) (t :: k). (Ob b) => (NFold n b ~> t) -> CoPow n b t
instance (Monoidal k, SNatI n) => Profunctor (Pow n :: k +-> k) where
dimap l r (Pow sa) = Pow (powDist @n r . sa . l) \\ l \\ r
r \\ Pow sa = r \\ sa
instance (Monoidal k, SNatI n) => Profunctor (CoPow n :: k +-> k) where
dimap l r (CoPow bt) = CoPow (r . bt . powDist @n l) \\ l \\ r
r \\ CoPow bt = r \\ bt
instance (Monoidal k, SNatI n) => SetterFl (Pow n :: k +-> k) (CoPow n :: k +-> k) where
overP (Pow sl) (CoPow rt) f = rt . powDist @n f . sl
instance (Monoidal k, SNatI n) => FoldFl (Pow n :: k +-> k) (CoPow n :: k +-> k) where
foldMapP (Pow sl) am = fanIn @n . powDist @n am . sl
instance (Monoidal k, SNatI n) => TravFl (Pow n :: k +-> k) (CoPow n :: k +-> k)
instance (Monoidal k, SNatI n) => MonTravFl (Pow n :: k +-> k) (CoPow n :: k +-> k) where
monTravP (Pow sl) (CoPow rt) rab = dimap sl rt (powDist @n rab)
-- | A power grate is a glass: ignore the source, and for each of the @n@ positions feed the
-- consumer the selector "project this focus". The selectors come from @splitPow@ of @sl@, the
-- consumer is copied @n@ times with 'fanOut', @powZip@ pairs them, and @powDist@ applies each.
-- Everything is stated with the 'CopyDiscard' structure that 'CCC' provides, so the tensor
-- and the product never have to be identified by hand.
instance (Monoidal k, HasCoproducts k, SNatI n) => GlassFl (Pow n :: k +-> k) (CoPow n :: k +-> k) where
glassP @s @a @b (Pow sl@Objs) (CoPow rt@Objs) =
withObSel @s @a @b
( withOb2 @k @s @(Mod s a b)
( rt
. powDist @n (apply @k @(s ~~> a) @b)
. powZip @n @k @(Mod s a b) @(s ~~> a)
. ( (fanOut @n @(Mod s a b) . snd @s @(Mod s a b))
&&& (splitPow @n @k @s @a . mkExponential sl . discard @k @(s ** Mod s a b))
)
)
)
instance (CopyDiscard k, HasCoproducts k, SNatI n) => GrateFl (Pow n :: k +-> k) (CoPow n :: k +-> k) where
zipWithP (Pow @_ @_ @a sl) (CoPow rt) @x kk = rt . powDist @n kk . splitPow @n @_ @x @a . (sl ^^^ obj @x)
instance (CopyDiscard k, HasCoproducts k, SNatI n) => PowerGrateFl (Pow n :: k +-> k) (CoPow n :: k +-> k) where
powerGrateP (Pow sl) (CoPow rt) rab = dimap sl rt (powDist @n rab)
-- | A tensor power is a fixed-shape traversable: distribute the carrier over the @n@ copies.
instance (Monoidal k, SNatI n) => Traversable (Pow n :: k +-> k) where
traverse @_ @_ @b (Pow f :.: p) = p // withObNFold @n @b (lmap f (powDist @n p) :.: Pow id)
instance (Monoidal k, SNatI n) => Representable (Pow n :: k +-> k) where
type Pow n % a = NFold n a
index (Pow f) = f
tabulate = Pow
repMap = powDist @n
instance (SymMonoidal k, SNatI n) => MonoidalProfunctor (Pow n :: k +-> k) where
one = Pow (powUnit @n)
Pow @_ @_ @a f ** Pow @_ @_ @c g = f // g // withOb2 @k @a @c (Pow (powZip @n @k @a @c . (f ** g)))
instance (SymMonoidal k, HasCoproducts k, SNatI n) => MonoidalProfunctor (Coprod (Pow n :: k +-> k)) where
one = withObNFold @n @(InitialObject :: k) (Coprod (Pow initiate))
Coprod (Pow @_ @_ @a f) ** Coprod (Pow @_ @_ @c g) =
withObCoprod @k @a @c (Coprod (Pow (powDist @n (lft @k @a @c) . f ||| powDist @n (rgt @k @a @c) . g)))
instance (CopyDiscard k, SNatI n) => Strong M.Tensor (Pow n :: k +-> k) where
act @x (Pow @_ @_ @a f) = f // withOb2 @k @x @a (Pow (powZip @n @k @x @a . (fanOut @n @x ** f)))
instance (CopyDiscard k, HasCoproducts k, SNatI n) => Strong CoprodAction (Pow n :: k +-> k) where
act @(COPR x) (Pow @_ @_ @a f) =
f // withObCoprod @k @x @a (Pow (powDist @n (lft @k @x @a) . fanOut @n @x ||| powDist @n (rgt @k @x @a) . f))
instance (CopyDiscard k, HasCoproducts k, SNatI n) => CotravFl (Pow n :: k +-> k) (CoPow n :: k +-> k)
instance (CopyDiscard k, HasCoproducts k, SNatI n) => KaleidoFl (Pow n :: k +-> k) (CoPow n :: k +-> k) where
kaleidoP (Pow sl) (CoPow rt) rab = dimap sl rt (kaleidoAct @_ @(Pow n) rab)
instance (Monoidal k, SNatI n) => Proadjunction (Pow n :: k +-> k) (CoPow n) where
unit @x = (CoPow id :.: Pow id) \\ powDist @n (id :: x ~> x)
counit (Pow sl :.: CoPow rt) = rt . sl
-- | Build an @n@-ary power grate from a tensor-power decomposition of @s@ and recomposition
-- of @t@.
powerGrate
:: forall {k} (n :: Nat) (s :: k) (t :: k) a b
. (CopyDiscard k, HasCoproducts k, SNatI n, Ob a, Ob b)
=> (s ~> NFold n a) -> (NFold n b ~> t) -> PowerGrate s t a b
powerGrate sl rt = legs2prof @PowerGrateFl (Pow @n sl) (CoPow @n rt)
-- | Zip two sources through a 'Proarrow.Optic.Kaleidoscope.Kaleidoscope' (or any stronger optic, a
-- 'Proarrow.Optic.Grate.Grate' in particular, in any encoding): combine the foci pairwise. This is
-- 'kaleidoscopeOf' at the carrier @'RepCostar' ('Pow' 2)@, the costar of the binary tensor power:
-- a binary combination @(a ** a) ~> b@ of foci, which the optic's applicative lifts by @liftA2@.
zipWithOf
:: forall {k} c (s :: k) (t :: k) a b
. (Monoidal k, Ob a, (Ob a, Ob b) => c (ExOptic KaleidoFl a b))
=> Optic c s t a b -> ((a ** a) ~> b) -> (s ** s) ~> t
zipWithOf o f =
case kaleidoscopeOf o (RepCostar @_ @(Pow Nat2) (f . (obj @a ** rightUnitor @k @a))) of
RepCostar @s' g -> g . (obj @s' ** rightUnitorInv @k @s')