{-# LANGUAGE
    CPP,
    DataKinds,
    PolyKinds,
    TypeFamilies,
    TypeOperators,
    UndecidableInstances #-}

-- | Semigroups and monoids.
module Fcf.Class.Monoid
  ( -- * Pure type families
    -- | Nicer to use when applied explicitly.
    type (<>)
  , MEmpty

    -- * First-class families
    -- | Can be composed and passed to higher-order functions.
  , type (.<>)
  , MEmpty_
  ) where

import Fcf.Core (Exp, Eval)
import Data.Monoid (All(..), Any(..))
import Data.Type.Bool (type (&&), type (||))

#if __GLASGOW_HASKELL__ >= 802
import GHC.TypeLits (AppendSymbol)
#endif

-- $setup
-- >>> import GHC.TypeLits (Nat)
-- >>> import Numeric.Natural (Natural)

-- | Type-level semigroup composition @('Data.Semigroup.<>')@.
--
-- This is the fcf-encoding of @('<>')@.
-- To define a new semigroup, add type instances to @('<>')@.
data (.<>) :: a -> a -> Exp a
type instance Eval (x .<> y) = x <> y

-- | Type-level semigroup composition @('Data.Semigroup.<>')@.
type family (<>) (x :: a) (y :: a) :: a

-- (,)
type instance (<>) '(a1, a2) '(b1, b2) = '(a1 <> b1, a2 <> b2)

-- (,,)
type instance (<>) '(a1, a2, a3) '(b1, b2, b3) = '(a1 <> b1, a2 <> b2, a3 <> b3)

-- List
type instance (<>) '[] ys = ys
type instance (<>) (x ': xs) ys = x ': (<>) xs ys

-- Maybe
type instance (<>) 'Nothing b = b
type instance (<>) a 'Nothing = a
type instance (<>) ('Just a) ('Just b) = 'Just (a <> b)

-- Ordering
type instance (<>) 'EQ b = b
type instance (<>) a 'EQ = a
type instance (<>) 'LT _b = 'LT
type instance (<>) 'GT _b = 'GT

-- ()
type instance (<>) _a _b = '()

-- All
type instance (<>) ('All a) ('All b) = 'All (a && b)

-- Any
type instance (<>) ('Any a) ('Any b) = 'Any (a || b)

#if __GLASGOW_HASKELL__ >= 802
-- Symbol
-- | With /base >= 4.10.0.0/.
type instance (<>) x y = AppendSymbol x y
#endif

-- | Type-level monoid identity 'Data.Monoid.mempty'.
--
-- This is the fcf-encoding of 'MEmpty'.
data MEmpty_ :: Exp a
type instance Eval MEmpty_ = MEmpty

-- | Type-level monoid identity 'Data.Monoid.mempty'.
--
-- === __Examples__
--
-- >>> :kind! LT <> MEmpty
-- LT <> MEmpty :: Ordering
-- = LT
--
-- >>> :kind! MEmpty <> '(EQ, [1, 2])
-- MEmpty <> '(EQ, [1, 2]) :: (Ordering, [Natural])
-- = '(EQ, [1, 2])
--
-- >>> :kind! '(GT, Just '()) <> MEmpty
-- '(GT, Just '()) <> MEmpty :: (Ordering, Maybe ())
-- = '(GT, Just '())
type family MEmpty :: a

-- (,)
type instance MEmpty = '(MEmpty, MEmpty)

-- (,,)
type instance MEmpty = '(MEmpty, MEmpty, MEmpty)

-- List
type instance MEmpty = '[]

-- Maybe
type instance MEmpty = 'Nothing

-- Ordering
type instance MEmpty = 'EQ

-- ()
type instance MEmpty = '()

-- All
type instance MEmpty = 'All 'True

-- Any
type instance MEmpty = 'Any 'False

-- Symbol
type instance MEmpty = ""
