packages feed

monoidal-functors-0.3.0.0: src/Data/Functor/Monoidal/Generic.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}

-- | Generic deriving of 'Semigroupal' instances via @kind-generics@.
--
-- The @kind-generics@ 'RepK' represents a type parameter structurally,
-- whether it occurs covariantly (as in an @Applicative@) or contravariantly
-- (as in a @Divisible@). One mechanism therefore derives
-- @'Semigroupal' (->) tC (,)@ for both, across the domain tensors @(,)@
-- (Applicative and Divisible), @Either@ (Alternative and Decidable), and
-- @These@ (Semialign).
--
-- > data Foo a = Foo (Maybe a) [a]
-- > $(deriveGenericK ''Foo)
-- > deriving via FromGeneric Foo instance Semigroupal (->) (,) (,) Foo
module Data.Functor.Monoidal.Generic
  ( FromGeneric (..),
    FromRepresentable (..),
    GSemigroupalK (..),
    GCosemigroupalK (..),
    GUnitalK (..),
  )
where

import Control.Category.Tensor (Associative, Tensor)
import Data.Distributive (Distributive)
import Data.Functor.Contravariant (Op (..))
import Data.Functor.Monoidal (Monoidal, Semigroupal (..), Unital (..))
import Generics.Kind
import Prelude

-- | @combine@ over a @kind-generics@ 'RepK', for the product codomain tensor
-- @(,)@ and an arbitrary domain tensor @tC@.
class GSemigroupalK tC rep where
  gcombineK :: rep (x :&&: LoT0) -> rep (x' :&&: LoT0) -> rep (tC x x' :&&: LoT0)

-- | The empty constructor.
instance GSemigroupalK tC U1 where
  gcombineK U1 U1 = U1

-- | Metadata is transparent.
instance (GSemigroupalK tC f) => GSemigroupalK tC (M1 i c f) where
  gcombineK (M1 a) (M1 b) = M1 (gcombineK a b)

-- | Combine each side of a product componentwise, for any codomain tensor.
instance (GSemigroupalK tC f, GSemigroupalK tC g) => GSemigroupalK tC (f :*: g) where
  gcombineK (a :*: b) (a' :*: b') = gcombineK a a' :*: gcombineK b b'

-- | The bare parameter, like 'GHC.Generics.Par1'. Only the product tensor
-- @(,)@ has a canonical merge of two values into one.
instance GSemigroupalK (,) (Field Var0) where
  gcombineK (Field x) (Field x') = Field (x, x')

-- | A constant field merges with its 'Monoid'. The value does not mention the
-- parameter, so this holds for any tensor.
instance (Monoid c) => GSemigroupalK tC (Field (Kon c)) where
  gcombineK (Field c) (Field c') = Field (c <> c')

-- | A sub-functor @g@ applied to the parameter delegates to @g@'s own
-- 'Semigroupal' instance at tensor @tC@. This holds whether @g@ is covariant
-- (Applicative, Alternative, Semialign) or contravariant (Divisible, Decidable).
instance (Semigroupal (->) tC (,) g) => GSemigroupalK tC (Field (Kon g :@: Var0)) where
  gcombineK (Field a) (Field b) = Field (combine @(->) @tC @(,) (a, b))

-- | A nested field @f (g a)@. Product-combine the outer functor, then map the
-- inner combine over it.
instance
  (Semigroupal (->) (,) (,) f, Functor f, Semigroupal (->) tC (,) g) =>
  GSemigroupalK tC (Field (Kon f :@: (Kon g :@: Var0)))
  where
  gcombineK (Field a) (Field b) =
    Field (fmap (combine @(->) @tC @(,)) (combine @(->) @(,) @(,) (a, b)))

-- | A bare function field @x -> r@, the shape of primitive contravariants like
-- 'Data.Functor.Contravariant.Predicate' and @'Op' r@. @kind-generics@
-- represents it as an application of @(->)@. It delegates to the @'Op' r@
-- instance at the same tensor (Divisible at @(,)@, Decidable at @Either@).
instance
  (Semigroupal (->) tC (,) (Op r)) =>
  GSemigroupalK tC (Field ((Kon (->) :@: Var0) :@: Kon r))
  where
  gcombineK (Field h1) (Field h2) =
    Field (getOp (combine @(->) @tC @(,) (Op h1, Op h2)))

