proarrow-0.1.0.0: src/Proarrow/Optic/Glass.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
-- | The __glass__ (Clarke et al., /Profunctor optics: a categorical update/): the optic for the
-- combined action of the product and the exponential,
--
-- > Glass s t a b = exists c d. (s ~> c && (d ~~> a), (c && (d ~~> b)) ~> t)
--
-- which collapses to the single leg @(s && ((s ~~> a) ~~> b)) ~> t@: given the source and a way
-- to turn any selector @s ~~> a@ into a @b@, produce a @t@. A lens is the case @d = Unit@, a grate
-- the case @c = Unit@, so 'GlassFl' is the join of 'Proarrow.Optic.Lens.LensFl' and
-- 'Proarrow.Optic.Grate.GrateFl'. Like 'Proarrow.Optic.AffineTraversal.AffineTravFl' it has no
-- witnesses of its own: its generating pairs are the product pair and the exponential pair, and
-- 'glass' packs its single leg as their composite.
--
-- It sits directly below 'Proarrow.Optic.Setter.SetterFl': a glass sets, but it neither folds
-- (grates do not) nor distributes an applicative (lenses do not).
module Proarrow.Optic.Glass where
import Prelude (($))
import Proarrow.Category.Monoidal (Monoidal (..), MonoidalProfunctor (..), SymMonoidal (..), type (**))
import Proarrow.Category.Monoidal.Cartesian (CCC, productToTensor, tensorToProduct)
import Proarrow.Category.Monoidal.Closed (Closed (..), Exp, comp, mkExponential, swapClosed)
import Proarrow.Category.Monoidal.CopyDiscard (fst, snd, (&&&))
import Proarrow.Core (CategoryOf (..), Promonad (..), obj, type (+->))
import Proarrow.Limit.BinaryProduct (HasBinaryProducts (type (&&)), Product)
import Proarrow.Limit.BinaryProduct qualified as P
import Proarrow.Object (pattern Objs)
import Proarrow.Optic
( ExOptic
, FLAVOR
, Optic
, Prostrong (..)
, legs2prof
, withLegs
)
import Proarrow.Optic.Setter (SetterFl)
import Proarrow.Profunctor.Corepresentable (Corep (..))
import Proarrow.Profunctor.Instance.Composition ((:.:) (..))
import Proarrow.Profunctor.Instance.Identity (Id (..))
import Proarrow.Profunctor.Representable (Rep (..))
-- | The glass flavor. Its one method is the collapsed leg; everything is stated in a cartesian
-- closed category, where the residual can be copied and selectors can be internalised.
type GlassFl :: forall {k}. FLAVOR k k
class (SetterFl p q) => GlassFl (p :: k +-> k) (q :: k +-> k) where
glassP :: (CCC k) => p s a -> q b t -> (s && Mod s a b) ~> t
-- | A /modifier/: given a selector @s '~~>' a@ for reading the focus out of the source, it
-- produces the new focus @b@. It is the right half of a glass's single leg, and the whole of a
-- 'Proarrow.Optic.Grate.grate'\'s argument.
type Mod :: forall {k}. k -> k -> k -> k
type Mod s a b = (s ~~> a) ~~> b
-- | Feed a fixed selector @s ~> a@ to a 'Mod'.
applySel :: forall {k} (s :: k) a b. (Closed k, Ob s, Ob a, Ob b) => (s ~> a) -> Mod s a b ~> b
applySel sel =
withObSel @s @a @b $
apply @k @(s ~~> a) @b . (obj @(Mod s a b) ** mkExponential sel) . rightUnitorInv @k @(Mod s a b)
-- | The two 'Ob' facts every modifier needs: the selector type @s '~~>' a@ and the 'Mod' that
-- consumes it. Each 'GlassFl' instance below opens with this.
withObSel
:: forall {k} (s :: k) a b r
. (Closed k, Ob s, Ob a, Ob b) => ((Ob (s ~~> a), Ob (Mod s a b)) => r) -> r
withObSel r = withObExp @k @s @a (withObExp @k @(s ~~> a) @b r)
-- | The product pair, a lens witness: the selector is the lens's own @get@, applied to the source
-- at hand; the residual is kept.
instance (HasBinaryProducts k, Ob (c :: k)) => GlassFl (Rep (Product c)) (Corep (Product c)) where
glassP @s @a @b (Rep h@Objs) (Corep i) =
withObSel @s @a @b $
i
. tensorToProduct @c @b
. ( (P.fst @k @c @a . h . fst @s @(Mod s a b))
&&& (applySel @s @a @b (P.snd @k @c @a . h) . snd @s @(Mod s a b))
)
. productToTensor @s @(Mod s a b)
-- | The exponential pair, a grate witness: the source is ignored, and the consumer is fed the
-- selector @\\s -> h s d@ for each point @d@ of the exponent.
instance (Closed k, Ob (d :: k)) => GlassFl (Rep (Exp d)) (Corep (Exp d)) where
glassP @s @a @b (Rep h@Objs) (Corep i) =
withObSel @s @a @b $
i
. curry @k @(Mod s a b) @d (apply @k @(s ~~> a) @b . (obj @(Mod s a b) ** swapClosed @a @s @d h))
. snd @s @(Mod s a b)
. productToTensor @s @(Mod s a b)
instance (CategoryOf k) => GlassFl (Id :: k +-> k) (Id :: k +-> k) where
glassP @s @a @b (Id l@Objs) (Id r@Objs) =
withObSel @s @a @b $
r . applySel @s @a @b l . snd @s @(Mod s a b) . productToTensor @s @(Mod s a b)
-- | Composition threads the selector through: the outer glass is given the consumer
-- @\\sel -> inner (sel s, \\sel' -> k (sel' . sel))@.
instance
forall k (f :: k +-> k) (f' :: k +-> k) (g :: k +-> k) (g' :: k +-> k)
. (GlassFl f g, GlassFl f' g')
=> GlassFl (f :.: f') (g' :.: g)
where
glassP @s @a @b (f@Objs :.: (f'@Objs :: f' x a)) ((g'@Objs :: g' b y) :.: g@Objs) =
withObSel @s @a @b $
withObSel @s @x @y $
withObSel @x @a @b $
withOb2 @k @s @(Mod s a b) $
withOb2 @k @(s ** Mod s a b) @(s ~~> x) $
withOb2 @k @((s ** Mod s a b) ** (s ~~> x)) @(x ~~> a) $
let
-- the inner glass, fed a product-typed pair
inner = glassP @f' @g' f' g' . tensorToProduct @x @(Mod x a b)
-- the source of the inner glass: the outer selector applied to @s@
xpart =
apply @k @s @x
. ( snd @(s ** Mod s a b) @(s ~~> x)
&&& (fst @s @(Mod s a b) . fst @(s ** Mod s a b) @(s ~~> x))
)
-- the inner consumer: compose the selectors, hand the result to @k@
kk =
snd @s @(Mod s a b)
. fst @(s ** Mod s a b) @(s ~~> x)
. fst @((s ** Mod s a b) ** (s ~~> x)) @(x ~~> a)
sel =
comp @s @x @a
. ( snd @((s ** Mod s a b) ** (s ~~> x)) @(x ~~> a)
&&& (snd @(s ** Mod s a b) @(s ~~> x) . fst @((s ** Mod s a b) ** (s ~~> x)) @(x ~~> a))
)
kipart = curry @k @((s ** Mod s a b) ** (s ~~> x)) @(x ~~> a) (apply @k @(s ~~> a) @b . (kk &&& sel))
body = inner . (xpart &&& kipart)
in
glassP @f @g f g
. tensorToProduct @s @(Mod s x y)
. (fst @s @(Mod s a b) &&& curry @k @(s ** Mod s a b) @(s ~~> x) body)
. productToTensor @s @(Mod s a b)
type Glass (s :: k) (t :: k) a b = Optic (Prostrong GlassFl) s t a b
type Glass' s a = Glass s s a a
-- | Build a glass from its single leg. The residuals are the whole source and the "logarithm"
-- @s ~~> a@, so the witness is the lens witness at @s@ composed with the grate witness at @s ~~> a@.
glass
:: forall {k} (s :: k) (t :: k) a b
. (CCC k, Ob s, Ob a, Ob b)
=> ((s && Mod s a b) ~> t) -> Glass s t a b
glass f =
withObSel @s @a @a $
withObExp @k @(s ~~> a) @b $
let ev = curry @k @s @(s ~~> a) (apply @k @s @a . swap @k @s @(s ~~> a))
in legs2prof @GlassFl
(Rep @(Mod s a a) @(Product s) (id P.&&& ev) :.: Rep @a @(Exp (s ~~> a)) (obj @(Mod s a a)))
(Corep @b @(Exp (s ~~> a)) (obj @(Mod s a b)) :.: Corep @(Mod s a b) @(Product s) f)
-- | Eliminate any glass-flavored optic (a lens, a grate, or a composite of both, in either
-- encoding) to its single leg.
withGlass
:: forall {k} c (s :: k) (t :: k) a b r
. (CCC k, (Ob a, Ob b) => c (ExOptic GlassFl a b))
=> Optic c s t a b -> (((s && Mod s a b) ~> t) -> r) -> r
withGlass o k = withLegs @GlassFl o \ @p @q p q -> k (glassP @p @q p q)