{-# LANGUAGE
      DeriveFunctor,
      TupleSections
  #-}

{-|
Module      : Data.Monoid.Zero
Copyright   : (c) 2015 Maciej PirĂ³g
License     : MIT
Maintainer  : maciej.adam.pirog@gmail.com
Stability   : experimental

Typeclasses for monoids with (left, right, or \"two-sided\") zero
elements (aka absorbing elements) and their free implementations.
Just like monoids are related to backtracking computations, right
zero monoids are related to backtracking computation with Prolog's
cut operator (see the module "Data.List.Cut").
-}
module Data.Monoid.Zero
  (
    -- * Zero monoids
    MonoidZero(..),
    AdjoinZero(..),
    -- * Left zero monoids
    MonoidLZero(..),
    AdjoinLZero(..),
    -- * Right zero monoids
    MonoidRZero(..),
    AdjoinRZero(..),
  )
  where

import Control.Applicative (pure, (<$>))
import Data.Foldable (Foldable (..))
import Data.Traversable (Traversable (..))
import Data.Monoid (Monoid(..), Product(..), All(..), Any(..))

--
-- MONOID ZERO
--

-- | Class of zero monoids. Instances should satisfy:
--
-- * @'mappend' zero m = zero@
--
-- * @'mappend' m zero = zero@
class (Monoid m) => MonoidZero m where
  zero :: m

-- | A zero monoid freely generated by a monoid.
newtype AdjoinZero m = AdjoinZero { adjoinZero :: Maybe m }
 deriving (Functor, Eq, Ord)

instance (Show m) => Show (AdjoinZero m) where
  show (AdjoinZero (Just m)) = show m
  show (AdjoinZero Nothing)  = "*"

instance Foldable AdjoinZero where
  foldMap f (AdjoinZero (Just m)) = f m
  foldMap f (AdjoinZero Nothing)  = mempty

instance Traversable AdjoinZero where
  traverse f (AdjoinZero (Just a)) = AdjoinZero . Just <$> f a
  traverse f (AdjoinZero Nothing)  = pure $ AdjoinZero Nothing

instance (Monoid m) => Monoid (AdjoinZero m) where
  mempty = AdjoinZero $ Just $ mempty
  AdjoinZero Nothing `mappend` _     = AdjoinZero Nothing
  _ `mappend` AdjoinZero Nothing     = AdjoinZero Nothing
  AdjoinZero (Just m) `mappend` AdjoinZero (Just n) =
    AdjoinZero $ Just (m `mappend` n)

instance (Monoid m) => MonoidZero (AdjoinZero m) where
  zero = AdjoinZero Nothing

instance (Monoid m) => MonoidLZero (AdjoinZero m) where
  lzero = AdjoinZero Nothing

instance (Monoid m) => MonoidRZero (AdjoinZero m) where
  rzero = AdjoinZero Nothing

instance (Num n) => MonoidZero (Product n) where
  zero = Product 0

instance MonoidZero All where
  zero = All False

instance MonoidZero Any where
  zero = Any True

instance MonoidZero () where
  zero = ()

instance (MonoidZero a, MonoidZero b) => MonoidZero (a, b) where
  zero = (zero, zero)

instance (MonoidZero a, MonoidZero b, MonoidZero c) => MonoidZero (a, b, c) where
  zero = (zero, zero, zero)

instance (MonoidZero a, MonoidZero b, MonoidZero c, MonoidZero d) => MonoidZero (a, b, c, d) where
  zero = (zero, zero, zero, zero)

instance (MonoidZero a, MonoidZero b, MonoidZero c, MonoidZero d, MonoidZero e) => MonoidZero (a, b, c, d, e) where
  zero = (zero, zero, zero, zero, zero)

--
-- MONOID LZERO
--

-- | Class of left zero monoids. Instances should satisfy:
--
-- * @'mappend' m lzero = lzero@
class (Monoid m) => MonoidLZero m where
  lzero :: m

-- | A left zero monoid freely generated by a monoid.
newtype AdjoinLZero m = AdjoinLZero { unAdjoinLZero :: (Bool, m) }
 deriving (Functor, Eq, Ord)

instance Foldable AdjoinLZero where
  foldMap f (AdjoinLZero (_, a)) = f a

instance Traversable AdjoinLZero where
  traverse f (AdjoinLZero (b, a)) = AdjoinLZero . (b, ) <$> f a

instance (Show m) => Show (AdjoinLZero m) where
  show (AdjoinLZero (b, m)) = (if b then "*" else "") ++ show m

instance (Monoid m) => Monoid (AdjoinLZero m) where
  mempty = AdjoinLZero (False, mempty)
  _ `mappend` AdjoinLZero (True, n) = AdjoinLZero (True, n)
  AdjoinLZero (b, m) `mappend` AdjoinLZero (False, n) =
      AdjoinLZero (b, m `mappend` n)

instance (Monoid m) => MonoidLZero (AdjoinLZero m) where
  lzero = AdjoinLZero (True, mempty)

instance (Num n) => MonoidLZero (Product n) where
  lzero = Product 0

instance MonoidLZero All where
  lzero = All False

instance MonoidLZero Any where
  lzero = Any True

instance MonoidLZero () where
  lzero = ()

instance (MonoidLZero a, MonoidLZero b) => MonoidLZero (a, b) where
  lzero = (lzero, lzero)

instance (MonoidLZero a, MonoidLZero b, MonoidLZero c) => MonoidLZero (a, b, c) where
  lzero = (lzero, lzero, lzero)

instance (MonoidLZero a, MonoidLZero b, MonoidLZero c, MonoidLZero d) => MonoidLZero (a, b, c, d) where
  lzero = (lzero, lzero, lzero, lzero)

instance (MonoidLZero a, MonoidLZero b, MonoidLZero c, MonoidLZero d, MonoidLZero e) => MonoidLZero (a, b, c, d, e) where
  lzero = (lzero, lzero, lzero, lzero, lzero)

--
-- MONOID RZERO
--

-- | Class of right zero monoids. Instances should satisfy:
--
-- * @'mappend' rzero m = rzero@
class (Monoid m) => MonoidRZero m where
  rzero :: m

-- | A right zero monoid freely generated by a monoid.
newtype AdjoinRZero m = AdjoinRZero { unAdjoinRZero :: (m, Bool) }
 deriving (Functor, Eq, Ord)

instance Foldable AdjoinRZero where
  foldMap f (AdjoinRZero (a, _)) = f a

instance Traversable AdjoinRZero where
  traverse f (AdjoinRZero (a, b)) = AdjoinRZero . (, b) <$> f a

instance (Show m) => Show (AdjoinRZero m) where
  show (AdjoinRZero (m, b)) = show m ++ (if b then "*" else "")

instance (Monoid m) => Monoid (AdjoinRZero m) where
  mempty = AdjoinRZero (mempty, False)
  AdjoinRZero (m, True)  `mappend` _ = AdjoinRZero (m, True)
  AdjoinRZero (m, False) `mappend` AdjoinRZero (n, b) =
    AdjoinRZero (m `mappend` n, b)

instance (Monoid m) => MonoidRZero (AdjoinRZero m) where
  rzero = AdjoinRZero (mempty, True)

instance (Num n) => MonoidRZero (Product n) where
  rzero = Product 0

instance MonoidRZero All where
  rzero = All False

instance MonoidRZero Any where
  rzero = Any True

instance MonoidRZero () where
  rzero = ()

instance (MonoidRZero a, MonoidRZero b) => MonoidRZero (a, b) where
  rzero = (rzero, rzero)

instance (MonoidRZero a, MonoidRZero b, MonoidRZero c) => MonoidRZero (a, b, c) where
  rzero = (rzero, rzero, rzero)

instance (MonoidRZero a, MonoidRZero b, MonoidRZero c, MonoidRZero d) => MonoidRZero (a, b, c, d) where
  rzero = (rzero, rzero, rzero, rzero)

instance (MonoidRZero a, MonoidRZero b, MonoidRZero c, MonoidRZero d, MonoidRZero e) => MonoidRZero (a, b, c, d, e) where
  rzero = (rzero, rzero, rzero, rzero, rzero)