packages feed

distributors-0.4.0.0: src/Control/Lens/Grate.hs

{- |
Module      : Control.Lens.Grate
Description : grates
Copyright   : (C) 2026 - Eitan Chatav
License     : BSD-style (see the file LICENSE)
Maintainer  : Eitan Chatav <eitan.chatav@gmail.com>
Stability   : provisional
Portability : non-portable

See O'Connor, [Grate: A new kind of Optic]
(https://r6research.livejournal.com/28050.html)
-}

module Control.Lens.Grate
  ( -- * Grate
    Grate
  , AGrate
    -- * Combinators
  , grate
  , withGrate
  , cloneGrate
  , mapGrate
  , cotraversed
  , represented
  , cotraverseOf
  , distributeOf
  , collectOf
  , distributing
    -- * Grating
  , Grating (..)
  ) where

import Data.Distributive
import Data.Function
import Data.Functor.Identity
import Data.Functor.Rep
import Data.Profunctor
import Data.Profunctor.Monoidal

{- | `Grate`s are an optic that are dual to
`Control.Lens.Traversal.Traversal`s, as `Distributive` is `Traversable`.

Every `Control.Lens.Monocle.Monocle` is a `Grate`.

`Grate`s are isomorphic to `Grating`s.
-}
type Grate s t a b = forall p f.
  (Closed p, Monoidal p, Distributive f, Applicative f)
    => p a (f b) -> p s (f t)

{- | `AGrate` is monomorphically a `Grate`. -}
type AGrate s t a b =
  Grating a b a (Identity b) -> Grating a b s (Identity t)

{- | Build a `Grate`. -}
grate :: (((s -> a) -> b) -> t) -> Grate s t a b
grate f = dimap (&) (cotraverse f) . closed

{- | Build a `Grate` from a `Distributive`. -}
cotraversed :: Distributive g => Grate (g a) (g b) a b
cotraversed = grate $ flip cotraverse id

{- | Build a `Grate` from a `Representable`. -}
represented :: Representable g => Grate (g a) (g b) a b
represented = grate $ tabulate . (. flip index)

{- | Action of `AGrate` on `Closed` `Profunctor`s. -}
mapGrate :: Closed p => AGrate s t a b -> p a b -> p s t
mapGrate grt = dimap (&) (withGrate grt) . closed

{- | Clone `AGrate` so that you can reuse the same
monomorphically typed `Grate` for different purposes.
-}
cloneGrate :: AGrate s t a b -> Grate s t a b
cloneGrate = grate . withGrate

{- | Run `AGrate`. -}
withGrate :: AGrate s t a b -> ((s -> a) -> b) -> t
withGrate grt = runGrating $ runIdentity <$> grt (Identity <$> Grating ($ id))

{- | Distribute over a `Closed` `Profunctor`. -}
distributing
  :: (Closed p, forall x. Functor (p x), Distributive g)
  => AGrate s t a b -> p a (g b) -> g (p s t)
distributing grt
  = distribute
  . dimap (&) (cotraverse (withGrate grt))
  . closed

{- | Dual to `Control.Lens.Combinators.traverseOf`. -}
cotraverseOf :: Functor f => AGrate s t a b -> (f a -> b) -> f s -> t
cotraverseOf grt = runCostar . mapGrate grt . Costar

{- | Dual to `Control.Lens.Combinators.sequenceAOf`. -}
distributeOf :: Functor f => AGrate s t b (f b) -> f s -> t
distributeOf grt = cotraverseOf grt id

{- | `collect` with `AGrate`. -}
collectOf :: Functor f => AGrate s t b (f b) -> (a -> s) -> f a -> t
collectOf grt f = distributeOf grt . fmap f

{- | `Grating` provides an efficient
concrete representation of `Grate`s. -}
newtype Grating a b s t = Grating
  {runGrating :: ((s -> a) -> b) -> t}
instance Functor (Grating a b s) where fmap = fmapRep
instance Applicative (Grating a b s) where
  pure = pureRep
  (<*>) = apRep
instance Distributive (Grating a b s) where
  distribute = distributeRep
  collect = collectRep
instance Representable (Grating a b s) where
  type Rep (Grating a b s) = (s -> a) -> b
  index (Grating k) f = k f
  tabulate = Grating