{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies,
             UndecidableInstances, ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies, FlexibleContexts, CPP #-}
{-# OPTIONS_GHC -Wall #-}

{-# OPTIONS_GHC -fno-warn-unused-imports #-} -- TEMP
{-# OPTIONS_GHC -fno-warn-unused-binds   #-} -- TEMP

----------------------------------------------------------------------
-- |
-- Module      :  Data.Boolean
-- Copyright   :  (c) Conal Elliott 2009-2012
-- License     :  BSD3
-- 
-- Maintainer  :  conal@conal.net
-- Stability   :  experimental
-- 
-- Some classes for generalized boolean operations.
-- 
-- In this design, for if-then-else, equality and inequality tests, the
-- boolean type depends on the value type.
-- 
-- I also tried using a unary type constructor class.  The class doesn't work
-- for regular booleans, so generality is lost.  Also, we'd probably have
-- to wire class constraints in like: @(==*) :: Eq a => f Bool -> f a -> f
-- a -> f a@, which disallows situations needing additional constraints,
-- e.g., Show.
--
-- Starting with 0.1.0, this package uses type families.
-- Up to version 0.0.2, it used MPTCs with functional dependencies.
-- My thanks to Andy Gill for suggesting & helping with the change.

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

module Data.Boolean
  ( Boolean(..), BooleanOf, IfB(..)
  , boolean, cond, crop
  , EqB(..), OrdB(..)
  , minB, maxB, sort2B
  , guardedB, caseB
  ) where

#if MIN_VERSION_base(4,8,0)
import Prelude hiding ((<*))
#endif
import Data.Monoid (Monoid,mempty)
import Control.Applicative (Applicative(pure),liftA2,liftA3)

{--------------------------------------------------------------------
    Classes
--------------------------------------------------------------------}

infixr 3  &&*
infixr 2  ||*

-- | Generalized boolean class
class Boolean b where
  true, false  :: b
  notB         :: b -> b
  (&&*), (||*) :: b -> b -> b

instance Boolean Bool where
  true  = True
  false = False
  notB  = not
  (&&*) = (&&)
  (||*) = (||)

-- | 'BooleanOf' computed the boolean analog of a specific type.
type family BooleanOf a

-- | Types with conditionals
class Boolean (BooleanOf a) => IfB a where
  ifB  :: (bool ~ BooleanOf a) => bool -> a -> a -> a

-- | Expression-lifted conditional with condition last
boolean :: (IfB a, bool ~ BooleanOf a) => a -> a -> bool -> a
boolean t e bool = ifB bool t e

-- | Point-wise conditional
cond :: (Applicative f, IfB a, bool ~ BooleanOf a) => f bool -> f a -> f a -> f a
cond = liftA3 ifB

-- | Generalized cropping, filling in 'mempty' where the test yields false.
crop :: (Applicative f, Monoid (f a), IfB a, bool ~ BooleanOf a) => f bool -> f a -> f a
crop r f = cond r f mempty

-- | A generalized replacement for guards and chained ifs.
guardedB :: (IfB b, bool ~ BooleanOf b) => bool -> [(bool,b)] -> b -> b
guardedB _ []        e = e
guardedB a ((c,b):l) e = ifB c b (guardedB a l e)

-- | A generalized version of a case like control structure.
caseB :: (IfB b, bool ~ BooleanOf b) => a -> [(a -> bool, b)] -> b -> b
caseB _ []        e = e
caseB x ((p,b):l) e = ifB (p x) b (caseB x l e)

infix  4  ==*, /=*

-- | Types with equality.  Minimum definition: '(==*)'.
class Boolean (BooleanOf a) => EqB a where
  (==*), (/=*) :: (bool ~ BooleanOf a) => a -> a -> bool
  u /=* v = notB (u ==* v)

infix  4  <*, <=*, >=*, >*

-- | Types with inequality.  Minimum definition: '(<*)'.
class Boolean (BooleanOf a) => OrdB a where
  (<*), (<=*), (>*), (>=*) :: (bool ~ BooleanOf a) => a -> a -> bool
  u >*  v = v <* u
  u >=* v = notB (u <* v)
  u <=* v = v >=* u

-- | Variant of 'min' using 'ifB' and '(<=*)'
minB :: (IfB a, OrdB a) => a -> a -> a
u `minB` v = ifB (u <=* v) u v

-- | Variant of 'max' using 'ifB' and '(>=*)'
maxB :: (IfB a, OrdB a) => a -> a -> a
u `maxB` v = ifB (u >=* v) u v

-- | Variant of 'min' and 'max' using 'ifB' and '(<=*)'
sort2B :: (IfB a, OrdB a) => (a,a) -> (a,a)
sort2B (u,v) = ifB (u <=* v) (u,v) (v,u)



{--------------------------------------------------------------------
    Instances for Prelude types
--------------------------------------------------------------------}

-- Simple if-then-else as function.
ife :: Bool -> a -> a -> a
ife c t e = if c then t else e

-- I'd give the following instances:
-- 
--     instance          IfB a where ifB = ife
--     instance Eq  a => EqB a where { (==*) = (==) ; (/=*) = (/=) }
--     instance Ord a => Ord a where { (<*) = (<) ; (<=*) = (<=)}
-- 
-- Sadly, doing so would break the a->bool fundep, which is needed elsewhere
-- for disambiguation.  So use the instances above as templates, filling
-- in specific types for a.

#define SimpleInstances(Ty) \
instance IfB  (Ty) where { ifB = ife } ;\
instance EqB  (Ty) where { (==*) = (==) ; (/=*) = (/=) } ;\
instance OrdB (Ty) where { (<*) = (<) ; (<=*) = (<=) }

#define SimpleTy(Ty) \
type instance BooleanOf (Ty) = Bool ;\
SimpleInstances(Ty)

SimpleTy(Int)
SimpleTy(Integer)
SimpleTy(Float)
SimpleTy(Double)
SimpleTy(Bool)
SimpleTy(Char)

-- Similarly for other simple types.

-- TODO: Export these macros for external use. I guess I'd want a .h file as in
-- the applicative-numbers package.

type instance BooleanOf [a]       = BooleanOf a
type instance BooleanOf (a,b)     = BooleanOf a
type instance BooleanOf (a,b,c)   = BooleanOf a
type instance BooleanOf (a,b,c,d) = BooleanOf a
type instance BooleanOf (z -> a)  = z -> BooleanOf a

-- I'm uncomfortable with this list instance. It's unlike tuples and unlike
-- functions. It could be generalized from BooleanOf a ~ Bool to a general case
-- for applicatives, but then the list version would form cross products.
-- Consider strings and other list types under a variety of use scenarios.

instance (Boolean (BooleanOf a),BooleanOf a ~ Bool) => IfB [a] where { ifB = ife }

instance (bool ~ BooleanOf p, bool ~ BooleanOf q
         ,IfB p, IfB q) => IfB (p,q) where
  ifB w (p,q) (p',q') = (ifB w p p', ifB w q q')

instance (bool ~ BooleanOf p, bool ~ BooleanOf q, bool ~ BooleanOf r
         ,IfB p, IfB q, IfB r)
      => IfB (p,q,r) where
  ifB w (p,q,r) (p',q',r') = (ifB w p p', ifB w q q', ifB w r r')

instance (bool ~ BooleanOf p, bool ~ BooleanOf q, bool ~ BooleanOf r, bool ~ BooleanOf s
         ,IfB p, IfB q, IfB r, IfB s) => IfB (p,q,r,s) where
  ifB w (p,q,r,s) (p',q',r',s') =
    (ifB w p p', ifB w q q', ifB w r r', ifB w s s')

-- Instances for functions, using the standard pattern for applicative functions.
-- Note that the [] applicative does not use this instance. Fishy.

instance Boolean bool => Boolean (z -> bool) where
  true  = pure true
  false = pure false
  notB  = fmap notB
  (&&*) = liftA2 (&&*)
  (||*) = liftA2 (||*)

instance IfB a => IfB (z -> a) where
  ifB = cond

instance EqB a => EqB (z -> a) where
  { (==*) = liftA2 (==*) ; (/=*) = liftA2 (/=*) }
instance OrdB a => OrdB (z -> a) where
  { (<*) = liftA2 (<*) ; (<=*) = liftA2 (<=*) }

-- TODO: Generalize the function instance into a macro for arbitrary
-- applicatives. Instantiate for functions.

{-

{--------------------------------------------------------------------
    Tests
--------------------------------------------------------------------}

t1 :: String
t1 = ifB True "foo" "bar"

t2 :: Float -> Float
t2 = ifB (< 0) negate id

--     No instance for (IfB (a -> Bool) (a1 -> a1))
--       arising from a use of `ifB'
-- 
-- t2 = ifB (< 0) negate id                -- abs

-}