barbies-2.0.1.0: src/Barbies/Internal/DistributiveB.hs
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Barbies.Internal.DistributiveB
( DistributiveB(..)
, bdistribute'
, bcotraverse
, bdecompose
, brecompose
, gbdistributeDefault
, CanDeriveDistributiveB
)
where
import Barbies.Internal.FunctorB (FunctorB(..))
import Barbies.Generics.Distributive (GDistributive(..))
import Data.Functor.Compose (Compose (..))
import Data.Functor.Identity (Identity (..))
import Data.Functor.Product (Product (..))
import Data.Generics.GenericN
import Data.Proxy (Proxy (..))
import Data.Distributive
import Data.Kind (Type)
-- | A 'FunctorB' where the effects can be distributed to the fields:
-- `bdistribute` turns an effectful way of building a Barbie-type
-- into a pure Barbie-type with effectful ways of computing the
-- values of its fields.
--
-- This class is the categorical dual of `Barbies.Internal.TraversableB.TraversableB`,
-- with `bdistribute` the dual of `Barbies.Internal.TraversableB.bsequence`
-- and `bcotraverse` the dual of `Barbies.Internal.TraversableB.btraverse`. As such,
-- instances need to satisfy these laws:
--
-- @
-- 'bdistribute' . h = 'bmap' ('Compose' . h . 'getCompose') . 'bdistribute' -- naturality
-- 'bdistribute' . 'Data.Functor.Identity' = 'bmap' ('Compose' . 'Data.Functor.Identity') -- identity
-- 'bdistribute' . 'Compose' = 'bmap' ('Compose' . 'Compose' . 'fmap' 'getCompose' . 'getCompose') . 'bdistribute' . 'fmap' 'bdistribute' -- composition
-- @
--
-- By specializing @f@ to @((->) a)@ and @g@ to 'Identity', we can define a function that
-- decomposes a function on distributive barbies into a collection of simpler functions:
--
-- @
-- 'bdecompose' :: 'DistributiveB' b => (a -> b 'Identity') -> b ((->) a)
-- 'bdecompose' = 'bmap' ('fmap' 'runIdentity' . 'getCompose') . 'bdistribute'
-- @
--
-- Lawful instances of the class can then be characterized as those that satisfy:
--
-- @
-- 'brecompose' . 'bdecompose' = 'id'
-- 'bdecompose' . 'brecompose' = 'id'
-- @
--
-- This means intuitively that instances need to have a fixed shape (i.e. no sum-types can be involved).
-- Typically, this means record types, as long as they don't contain fields where the functor argument is not applied.
--
--
-- There is a default implementation of 'bdistribute' based on
-- 'Generic'. Intuitively, it works on product types where the shape
-- of a pure value is uniquely defined and every field is covered by
-- the argument @f@.
class (FunctorB b) => DistributiveB (b :: (k -> Type) -> Type) where
bdistribute :: Functor f => f (b g) -> b (Compose f g)
default bdistribute
:: forall f g
. CanDeriveDistributiveB b f g
=> Functor f => f (b g) -> b (Compose f g)
bdistribute = gbdistributeDefault
-- | A version of `bdistribute` with @g@ specialized to `Identity`.
bdistribute' :: (DistributiveB b, Functor f) => f (b Identity) -> b f
bdistribute' = bmap (fmap runIdentity . getCompose) . bdistribute
-- | Dual of `Barbies.Internal.TraversableB.btraverse`
bcotraverse :: (DistributiveB b, Functor f) => (forall a . f (g a) -> f a) -> f (b g) -> b f
bcotraverse h = bmap (h . getCompose) . bdistribute
-- | Decompose a function returning a distributive barbie, into
-- a collection of simpler functions.
bdecompose :: DistributiveB b => (a -> b Identity) -> b ((->) a)
bdecompose = bdistribute'
-- | Recompose a decomposed function.
brecompose :: FunctorB b => b ((->) a) -> a -> b Identity
brecompose bfs = \a -> bmap (Identity . ($ a)) bfs
-- | @'CanDeriveDistributiveB' B f g@ is in practice a predicate about @B@ only.
-- Intuitively, it says the the following holds for any arbitrary @f@:
--
-- * There is an instance of @'Generic' (B f)@.
--
-- * @(B f)@ has only one constructor, and doesn't contain "naked" fields
-- (that is, not covered by `f`).
--
-- * @B f@ can contain fields of type @b f@ as long as there exists a
-- @'DistributiveB' b@ instance. In particular, recursive usages of @B f@
-- are allowed.
--
-- * @B f@ can also contain usages of @b f@ under a @'Distributive' h@.
-- For example, one could use @a -> (B f)@ as a field of @B f@.
type CanDeriveDistributiveB b f g
= ( GenericP 0 (b g)
, GenericP 0 (b (Compose f g))
, GDistributive 0 f (RepP 0 (b g)) (RepP 0 (b (Compose f g)))
)
-- | Default implementation of 'bdistribute' based on 'Generic'.
gbdistributeDefault
:: CanDeriveDistributiveB b f g
=> Functor f => f (b g) -> b (Compose f g)
gbdistributeDefault
= toP (Proxy @0) . gdistribute (Proxy @0) . fmap (fromP (Proxy @0))
{-# INLINE gbdistributeDefault #-}
-- ------------------------------------------------------------
-- Generic derivation: Special cases for DistributiveB
-- -----------------------------------------------------------
type P = Param
instance
( Functor f
, DistributiveB b
) => GDistributive 0 f (Rec (b' (P 0 g)) (b g)) (Rec (b' (P 0 (Compose f g))) (b (Compose f g)))
where
gdistribute _ = Rec . K1 . bdistribute . fmap (unK1 . unRec)
{-# INLINE gdistribute #-}
instance
( Functor f
, Distributive h
, DistributiveB b
) =>
GDistributive n f (Rec (h (b (P n g))) (h (b g))) (Rec (h (b (P n (Compose f g)))) (h (b (Compose f g))))
where
gdistribute _ = Rec . K1 . fmap bdistribute . distribute . fmap (unK1 . unRec)
{-# INLINE gdistribute #-}
-- --------------------------------
-- Instances for base types
-- --------------------------------
instance DistributiveB Proxy where
bdistribute _ = Proxy
{-# INLINE bdistribute #-}
fstF :: Product f g a -> f a
fstF (Pair x _y) = x
sndF :: Product f g a -> g a
sndF (Pair _x y) = y
instance (DistributiveB a, DistributiveB b) => DistributiveB (Product a b) where
bdistribute xy = Pair (bdistribute $ fstF <$> xy) (bdistribute $ sndF <$> xy)
{-# INLINE bdistribute #-}
instance (Distributive h, DistributiveB b) => DistributiveB (h `Compose` b) where
bdistribute = Compose . fmap bdistribute . distribute . fmap getCompose
{-# INLINE bdistribute #-}