{-# LANGUAGE CPP #-}
-- | This module provides some convenient functions for dealing with Booleans.
-- 
-- The most important one being 'bool', a function that can be used in place of
-- the build-in @if then else@-syntax.
module Data.Bool.Extras
  (
  -- * Main function
    bool

  -- * Other functions
  , mwhen
  , mwhenM
  , whenA
  , whenC
  , whenM

  -- * Morphisms
  , BoolAlgebra
  , cata
  , ana
  ) where

import Control.Arrow
import Control.Category (Category)
import qualified Control.Category as Cat
import Control.Monad
import Data.Bool
import Data.Monoid

#if !MIN_VERSION_base(4,7,0)
-- | Defines the fold over a boolean value.
--
-- Returns its first argument when applied to `False',
-- returns its second argument when applied to `True'.
-- 
-- Comparable to the `maybe' or `either' functions for their respective data
-- types.
{-# INLINE bool #-}
bool :: a -> a -> Bool -> a
bool x _ False = x
bool _ y True  = y
-- Expressed in terms of `cata':
-- bool = curry cata
#endif

-- | Boolean operation for monoids.
-- 
-- Returns its first argument when applied to `True',
-- returns `mempty' when applied to `False'.
mwhen :: (Monoid a) => a -> Bool -> a
mwhen = bool mempty

-- | Boolean operation for monads, with a monoid default.
-- 
-- Return its first argument when applied to `True',
-- returns `return mempty' when applied to `False'.
mwhenM :: (Monad m, Monoid a) => m a -> Bool -> m a
mwhenM = bool (return mempty)

-- | Boolean operation for arrows.
-- 
-- Returns its first argument when applied to `True',
-- returns `returnA' when applied to `False'.
whenA :: (Arrow a) => a b b -> Bool -> a b b
whenA = bool returnA

-- | Boolean operation for categories.
-- 
-- Returns its first argument when applied to `True',
-- returns @Control.Category.@`Cat.id' when applied to `False'.
whenC :: (Category cat) => cat a a -> Bool -> cat a a
whenC = bool Cat.id

-- | Boolean operation for monads.
-- 
-- Returns its first argument when applied to `True',
-- returns `return' when applied to `False'.
--
-- @Control.Monad.@`when' can be expressed in terms of `whenM', like so:
-- 
-- > when :: Monad m => Bool -> m () -> m ()
-- > when b m = (const m `whenM` b) ()
whenM :: (Monad m) => (a -> m a) -> Bool -> (a -> m a)
whenM = bool return
-- Alternative implementation using Kleisli arrows:
-- whenM m = runKleisli . whenC (Kleisli m)

{-
-- Functions that are also possible, but we haven't found an explicit need for

whenP :: (MonadPlus m) => a -> Bool -> m a
whenP = bool mzero . return

(<?>) :: (Applicative f) => (a -> f a) -> Bool -> (a -> f a)
(<?>) = bool pure
-}


-- | Algebra for Bool data type.
-- 
-- The first field of the pair represents the `False' value,
-- the second field represents the `True' value.
type BoolAlgebra r = (r, r)

-- | Catamorphism for booleans.
cata :: BoolAlgebra r -> Bool -> r
cata (x, _) False = x
cata (_, y) True  = y

-- | Anamorphism for booleans.
ana :: (b -> Bool) -> b -> Bool
ana f b = f b