packages feed

monoidal-functors-0.3.0.0: src/Data/Bifunctor/Monoidal.hs

module Data.Bifunctor.Monoidal
  ( -- * Semigroupal
    Semigroupal (..),
    (|??|),
    (|**|),
    (|*+|),
    (|*&|),
    (|++|),
    (|+*|),
    (|+&|),
    (|&&|),
    (|&*|),
    (|&+|),

    -- * Unital
    Unital (..),

    -- * Monoidal
    Monoidal,

    -- * Newtypes for Deriving Via
    FromBiapplicative (..),
    StrongCategory (..),
  )
where

--------------------------------------------------------------------------------

import Control.Applicative (Alternative (..), Applicative (..), pure, (<$>))
import Control.Arrow (Kleisli (..))
import Control.Category (Category (..))
import Control.Category.Cartesian (Cocartesian (..))
import Control.Category.Tensor (Associative, Iso (..), Tensor (..), gbimap)
import Control.Monad (Functor (..), Monad)
import Data.Biapplicative (Biapplicative (..), Bifunctor (..))
import Data.Bifunctor.Biap (Biap (..))
import Data.Bifunctor.Biff (Biff (..))
import Data.Bifunctor.Clown (Clown (..))
import Data.Bifunctor.Flip (Flip (..))
import Data.Bifunctor.Joker (Joker (..))
import Data.Bifunctor.Product (Product (..))
import Data.Bifunctor.Tannen (Tannen (..))
import Data.Bifunctor.Wrapped (WrappedBifunctor (..))
import Data.Either (Either, either)
import Data.Function (const, ($))
import Data.Functor.Const (Const (..))
import Data.Functor.Contravariant (Op (..))
import Data.Functor.Monoidal qualified as F
import Data.Profunctor (Forget (..), Profunctor (..), Star (..), Strong (..))
import Data.Semigroup (Arg (..))
import Data.Semigroupoid (Semigroupoid (..))
import Data.Tagged (Tagged (..))
import Data.These (These (..), these)
import Data.Tuple (fst, snd, uncurry)
import Data.Void (Void, absurd)
import Prelude (Either (..), Monoid, curry)

--------------------------------------------------------------------------------

-- $setup
-- >>> :set -dppr-cols=1000
-- >>> import Prelude
-- >>> import Data.Void (Void)

--------------------------------------------------------------------------------