-- | The deriving-via vehicle.
--
-- > deriving via FromGeneric Foo instance Semigroupal (->) tC (,) Foo
newtype FromGeneric f a = FromGeneric (f a)

instance
  (GenericK f, GSemigroupalK tC (RepK f), Associative (->) tC) =>
  Semigroupal (->) tC (,) (FromGeneric f)
  where
  combine ::
    forall x x'.
    (FromGeneric f x, FromGeneric f x') ->
    FromGeneric f (tC x x')
  combine (FromGeneric fx, FromGeneric fx') =
    FromGeneric
      ( toK @_ @f @(tC x x' :&&: LoT0)
          (gcombineK (fromK @_ @f @(x :&&: LoT0) fx) (fromK @_ @f @(x' :&&: LoT0) fx'))
      )

--------------------------------------------------------------------------------
-- Split (Op), the comonoidal direction for the product tensor

-- | @combine@ over @Op@ for the product tensor. Splits one structure into two.
--
-- @rep ((x, x') :&&: LoT0) -> (rep (x :&&: LoT0), rep (x' :&&: LoT0))@
class GCosemigroupalK rep where
  gcosplitK :: rep ((x, x') :&&: LoT0) -> (rep (x :&&: LoT0), rep (x' :&&: LoT0))

instance GCosemigroupalK U1 where
  gcosplitK U1 = (U1, U1)

instance (GCosemigroupalK f) => GCosemigroupalK (M1 i c f) where
  gcosplitK (M1 a) = let (l, r) = gcosplitK a in (M1 l, M1 r)

instance (GCosemigroupalK f, GCosemigroupalK g) => GCosemigroupalK (f :*: g) where
  gcosplitK (a :*: b) =
    let (al, ar) = gcosplitK a
        (bl, br) = gcosplitK b
     in (al :*: bl, ar :*: br)

-- | Splitting a sum is total. The single input constructor determines matched
-- outputs. @:+:@ has none of the mismatched-constructor trouble that blocks
-- the combine direction.
instance (GCosemigroupalK f, GCosemigroupalK g) => GCosemigroupalK (f :+: g) where
  gcosplitK (L1 a) = let (l, r) = gcosplitK a in (L1 l, L1 r)
  gcosplitK (R1 b) = let (l, r) = gcosplitK b in (R1 l, R1 r)

-- | The bare parameter splits the pair.
instance GCosemigroupalK (Field Var0) where
  gcosplitK (Field (x, x')) = (Field x, Field x')

-- | A constant field is duplicated.
instance GCosemigroupalK (Field (Kon c)) where
  gcosplitK (Field c) = (Field c, Field c)

-- | A sub-functor uses the functorial unzip @fmap fst@ and @fmap snd@. This is
-- total for any 'Functor' and gives the standalone split.
instance (Functor g) => GCosemigroupalK (Field (Kon g :@: Var0)) where
  gcosplitK (Field gxx) = (Field (fmap fst gxx), Field (fmap snd gxx))

-- | A nested field @f (g a)@ splits with a double @fmap@.
instance (Functor f, Functor g) => GCosemigroupalK (Field (Kon f :@: (Kon g :@: Var0))) where
  gcosplitK (Field fgxx) = (Field (fmap (fmap fst) fgxx), Field (fmap (fmap snd) fgxx))

-- | The generic split, shared by both vehicles below.
gsplit ::
  forall f x x'.
  (GenericK f, GCosemigroupalK (RepK f)) =>
  f (x, x') ->
  (f x, f x')
gsplit fxx =
  let (l, r) = gcosplitK (fromK @_ @f @((x, x') :&&: LoT0) fxx)
   in (toK @_ @f @(x :&&: LoT0) l, toK @_ @f @(x' :&&: LoT0) r)

-- | The standalone split.
--
-- > deriving via FromGeneric Foo instance Semigroupal Op (,) (,) Foo
--
-- This has unzip semantics and is total, even on sums. It does not always
-- invert the cartesian @combine@.
instance
  (GenericK f, GCosemigroupalK (RepK f)) =>
  Semigroupal Op (,) (,) (FromGeneric f)
  where
  combine :: Op (FromGeneric f x, FromGeneric f x') (FromGeneric f (x, x'))
  combine = Op (\(FromGeneric fxx) -> let (a, b) = gsplit fxx in (FromGeneric a, FromGeneric b))

-- | The coherent split, gated on 'Distributive'. A split inverts the cartesian
-- @combine@ when the functor is representable. This vehicle compiles
-- only for representable functors.
--
-- > deriving via FromRepresentable Foo instance Semigroupal Op (,) (,) Foo
newtype FromRepresentable f a = FromRepresentable (f a)

instance
  (GenericK f, GCosemigroupalK (RepK f), Distributive f) =>
  Semigroupal Op (,) (,) (FromRepresentable f)
  where
  combine :: Op (FromRepresentable f x, FromRepresentable f x') (FromRepresentable f (x, x'))
  combine = Op (\(FromRepresentable fxx) -> let (a, b) = gsplit fxx in (FromRepresentable a, FromRepresentable b))

--------------------------------------------------------------------------------
-- Unital and Monoidal

-- | @introduce@ over a @kind-generics@ 'RepK', building the unit-filled
-- structure at a domain unit @iC@. This is @()@ for the product tensor
-- (Applicative and Divisible) and @Void@ for @Either@ and @These@ (Alternative
-- and Decidable).
class GUnitalK iC rep where
  gintroduceK :: rep (iC :&&: LoT0)

instance GUnitalK iC U1 where
  gintroduceK = U1

instance (GUnitalK iC f) => GUnitalK iC (M1 i c f) where
  gintroduceK = M1 gintroduceK

instance (GUnitalK iC f, GUnitalK iC g) => GUnitalK iC (f :*: g) where
  gintroduceK = gintroduceK :*: gintroduceK

-- | A bare parameter can only be introduced at the product unit @()@, the one
-- unit object with a canonical inhabitant. There is no value of @Void@ to place
-- here. A type with a bare-parameter field has no @Either@/@These@ unit.
instance GUnitalK () (Field Var0) where
  gintroduceK = Field ()

-- | A constant field is the 'Monoid' identity, at any unit.
instance (Monoid c) => GUnitalK iC (Field (Kon c)) where
  gintroduceK = Field mempty

-- | A sub-functor delegates to its own 'Unital' instance at the same unit.
instance (Unital (->) iC () g) => GUnitalK iC (Field (Kon g :@: Var0)) where
  gintroduceK = Field (introduce @(->) @iC @() ())

-- | A nested field @f (g iC)@. The outer functor at the product unit, the inner
-- at @iC@, mirroring the nested @combine@.
instance
  (Functor f, Unital (->) () () f, Unital (->) iC () g) =>
  GUnitalK iC (Field (Kon f :@: (Kon g :@: Var0)))
  where
  gintroduceK =
    Field (fmap (const (introduce @(->) @iC @() ())) (introduce @(->) @() @() ()))

-- | A bare function field's unit delegates to @'Op' r@ (@conquer@ at @()@,
-- @lose@ at @Void@).
instance
  (Unital (->) iC () (Op r)) =>
  GUnitalK iC (Field ((Kon (->) :@: Var0) :@: Kon r))
  where
  gintroduceK = Field (getOp (introduce @(->) @iC @() ()))

-- | The unit vehicle, at any domain unit @iC@:
--
-- > deriving via FromGeneric Foo instance Unital (->) () () Foo
-- > deriving via FromGeneric Foo instance Unital (->) Void () Foo
instance (GenericK f, GUnitalK iC (RepK f)) => Unital (->) iC () (FromGeneric f) where
  introduce :: () -> FromGeneric f iC
  introduce () = FromGeneric (toK @_ @f @(iC :&&: LoT0) gintroduceK)

-- | @Monoidal@ from 'Semigroupal' plus 'Unital', for any codomain
-- tensor @tC@ paired with its unit @iC@:
--
-- > deriving via FromGeneric Foo instance Monoidal (->) (,) () (,) () Foo
-- > deriving via FromGeneric Foo instance Monoidal (->) Either Void (,) () Foo
instance
  (GenericK f, GSemigroupalK tC (RepK f), GUnitalK iC (RepK f), Tensor (->) tC iC) =>
  Monoidal (->) tC iC (,) () (FromGeneric f)