morley-prelude-0.5.2: src/Morley/Prelude/Boolean.hs
-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA
{-# LANGUAGE NoImplicitPrelude #-}
-- | This module replaces the monomorphic boolean operators from "Prelude"
-- with a set of polymorphic operators.
module Morley.Prelude.Boolean
( Boolean(..)
, BooleanMonoid(..)
, ApplicativeBoolean(..)
, or
, and
, or1
, and1
, Any(..)
, All(..)
, any
, all
, any1
, all1
-- * Example definitions
-- $setup
) where
import Universum hiding (All(..), Any(..), all, and, any, not, or, (&&), (||))
import Universum qualified
import Data.Coerce (coerce)
import Data.Data (Data)
{- $setup
>>> :{
data Vote = Yay | Nay deriving Show
--
instance Boolean Vote where
Yay && Yay = Yay
_ && _ = Nay
Nay || Nay = Nay
_ || _ = Yay
not Yay = Nay
not Nay = Yay
--
instance BooleanMonoid Vote where
true = Yay
false = Nay
:}
-}
{- | Generalized boolean operators.
This is useful for defining things that behave like booleans, e.g. predicates,
or EDSL for predicates.
>>> Yay && Nay
Nay
>>> and1 $ Yay :| replicate 9 Yay
Yay
There are also instances for these types lifted into 'IO' and @(->) a@:
>>> (const Yay) && (const Nay) $ ()
Nay
>>> (const Yay) || (const Nay) $ ()
Yay
-}
class Boolean a where
(&&) :: a -> a -> a
(||) :: a -> a -> a
infixr 3 &&
infixr 2 ||
not :: a -> a
instance Boolean Bool where
(&&) = (Universum.&&)
(||) = (Universum.||)
not = Universum.not
{- | Generalized 'True' and 'False'.
This is useful to complete the isomorphism between regular and generalized
booleans. It's a separate class because not all boolean-like things form a monoid.
>>> or $ replicate 10 Nay
Nay
-}
class Boolean a => BooleanMonoid a where
false :: a
false = not true
true :: a
true = not false
{-# MINIMAL true | false #-}
instance BooleanMonoid Bool where
false = False
true = True
{- | A generalized version of @All@ monoid wrapper.
>>> All Nay <> All Nay
All {getAll = Nay}
>>> All Yay <> All Nay
All {getAll = Nay}
>>> All Yay <> All Yay
All {getAll = Yay}
-}
newtype All a = All { getAll :: a }
deriving stock (Generic, Data, Read, Show, Eq, Ord)
deriving newtype (Bounded, Enum)
{- | A generalized version of @Any@ monoid wrapper.
>>> Any Nay <> Any Nay
Any {getAny = Nay}
>>> Any Yay <> Any Nay
Any {getAny = Yay}
>>> Any Yay <> Any Yay
Any {getAny = Yay}
-}
newtype Any a = Any { getAny :: a }
deriving stock (Generic, Data, Read, Show, Eq, Ord)
deriving newtype (Bounded, Enum)
instance Boolean a => Semigroup (Any a) where
(<>) = coerce $ (||) @a
instance Boolean a => Semigroup (All a) where
(<>) = coerce $ (&&) @a
instance BooleanMonoid a => Monoid (Any a) where
mempty = coerce $ false @a
instance BooleanMonoid a => Monoid (All a) where
mempty = coerce $ true @a
{- | Generalized version of 'Universum.or'.
>>> or $ replicate 10 Nay
Nay
>>> or $ Yay : replicate 10 Nay
Yay
-}
or :: (Container c, BooleanMonoid (Element c)) => c -> Element c
or = any id
{- | Generalized version of 'Universum.and'.
>>> and $ replicate 10 Yay
Yay
>>> and $ Nay : replicate 10 Yay
Nay
-}
and :: (Container c, BooleanMonoid (Element c)) => c -> Element c
and = all id
{- | A version of 'or' that works on 'NonEmpty', thus doesn't require
'BooleanMonoid' instance.
>>> or1 $ Yay :| [Nay]
Yay
-}
or1 :: Boolean a => NonEmpty a -> a
or1 = foldr1 (||)
{-# ANN or1 ("HLint: ignore Use or" :: Text) #-}
{- | A version of 'and' that works on 'NonEmpty', thus doesn't require
'BooleanMonoid' instance.
>>> and1 $ Yay :| [Nay]
Nay
-}
and1 :: Boolean a => NonEmpty a -> a
and1 = foldr1 (&&)
{-# ANN and1 ("HLint: ignore Use and" :: Text) #-}
{- | Generalized 'Universum.any'.
>>> any (\x -> if x > 50 then Yay else Nay) [1..100]
Yay
-}
any :: (Container c, BooleanMonoid b) => (Element c -> b) -> c -> b
any f = getAny . foldMap (Any . f)
{- | Generalized 'Universum.all'.
>>> all (\x -> if x > 50 then Yay else Nay) [1..100]
Nay
-}
all :: (Container c, BooleanMonoid b) => (Element c -> b) -> c -> b
all f = getAll . foldMap (All . f)
{- | A version of 'any' that works on 'NonEmpty', thus doesn't require
'BooleanMonoid' instance.
>>> any1 (\x -> if x > 50 then Yay else Nay) $ 50 :| replicate 10 0
Nay
-}
any1 :: Boolean b => (a -> b) -> NonEmpty a -> b
any1 f = or1 . fmap f
{- | A version of 'all' that works on 'NonEmpty', thus doesn't require
'BooleanMonoid' instance.
>>> all1 (\x -> if x > 50 then Yay else Nay) $ 100 :| replicate 10 51
Yay
-}
all1 :: Boolean b => (a -> b) -> NonEmpty a -> b
all1 f = and1 . fmap f
-- | A newtype for deriving a 'Boolean' instance for any 'Applicative' type
-- constructor using @DerivingVia@.
newtype ApplicativeBoolean f bool = ApplicativeBoolean (f bool)
deriving newtype (Functor, Applicative)
instance (Applicative f, Boolean bool) => Boolean (ApplicativeBoolean f bool) where
(&&) = liftA2 (&&)
(||) = liftA2 (||)
not = fmap not
deriving via (ApplicativeBoolean IO bool) instance Boolean bool => Boolean (IO bool)
deriving via (ApplicativeBoolean ((->) a) bool) instance Boolean bool => Boolean (a -> bool)
instance (Applicative f, BooleanMonoid bool)
=> BooleanMonoid (ApplicativeBoolean f bool) where
true = pure true
false = pure false
deriving via (ApplicativeBoolean IO bool)
instance BooleanMonoid bool => BooleanMonoid (IO bool)
deriving via (ApplicativeBoolean ((->) a) bool)
instance BooleanMonoid bool => BooleanMonoid (a -> bool)