{-# OPTIONS_GHC -Wunused-imports #-}

-- | Boolean algebras and types isomorphic to 'Bool'.
--
-- There are already solutions for 'Boolean' algebras in the Haskell ecosystem,
-- but they do not offer easy instantiations for types isomorphic to 'Bool'.
-- In particular, if type @a@ is isomorphic to 'Bool', so it satisfies `IsBool a`,
-- we would like to instantiate 'Boolean a' by just giving 'true' and 'false'.
-- To facilitate this within the limits of the Haskell class system,
-- we define the class 'Boolean' mutually with class 'IsBool',
-- so that operations 'not', '(&&)', and '(||)' can get default implementations.
--
-- Usage:
-- @
--    import Prelude hiding ( not, (&&), (||) )
--    import Agda.Utils.Boolean
-- @

module Agda.Utils.Boolean where

import Prelude ( Bool(True,False), Eq, ($), (.), const, id )
import qualified Prelude as P

infixr 3 &&
infixr 2 ||

-- | Boolean algebras.
--
class Boolean a where
  fromBool :: Bool -> a

  true :: a
  true = fromBool True

  false :: a
  false = fromBool False

  not :: a -> a

  (&&) :: a -> a -> a

  (||) :: a -> a -> a

  implies :: a -> a -> a
  implies a b = b || not a

  -- | Set difference, dual to 'implies'.
  butNot :: a -> a -> a
  butNot a b = a && not b

  default not :: IsBool a => a -> a
  not = fromBool1 P.not

  default (&&) :: IsBool a => a -> a -> a
  (&&) = fromBool2 (P.&&)

  default (||) :: IsBool a => a -> a -> a
  (||) = fromBool2 (P.||)

-- | Types isomorphic to 'Bool'.
--
class (Boolean a, Eq a) => IsBool a where
  toBool :: a -> Bool

  ifThenElse :: a -> b -> b -> b
  ifThenElse c t e = if toBool c then t else e

  fromBool1 :: (Bool -> Bool) -> (a -> a)
  fromBool1 f = fromBool . f . toBool

  fromBool2 :: (Bool -> Bool -> Bool) -> (a -> a -> a)
  fromBool2 f a b = fromBool $ f (toBool a) (toBool b)

  {-# MINIMAL toBool #-}

instance Boolean Bool where
  fromBool = id

instance IsBool Bool where
  toBool = id
  -- optional
  fromBool1 = id
  fromBool2 = id

instance Boolean b => Boolean (a -> b) where
  fromBool   = const . fromBool
  not f      = not . f
  (f && g) a = f a && g a
  (f || g) a = f a || g a