-- | Given monoidal categories \((\mathcal{C}, \otimes, I_{\mathcal{C}})\) and \((\mathcal{D}, \bullet, I_{\mathcal{D}})\).
-- A bifunctor \(F : \mathcal{C_1} \times \mathcal{C_2} \to \mathcal{D}\) is 'Semigroupal' if it supports a
-- natural transformation \(\phi_{AB,CD} : F\ A\ B \bullet F\ C\ D \to F\ (A \otimes C)\ (B \otimes D)\), which we call 'combine'.
--
-- === Laws
--
-- __Associativity:__
--
-- \[
-- \require{AMScd}
-- \begin{CD}
-- (F A B \bullet F C D) \bullet F X Y                      @>>{\alpha_{\mathcal{D}}}>                              F A B \bullet (F C D \bullet F X Y) \\
-- @VV{\phi_{AB,CD} \bullet 1}V                                                                                     @VV{1 \bullet \phi_{CD,FY}}V \\
-- F (A \otimes C) (B \otimes D) \bullet F X Y              @.                                                      F A B \bullet (F (C \otimes X) (D \otimes Y) \\
-- @VV{\phi_{(A \otimes C)(B \otimes D),XY}}V                                                                       @VV{\phi_{AB,(C \otimes X)(D \otimes Y)}}V \\
-- F ((A \otimes C) \otimes X)  ((B \otimes D) \otimes Y)   @>>{F \alpha_{\mathcal{C_1}}} \alpha_{\mathcal{C_2}}>   F (A \otimes (C \otimes X)) (B \otimes (D \otimes Y)) \\
-- \end{CD}
-- \]
--
-- @
-- 'combine' 'Control.Category..' 'Control.Category.Tensor.grmap' 'combine' 'Control.Category..' 'bwd' 'Control.Category.Tensor.assoc' ≡ 'fmap' ('bwd' 'Control.Category.Tensor.assoc') 'Control.Category..' 'combine' 'Control.Category..' 'Control.Category.Tensor.glmap' 'combine'
-- @
class (Associative cat tC1, Associative cat tC2, Associative cat tD) => Semigroupal cat tC1 tC2 tD f where
  -- | A natural transformation \(\phi_{AB,CD} : F\ A\ B \bullet F\ C\ D \to F\ (A \otimes C)\ (B \otimes D)\).
  --
  -- ==== __Examples__
  --
  -- >>> combine @(->) @(,) @(,) @(,) @(,) ((True, "Hello"), ((), "World"))
  -- ((True,()),("Hello","World"))
  --
  -- >>> combine @(->) @(,) @(,) @(,) @(->) (show, (>10)) (True, 11)
  -- ("True",True)
  combine :: cat (tD (f x y) (f x' y')) (f (tC1 x x') (tC2 y y'))

newtype FromBiapplicative p a b = FromBiapplicative (p a b)
  deriving newtype (Functor, Bifunctor, Biapplicative)

instance (Biapplicative p) => Semigroupal (->) (,) (,) (,) (FromBiapplicative p) where
  combine :: (FromBiapplicative p x y, FromBiapplicative p x' y') -> FromBiapplicative p (x, x') (y, y')
  combine = uncurry $ biliftA2 (,) (,)

deriving via FromBiapplicative (,) instance Semigroupal (->) (,) (,) (,) (,)

-- Colax (Op) splits. The split @f (tC1 x x') (tC2 y y') -> tD (f x y) (f x' y')@,
-- dual to the covariant @combine@.

-- | The product transpose, inverse to the covariant @(,)@ combine.
instance Semigroupal Op (,) (,) (,) (,) where
  combine :: Op ((x, y), (x', y')) ((x, x'), (y, y'))
  combine = Op (\((x, x'), (y, y')) -> ((x, y), (x', y')))

-- | The coproduct rearrangement, inverse to the covariant @Either@ combine.
instance Semigroupal Op Either Either Either Either where
  combine :: Op (Either (Either x y) (Either x' y')) (Either (Either x x') (Either y y'))
  combine = Op $ \case
    Left (Left x) -> Left (Left x)
    Left (Right x') -> Right (Left x')
    Right (Left y) -> Left (Right y)
    Right (Right y') -> Right (Right y')

-- | Tuples transpose the parameter pairs and duplicate the leading @Monoid@ tags.
-- The split needs no @Monoid@ (it duplicates rather than combines).
instance Semigroupal Op (,) (,) (,) ((,,) x) where
  combine :: Op ((x, a, b), (x, a', b')) (x, (a, a'), (b, b'))
  combine = Op (\(x, (a, a'), (b, b')) -> ((x, a, b), (x, a', b')))

instance Semigroupal Op (,) (,) (,) ((,,,) x y) where
  combine :: Op ((x, y, a, b), (x, y, a', b')) (x, y, (a, a'), (b, b'))
  combine = Op (\(x, y, (a, a'), (b, b')) -> ((x, y, a, b), (x, y, a', b')))

instance Semigroupal Op (,) (,) (,) ((,,,,) x y z) where
  combine :: Op ((x, y, z, a, b), (x, y, z, a', b')) (x, y, z, (a, a'), (b, b'))
  combine = Op (\(x, y, z, (a, a'), (b, b')) -> ((x, y, z, a, b), (x, y, z, a', b')))

instance Semigroupal Op (,) (,) (,) ((,,,,,) x y z w) where
  combine :: Op ((x, y, z, w, a, b), (x, y, z, w, a', b')) (x, y, z, w, (a, a'), (b, b'))
  combine = Op (\(x, y, z, w, (a, a'), (b, b')) -> ((x, y, z, w, a, b), (x, y, z, w, a', b')))

instance Semigroupal Op (,) (,) (,) ((,,,,,,) x y z w v) where
  combine :: Op ((x, y, z, w, v, a, b), (x, y, z, w, v, a', b')) (x, y, z, w, v, (a, a'), (b, b'))
  combine = Op (\(x, y, z, w, v, (a, a'), (b, b')) -> ((x, y, z, w, v, a, b), (x, y, z, w, v, a', b')))

-- | 'Arg' is a pair with a key-only 'Eq'. Its split transposes like a tuple.
instance Semigroupal Op (,) (,) (,) Arg where
  combine :: Op (Arg a b, Arg a' b') (Arg (a, a') (b, b'))
  combine = Op (\(Arg (a, a') (b, b')) -> (Arg a b, Arg a' b'))

-- | Phantom in one parameter, so the split projects the other and discards the
-- phantom nesting. 'Const' carries its content in the first parameter, 'Tagged'
-- in the second.
instance Semigroupal Op (,) (,) (,) Const where
  combine :: Op (Const a b, Const a' b') (Const (a, a') (b, b'))
  combine = Op (\(Const (a, a')) -> (Const a, Const a'))

instance Semigroupal Op (,) (,) (,) Tagged where
  combine :: Op (Tagged a b, Tagged a' b') (Tagged (a, a') (b, b'))
  combine = Op (\(Tagged (b, b')) -> (Tagged b, Tagged b'))

-- | Componentwise, at the product split. Each component splits and the results
-- transpose into a pair of products. @Associative@ \/ @Bifunctor@ on the tensors
-- are the 'Semigroupal' superclasses, listed so GHC can use @combine@ here.
instance
  ( Semigroupal Op tC1 tC2 (,) f,
    Semigroupal Op tC1 tC2 (,) g,
    Associative (->) tC1,
    Associative (->) tC2,
    Bifunctor tC1,
    Bifunctor tC2
  ) =>
  Semigroupal Op tC1 tC2 (,) (Product f g)
  where
  combine :: Op (Product f g a b, Product f g a' b') (Product f g (tC1 a a') (tC2 b b'))
  combine = Op $ \(Pair fab gab) ->
    let (fa, fa') = getOp (combine @Op @tC1 @tC2 @(,)) fab
        (ga, ga') = getOp (combine @Op @tC1 @tC2 @(,)) gab
     in (Pair fa ga, Pair fa' ga')

-- | 'Joker' and 'Clown' carry a functor on one parameter and are phantom in the
-- other, so their split delegates to the functor colax split of
-- "Data.Functor.Monoidal". 'Joker' acts on the second parameter, 'Clown' on the
-- first.
instance (F.Semigroupal Op tC2 tD f, Associative (->) tC1, Bifunctor tD) => Semigroupal Op tC1 tC2 tD (Joker f) where
  combine :: Op (tD (Joker f a b) (Joker f a' b')) (Joker f (tC1 a a') (tC2 b b'))
  combine = Op $ \(Joker fbb') -> gbimap Joker Joker (getOp (F.combine @Op @tC2 @tD) fbb')

instance (F.Semigroupal Op tC1 tD f, Associative (->) tC2, Bifunctor tD) => Semigroupal Op tC1 tC2 tD (Clown f) where
  combine :: Op (tD (Clown f a b) (Clown f a' b')) (Clown f (tC1 a a') (tC2 b b'))
  combine = Op $ \(Clown faa') -> gbimap Clown Clown (getOp (F.combine @Op @tC1 @tD) faa')

-- | 'Forget' is a function out of its first parameter, phantom in the second.
-- The split is the co-pairing, inverse to the covariant @Forget@ @combine@
-- (@either@). The generic colax law cannot exercise it (the split consumes a
-- profunctor at structured contravariant-argument types, which the rank-2 @Gen@
-- interface cannot generate input-dependently), so it is covered by ad-hoc tests
-- at fixed 'Either' types in the test suite (both inverse laws, coassociativity,
-- and conaturality).
instance Semigroupal Op Either Either (,) (Forget (f r)) where
  combine :: Op (Forget (f r) x y, Forget (f r) x' y') (Forget (f r) (Either x x') (Either y y'))
  combine = Op (\(Forget h) -> (Forget (h . Left), Forget (h . Right)))

-- NOTE: The function-like bifunctors @('->')@, @'Star' f@, and @'Kleisli' f@ have
-- no lawful colax dual. Their split would have to turn one function out of a
-- combined input into two functions out of the parts, which needs projections or
-- base points that do not exist naturally. Only 'Forget' splits, because its
-- codomain @f r@ does not depend on the (contravariant) argument.

-- | Transparent newtype wrappers delegate to the wrapped bifunctor's split.
instance (Semigroupal Op tC1 tC2 tD p, Associative (->) tC1, Associative (->) tC2, Bifunctor tD) => Semigroupal Op tC1 tC2 tD (Biap p) where
  combine :: Op (tD (Biap p a b) (Biap p a' b')) (Biap p (tC1 a a') (tC2 b b'))
  combine = Op $ \(Biap pv) -> gbimap Biap Biap (getOp (combine @Op @tC1 @tC2 @tD) pv)

instance (Semigroupal Op tC1 tC2 tD p, Associative (->) tC1, Associative (->) tC2, Bifunctor tD) => Semigroupal Op tC1 tC2 tD (WrappedBifunctor p) where
  combine :: Op (tD (WrappedBifunctor p a b) (WrappedBifunctor p a' b')) (WrappedBifunctor p (tC1 a a') (tC2 b b'))
  combine = Op $ \(WrapBifunctor pv) -> gbimap WrapBifunctor WrapBifunctor (getOp (combine @Op @tC1 @tC2 @tD) pv)

-- | 'Flip' swaps the parameters, so its split is the wrapped bifunctor's split at
-- the swapped tensors.
instance (Semigroupal Op tC2 tC1 tD p, Associative (->) tC1, Associative (->) tC2, Bifunctor tD) => Semigroupal Op tC1 tC2 tD (Flip p) where
  combine :: Op (tD (Flip p a b) (Flip p a' b')) (Flip p (tC1 a a') (tC2 b b'))
  combine = Op $ \(Flip pv) -> gbimap Flip Flip (getOp (combine @Op @tC2 @tC1 @tD) pv)

-- | 'Tannen' wraps a bifunctor in a functor. Map the bifunctor split under the
-- functor, then use the functor colax split to pull the tensor out.
instance (F.Semigroupal Op tD tD f, Semigroupal Op tC1 tC2 tD p, Functor f, Associative (->) tC1, Associative (->) tC2, Bifunctor tD) => Semigroupal Op tC1 tC2 tD (Tannen f p) where
  combine :: Op (tD (Tannen f p a b) (Tannen f p a' b')) (Tannen f p (tC1 a a') (tC2 b b'))
  combine = Op $ \(Tannen fp) -> gbimap Tannen Tannen (getOp (F.combine @Op @tD @tD) (fmap (getOp (combine @Op @tC1 @tC2 @tD)) fp))

-- | 'Biff' applies a functor to each parameter before the bifunctor. Each
-- parameter's functor splits into the codomain tensor @tD@, then the bifunctor
-- splits at @Op tD tD tD@. Fixing the intermediate tensors to @tD@ keeps them
-- determined by the instance head.
instance
  ( F.Semigroupal Op tC1 tD f,
    F.Semigroupal Op tC2 tD g,
    Semigroupal Op tD tD tD p,
    Functor f,
    Functor g,
    Bifunctor p,
    Associative (->) tC1,
    Associative (->) tC2,
    Bifunctor tD
  ) =>
  Semigroupal Op tC1 tC2 tD (Biff p f g)
  where
  combine :: Op (tD (Biff p f g a b) (Biff p f g a' b')) (Biff p f g (tC1 a a') (tC2 b b'))
  combine = Op $ \(Biff pv) ->
    gbimap Biff Biff (getOp (combine @Op @tD @tD @tD) (bimap (getOp (F.combine @Op @tC1 @tD)) (getOp (F.combine @Op @tC2 @tD)) pv))

deriving via FromBiapplicative ((,,) x) instance (Monoid x) => Semigroupal (->) (,) (,) (,) ((,,) x)

deriving via FromBiapplicative ((,,,) x y) instance (Monoid x, Monoid y) => Semigroupal (->) (,) (,) (,) ((,,,) x y)

deriving via FromBiapplicative ((,,,,) x y z) instance (Monoid x, Monoid y, Monoid z) => Semigroupal (->) (,) (,) (,) ((,,,,) x y z)

deriving via FromBiapplicative ((,,,,,) x y z w) instance (Monoid x, Monoid y, Monoid z, Monoid w) => Semigroupal (->) (,) (,) (,) ((,,,,,) x y z w)

deriving via FromBiapplicative ((,,,,,,) x y z w v) instance (Monoid x, Monoid y, Monoid z, Monoid w, Monoid v) => Semigroupal (->) (,) (,) (,) ((,,,,,,) x y z w v)

deriving via FromBiapplicative Arg instance Semigroupal (->) (,) (,) (,) Arg

deriving via FromBiapplicative Const instance Semigroupal (->) (,) (,) (,) Const

deriving via FromBiapplicative (Biap p) instance (Biapplicative p) => Semigroupal (->) (,) (,) (,) (Biap p)

deriving via FromBiapplicative Tagged instance Semigroupal (->) (,) (,) (,) Tagged

deriving via FromBiapplicative (Clown f) instance (Applicative f) => Semigroupal (->) (,) (,) (,) (Clown f)

deriving via FromBiapplicative (Flip p) instance (Biapplicative p) => Semigroupal (->) (,) (,) (,) (Flip p)

deriving via FromBiapplicative (Joker g) instance (Applicative g) => Semigroupal (->) (,) (,) (,) (Joker g)

deriving via FromBiapplicative (WrappedBifunctor p) instance (Biapplicative p) => Semigroupal (->) (,) (,) (,) (WrappedBifunctor p)

-- | A single variance-agnostic 'Product' instance. @combine@ is componentwise,
-- delegating to each component's own 'Semigroupal'. It subsumes the former
-- @FromBiapplicative@ instance and covers profunctor components and the
-- 'Either' \/ 'These' tensors.
instance (Semigroupal (->) tC1 tC2 (,) f, Semigroupal (->) tC1 tC2 (,) g) => Semigroupal (->) tC1 tC2 (,) (Product f g) where
  combine :: (Product f g x y, Product f g x' y') -> Product f g (tC1 x x') (tC2 y y')
  combine (Pair fxy gxy, Pair fxy' gxy') =
    Pair (combine @(->) @tC1 @tC2 @(,) (fxy, fxy')) (combine @(->) @tC1 @tC2 @(,) (gxy, gxy'))

deriving via FromBiapplicative (Tannen f p) instance (Applicative f, Biapplicative p) => Semigroupal (->) (,) (,) (,) (Tannen f p)

deriving via FromBiapplicative (Biff p f g) instance (Applicative f, Applicative g, Biapplicative p) => Semigroupal (->) (,) (,) (,) (Biff p f g)

instance (Profunctor p) => Semigroupal (->) (,) Either Either p where
  combine :: Either (p x y) (p x' y') -> p (x, x') (Either y y')
  combine = either (dimap fst Left) (dimap snd Right)

-- NOTE: This version could be used for a more general abstraction
-- of products in a category:
-- combine =
--   let fwd' = fwd assoc
--       bwd' = bwd assoc
--   in second swap . swap . fwd' . swap . first (bwd' . first swap) . fwd'

instance Semigroupal (->) Either Either Either (,) where
  combine :: Either (x, y) (x', y') -> (Either x x', Either y y')
  combine = either (bimap Left Left) (bimap Right Right)

instance Semigroupal (->) Either Either Either Either where
  combine :: Either (Either x y) (Either x' y') -> Either (Either x x') (Either y y')
  combine = either (bimap Left Left) (bimap Right Right)

instance Semigroupal (->) Either (,) (,) Either where
  combine :: (Either x y, Either x' y') -> Either (Either x x') (y, y')
  combine = \case
    (Left x, Left _) -> Left $ Left x
    (Left x, Right _) -> Left $ Left x
    (Right _, Left x') -> Left $ Right x'
    (Right y, Right y') -> Right (y, y')

instance Semigroupal (->) These (,) (,) Either where
  combine :: (Either x y, Either x' y') -> Either (These x x') (y, y')
  combine = \case
    (Left x, Left x') -> Left $ These x x'
    (Left x, Right _) -> Left $ This x
    (Right _, Left x') -> Left $ That x'
    (Right y, Right y') -> Right (y, y')

instance Semigroupal (->) (,) (,) (,) (->) where
  combine :: (x -> y, x' -> y') -> (x, x') -> (y, y')
  combine = uncurry bimap

instance Semigroupal (->) Either Either (,) (->) where
  combine :: (x -> y, x' -> y') -> Either x x' -> Either y y'
  combine fs = either (Left . fst fs) (Right . snd fs)

instance (Alternative f) => Semigroupal (->) Either Either (,) (Joker f) where
  combine :: (Joker f x y, Joker f x' y') -> Joker f (Either x x') (Either y y')
  combine = uncurry $ biliftA2 (\_ x' -> Right x') (\_ y' -> Right y')

instance (Functor f) => Semigroupal (->) Either Either Either (Joker f) where
  combine :: Either (Joker f x y) (Joker f x' y') -> Joker f (Either x x') (Either y y')
  combine = either (Joker . fmap Left . runJoker) (Joker . fmap Right . runJoker)

instance (Alternative f) => Semigroupal (->) Either Either (,) (Clown f) where
  combine :: (Clown f x y, Clown f x' y') -> Clown f (Either x x') (Either y y')
  combine = uncurry $ biliftA2 (\_ x' -> Right x') (\_ y' -> Right y')

instance (Applicative f) => Semigroupal (->) (,) (,) (,) (Star f) where
  combine :: (Star f x y, Star f x' y') -> Star f (x, x') (y, y')
  combine (Star fxy, Star fxy') = Star $ \(x, x') -> liftA2 (,) (fxy x) (fxy' x')

instance (Functor f) => Semigroupal (->) Either Either (,) (Star f) where
  combine :: (Star f x y, Star f x' y') -> Star f (Either x x') (Either y y')
  combine (Star fxy, Star fxy') = Star $ either (fmap Left . fxy) (fmap Right . fxy')

instance (Applicative f) => Semigroupal (->) These These (,) (Star f) where
  combine :: (Star f x y, Star f x' y') -> Star f (These x x') (These y y')
  combine (Star fxy, Star fxy') = Star $ these (fmap This . fxy) (fmap That . fxy') (\x x' -> liftA2 These (fxy x) (fxy' x'))

instance (Alternative f) => Semigroupal (->) These These These (Star f) where
  combine :: (Applicative f) => These (Star f x y) (Star f x' y') -> Star f (These x x') (These y y')
  combine = \case
    This (Star fxy) -> Star $ these (fmap This . fxy) (const empty) (\x _ -> This <$> fxy x)
    That (Star fxy') -> Star $ these (const empty) (fmap That . fxy') (\_ x' -> That <$> fxy' x')
    These (Star fxy) (Star fxy') -> Star $ these (fmap This . fxy) (fmap That . fxy') (\x x' -> liftA2 These (fxy x) (fxy' x'))

instance (Alternative f) => Semigroupal (->) Either Either Either (Star f) where
  combine :: Either (Star f x y) (Star f x' y') -> Star f (Either x x') (Either y y')
  combine = \case
    Left (Star fxy) -> Star $ either (fmap Left . fxy) (const empty)
    Right (Star fxy') -> Star $ either (const empty) (fmap Right . fxy')

instance (Alternative f) => Semigroupal (->) (,) Either (,) (Star f) where
  combine :: (Star f x y, Star f x' y') -> Star f (x, x') (Either y y')
  combine (Star f, Star g) = Star $ \(x, x') -> (Left <$> f x) <|> (Right <$> g x')

instance (Applicative f) => Semigroupal (->) (,) (,) (,) (Kleisli f) where
  combine :: (Kleisli f x y, Kleisli f x' y') -> Kleisli f (x, x') (y, y')
  combine (Kleisli fxy, Kleisli fxy') = Kleisli $ \(x, x') -> liftA2 (,) (fxy x) (fxy' x')

instance (Functor f) => Semigroupal (->) Either Either (,) (Kleisli f) where
  combine :: (Kleisli f x y, Kleisli f x' y') -> Kleisli f (Either x x') (Either y y')
  combine (Kleisli fxy, Kleisli fxy') = Kleisli $ either (fmap Left . fxy) (fmap Right . fxy')

instance (Applicative f) => Semigroupal (->) These These (,) (Kleisli f) where
  combine :: (Kleisli f x y, Kleisli f x' y') -> Kleisli f (These x x') (These y y')
  combine (Kleisli fxy, Kleisli fxy') = Kleisli $ these (fmap This . fxy) (fmap That . fxy') (\x x' -> liftA2 These (fxy x) (fxy' x'))

instance (Alternative f) => Semigroupal (->) These These These (Kleisli f) where
  combine :: (Applicative f) => These (Kleisli f x y) (Kleisli f x' y') -> Kleisli f (These x x') (These y y')
  combine = \case
    This (Kleisli fxy) -> Kleisli $ these (fmap This . fxy) (const empty) (\x _ -> This <$> fxy x)
    That (Kleisli fxy') -> Kleisli $ these (const empty) (fmap That . fxy') (\_ x' -> That <$> fxy' x')
    These (Kleisli fxy) (Kleisli fxy') -> Kleisli $ these (fmap This . fxy) (fmap That . fxy') (\x x' -> liftA2 These (fxy x) (fxy' x'))

instance (Alternative f) => Semigroupal (->) Either Either Either (Kleisli f) where
  combine :: Either (Kleisli f x y) (Kleisli f x' y') -> Kleisli f (Either x x') (Either y y')
  combine = \case
    Left (Kleisli fxy) -> Kleisli $ either (fmap Left . fxy) (const empty)
    Right (Kleisli fxy') -> Kleisli $ either (const empty) (fmap Right . fxy')

instance (Alternative f) => Semigroupal (->) (,) Either (,) (Kleisli f) where
  combine :: (Kleisli f x y, Kleisli f x' y') -> Kleisli f (x, x') (Either y y')
  combine (Kleisli f, Kleisli g) = Kleisli $ \(x, x') -> (Left <$> f x) <|> (Right <$> g x')

instance (Alternative f) => Semigroupal (->) (,) (,) (,) (Forget (f r)) where
  combine :: (Forget (f r) x y, Forget (f r) x' y') -> Forget (f r) (x, x') (y, y')
  combine (Forget f, Forget g) = Forget $ \(x, x') -> f x <|> g x'

instance Semigroupal (->) Either Either (,) (Forget (f r)) where
  combine :: (Forget (f r) x y, Forget (f r) x' y') -> Forget (f r) (Either x x') (Either y y')
  combine (Forget f, Forget g) = Forget $ either f g

instance (Alternative f) => Semigroupal (->) Either Either Either (Forget (f r)) where
  combine :: Either (Forget (f r) x y) (Forget (f r) x' y') -> Forget (f r) (Either x x') (Either y y')
  combine = \case
    Left (Forget f) -> Forget $ either f (const empty)
    Right (Forget g) -> Forget $ either (const empty) g

instance (Alternative f) => Semigroupal (->) (,) Either (,) (Forget (f r)) where
  combine :: (Forget (f r) x y, Forget (f r) x' y') -> Forget (f r) (x, x') (Either y y')
  combine (Forget f, Forget g) = Forget $ \(x, x') -> f x <|> g x'

infixr 9 |??|

(|??|) :: (Semigroupal (->) tC1 tC2 (,) p) => p a b -> p a' b' -> p (a `tC1` a') (b `tC2` b')
(|??|) = curry combine

infixr 9 |**|

(|**|) :: (Semigroupal (->) (,) (,) (,) p) => p a b -> p a' b' -> p (a, a') (b, b')
(|**|) = curry combine

infixr 9 |*+|

(|*+|) :: (Semigroupal (->) (,) Either (,) p) => p a b -> p a' b' -> p (a, a') (Either b b')
(|*+|) = curry combine

infixr 9 |*&|

(|*&|) :: (Semigroupal (->) (,) These (,) p) => p a b -> p a' b' -> p (a, a') (These b b')
(|*&|) = curry combine

infixr 9 |++|

(|++|) :: (Semigroupal (->) Either Either (,) p) => p a b -> p a' b' -> p (Either a a') (Either b b')
(|++|) = curry combine

infixr 9 |+*|

(|+*|) :: (Semigroupal (->) Either (,) (,) p) => p a b -> p a' b' -> p (Either a a') (b, b')
(|+*|) = curry combine

infixr 9 |+&|

(|+&|) :: (Semigroupal (->) These (,) (,) p) => p a b -> p a' b' -> p (These a a') (b, b')
(|+&|) = curry combine

infixr 9 |&&|

(|&&|) :: (Semigroupal (->) These These (,) p) => p a b -> p a' b' -> p (These a a') (These b b')
(|&&|) = curry combine

infixr 9 |&*|

(|&*|) :: (Semigroupal (->) These (,) (,) p) => p a b -> p a' b' -> p (These a a') (b, b')
(|&*|) = curry combine

infixr 9 |&+|

(|&+|) :: (Semigroupal (->) These Either (,) p) => p a b -> p a' b' -> p (These a a') (Either b b')
(|&+|) = curry combine

--------------------------------------------------------------------------------

-- | Given monoidal categories \((\mathcal{C}, \otimes, I_{\mathcal{C}})\) and \((\mathcal{D}, \bullet, I_{\mathcal{D}})\).
-- A bifunctor \(F : \mathcal{C_1} \times \mathcal{C_2} \to \mathcal{D}\) is 'Unital' if it supports a morphism
-- \(\phi : I_{\mathcal{D}} \to F\ I_{\mathcal{C_1}}\ I_{\mathcal{C_2}}\), which we call 'introduce'.
class Unital cat iC1 iC2 iD f where
  -- | @introduce@ maps from the identity in \(\mathcal{C_1} \times \mathcal{C_2}\) to the
  -- identity in \(\mathcal{D}\).
  --
  -- ==== __Examples__
  --
  -- >>> introduce @(->) @() @() @() @(,) ()
  -- ((),())
  --
  -- >>> introduce @(->) @Void @() @() @Either ()
  -- Right ()
  introduce :: cat iD (f iC1 iC2)

instance (Biapplicative p) => Unital (->) () () () (FromBiapplicative p) where
  introduce :: () -> FromBiapplicative p () ()
  introduce () = bipure () ()

deriving via FromBiapplicative (,) instance Unital (->) () () () (,)

deriving via FromBiapplicative ((,,) x) instance (Monoid x) => Unital (->) () () () ((,,) x)

deriving via FromBiapplicative ((,,,) x y) instance (Monoid x, Monoid y) => Unital (->) () () () ((,,,) x y)

deriving via FromBiapplicative ((,,,,) x y z) instance (Monoid x, Monoid y, Monoid z) => Unital (->) () () () ((,,,,) x y z)

deriving via FromBiapplicative ((,,,,,) x y z w) instance (Monoid x, Monoid y, Monoid z, Monoid w) => Unital (->) () () () ((,,,,,) x y z w)

deriving via FromBiapplicative ((,,,,,,) x y z w v) instance (Monoid x, Monoid y, Monoid z, Monoid w, Monoid v) => Unital (->) () () () ((,,,,,,) x y z w v)

deriving via FromBiapplicative Arg instance Unital (->) () () () Arg

deriving via FromBiapplicative Const instance Unital (->) () () () Const

deriving via FromBiapplicative (Biap p) instance (Biapplicative p) => Unital (->) () () () (Biap p)

deriving via FromBiapplicative Tagged instance Unital (->) () () () Tagged

deriving via FromBiapplicative (Clown f) instance (Applicative f) => Unital (->) () () () (Clown f)

deriving via FromBiapplicative (Flip p) instance (Biapplicative p) => Unital (->) () () () (Flip p)

deriving via FromBiapplicative (Joker g) instance (Applicative g) => Unital (->) () () () (Joker g)

deriving via FromBiapplicative (WrappedBifunctor p) instance (Biapplicative p) => Unital (->) () () () (WrappedBifunctor p)

-- | Componentwise 'introduce', delegating to each component's 'Unital'. Covers
-- every unit pair that both components support, covariant and profunctor alike.
instance (Unital (->) iC1 iC2 () f, Unital (->) iC1 iC2 () g) => Unital (->) iC1 iC2 () (Product f g) where
  introduce :: () -> Product f g iC1 iC2
  introduce () = Pair (introduce @(->) @iC1 @iC2 @() ()) (introduce @(->) @iC1 @iC2 @() ())

deriving via FromBiapplicative (Tannen f p) instance (Applicative f, Biapplicative p) => Unital (->) () () () (Tannen f p)

deriving via FromBiapplicative (Biff p f g) instance (Applicative f, Applicative g, Biapplicative p) => Unital (->) () () () (Biff p f g)

instance (Profunctor p, Category p) => Unital (->) () () () (StrongCategory p) where
  introduce :: () -> StrongCategory p () ()
  introduce () = StrongCategory id

instance Unital (->) Void Void Void (,) where
  introduce :: Void -> (Void, Void)
  introduce = spawn

instance Unital (->) Void Void Void Either where
  introduce :: Void -> Either Void Void
  introduce = bwd unitr

instance Unital (->) Void () () Either where
  introduce :: () -> Either Void ()
  introduce = Right

instance Unital (->) () () () (->) where
  introduce :: () -> () -> ()
  introduce () () = ()

instance Unital (->) Void Void Void (->) where
  introduce :: Void -> Void -> Void
  introduce = absurd

instance (Unital (->) Void Void () (->)) where
  introduce :: () -> Void -> Void
  introduce () = absurd

instance (Alternative f) => Unital (->) Void Void () (Joker f) where
  introduce :: () -> Joker f Void Void
  introduce () = Joker empty

instance Unital (->) Void Void Void (Joker f) where
  introduce :: Void -> Joker f Void Void
  introduce = absurd

instance (Applicative f) => Unital (->) () () () (Star f) where
  introduce :: () -> Star f () ()
  introduce () = Star pure

instance Unital (->) Void Void () (Star f) where
  introduce :: () -> Star f Void Void
  introduce () = Star absurd

instance (Alternative f) => Unital (->) Void Void Void (Star f) where
  introduce :: Void -> Star f Void Void
  introduce = absurd

instance (Alternative f) => Unital (->) () Void () (Star f) where
  introduce :: () -> Star f () Void
  introduce () = Star $ const empty

instance (Applicative f) => Unital (->) () () () (Kleisli f) where
  introduce :: () -> Kleisli f () ()
  introduce () = Kleisli pure

instance Unital (->) Void Void () (Kleisli f) where
  introduce :: () -> Kleisli f Void Void
  introduce () = Kleisli absurd

instance (Alternative f) => Unital (->) Void Void Void (Kleisli f) where
  introduce :: Void -> Kleisli f Void Void
  introduce = absurd

instance (Alternative f) => Unital (->) () Void () (Kleisli f) where
  introduce :: () -> Kleisli f () Void
  introduce () = Kleisli $ const empty

--------------------------------------------------------------------------------
-- Monoidal

-- | Given monoidal categories \((\mathcal{C}, \otimes, I_{\mathcal{C}})\) and \((\mathcal{D}, \bullet, I_{\mathcal{D}})\).
-- A bifunctor \(F : \mathcal{C_1} \times \mathcal{C_2} \to \mathcal{D}\) is 'Monoidal' if it maps between \(\mathcal{C_1} \times \mathcal{C_2}\)
-- and \(\mathcal{D}\) while preserving their monoidal structure. Eg., a homomorphism of monoidal categories.
--
-- See <https://ncatlab.org/nlab/show/monoidal+functor NCatlab> for more details.
--
-- === Laws
--
-- __Right Unitality:__
--
-- \[
-- \require{AMScd}
-- \begin{CD}
-- F A B \bullet I_{\mathcal{D}}   @>{1 \bullet \phi}>>                                     F A B \bullet F I_{\mathcal{C_{1}}} I_{\mathcal{C_{2}}}\\
-- @VV{\rho_{\mathcal{D}}}V                                                                 @VV{\phi AB,I_{\mathcal{C_{1}}}I_{\mathcal{C_{2}}}}V \\
-- F A B                           @<<{F \rho_{\mathcal{C_{1}}} \rho_{\mathcal{C_{2}}}}<    F (A \otimes I_{\mathcal{C_{1}}}) (B \otimes I_{\mathcal{C_{2}}})
-- \end{CD}
-- \]
--
-- @
-- 'combine' 'Control.Category..' 'Control.Category.Tensor.grmap' 'introduce' ≡ 'bwd' 'unitr' 'Control.Category..' 'fwd' 'unitr'
-- @
--
-- __ Left Unitality__:
--
-- \[
-- \begin{CD}
-- I_{\mathcal{D}} \bullet F A B   @>{\phi \bullet 1}>>                                          F I_{\mathcal{C_{1}}} I_{\mathcal{C_{2}}} \bullet F A B\\
-- @VV{\lambda_{\mathcal{D}}}V                                                                   @VV{I_{\mathcal{C_{1}}}I_{\mathcal{C_{2}}},\phi AB}V \\
-- F A B                           @<<{F \lambda_{\mathcal{C_{1}}} \lambda_{\mathcal{C_{2}}}}<   F (I_{\mathcal{C_{1}}} \otimes A) (I_{\mathcal{C_{2}}} \otimes B)
-- \end{CD}
-- \]
--
-- @
-- 'combine' 'Control.Category..' 'Control.Category.Tensor.glmap' 'introduce' ≡ 'fmap' ('bwd' 'unitl') 'Control.Category..' 'fwd' 'unitl'
-- @
class
  ( Tensor cat tC1 iC1,
    Tensor cat tC2 iC2,
    Tensor cat tD iD,
    Semigroupal cat tC1 tC2 tD f,
    Unital cat iC1 iC2 iD f
  ) =>
  Monoidal cat tC1 iC1 tC2 iC2 tD iD f

instance Monoidal (->) (,) () (,) () (,) () (,)

instance (Monoid x) => Monoidal (->) (,) () (,) () (,) () ((,,) x)

instance (Monoid x, Monoid y) => Monoidal (->) (,) () (,) () (,) () ((,,,) x y)

instance (Monoid x, Monoid y, Monoid z) => Monoidal (->) (,) () (,) () (,) () ((,,,,) x y z)

instance (Monoid x, Monoid y, Monoid z, Monoid w) => Monoidal (->) (,) () (,) () (,) () ((,,,,,) x y z w)

instance (Monoid x, Monoid y, Monoid z, Monoid w, Monoid v) => Monoidal (->) (,) () (,) () (,) () ((,,,,,,) x y z w v)

instance Monoidal (->) (,) () (,) () (,) () Arg

instance Monoidal (->) (,) () (,) () (,) () Const

instance (Biapplicative p) => Monoidal (->) (,) () (,) () (,) () (Biap p)

instance Monoidal (->) (,) () (,) () (,) () Tagged

instance (Applicative f) => Monoidal (->) (,) () (,) () (,) () (Clown f)

instance (Biapplicative p) => Monoidal (->) (,) () (,) () (,) () (Flip p)

instance (Applicative g) => Monoidal (->) (,) () (,) () (,) () (Joker g)

instance (Biapplicative p) => Monoidal (->) (,) () (,) () (,) () (WrappedBifunctor p)

instance (Semigroupal (->) tC1 tC2 (,) f, Semigroupal (->) tC1 tC2 (,) g, Unital (->) iC1 iC2 () f, Unital (->) iC1 iC2 () g, Tensor (->) tC1 iC1, Tensor (->) tC2 iC2) => Monoidal (->) tC1 iC1 tC2 iC2 (,) () (Product f g)

instance (Applicative f, Biapplicative p) => Monoidal (->) (,) () (,) () (,) () (Tannen f p)

instance (Applicative f, Applicative g, Biapplicative p) => Monoidal (->) (,) () (,) () (,) () (Biff p f g)

instance (Strong p, Semigroupoid p, Category p) => Monoidal (->) (,) () (,) () (,) () (StrongCategory p)

instance Monoidal (->) Either Void Either Void Either Void (,)

instance Monoidal (->) Either Void Either Void Either Void Either

instance Monoidal (->) Either Void (,) () (,) () Either

instance Monoidal (->) These Void (,) () (,) () Either

instance Monoidal (->) (,) () (,) () (,) () (->)

instance Monoidal (->) Either Void Either Void (,) () (->)

instance (Alternative f) => Monoidal (->) Either Void Either Void (,) () (Joker f)

instance (Functor f) => Monoidal (->) Either Void Either Void Either Void (Joker f)

instance (Applicative f) => Monoidal (->) (,) () (,) () (,) () (Star f)

instance (Functor f) => Monoidal (->) Either Void Either Void (,) () (Star f)

instance (Applicative f) => Monoidal (->) These Void These Void (,) () (Star f)

instance (Alternative f) => Monoidal (->) Either Void Either Void Either Void (Star f)

instance (Alternative f) => Monoidal (->) These Void These Void These Void (Star f)

instance (Alternative f) => Monoidal (->) (,) () Either Void (,) () (Star f)

instance (Applicative f) => Monoidal (->) (,) () (,) () (,) () (Kleisli f)

instance (Functor f) => Monoidal (->) Either Void Either Void (,) () (Kleisli f)

instance (Applicative f) => Monoidal (->) These Void These Void (,) () (Kleisli f)

instance (Alternative f) => Monoidal (->) Either Void Either Void Either Void (Kleisli f)

instance (Alternative f) => Monoidal (->) These Void These Void These Void (Kleisli f)

instance (Alternative f) => Monoidal (->) (,) () Either Void (,) () (Kleisli f)

newtype StrongCategory p a b = StrongCategory (p a b)
  deriving (Functor, Applicative, Monad, Profunctor, Category)

instance (Semigroupoid p) => Semigroupoid (StrongCategory p) where
  o :: StrongCategory p b c -> StrongCategory p a b -> StrongCategory p a c
  o (StrongCategory f) (StrongCategory g) = StrongCategory (f `o` g)

instance (Strong p, Semigroupoid p) => Semigroupal (->) (,) (,) (,) (StrongCategory p) where
  combine :: (StrongCategory p x y, StrongCategory p x' y') -> StrongCategory p (x, x') (y, y')
  combine (StrongCategory pxy, StrongCategory pxy') = StrongCategory $ first' pxy `o` second' pxy'