packages feed

swarm-0.3.0.0: src/Data/BoolExpr.hs

{-
Copyright (c) 2008, 2009, Nicolas Pouillard
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above
      copyright notice, this list of conditions and the following
      disclaimer in the documentation and/or other materials provided
      with the distribution.

    * Neither the name of the copyright holders nor the names of other
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE NoDeriveAnyClass #-}

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

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

-- |
-- Module    : Data.BoolExpr
-- Copyright : (c) Nicolas Pouillard 2008,2009
-- License   : BSD3
--
-- Maintainer: Nicolas Pouillard <nicolas.pouillard@gmail.com>
-- Stability : provisional
-- Portability:
--
-- Boolean expressions and various representations.
module Data.BoolExpr (
  -- * A boolean class
  Boolean (..),

  -- * Boolean trees
  BoolExpr (..),
  reduceBoolExpr,
  evalBoolExpr,

  -- * Boolean evaluation semantic
  Eval (..),
  runEvalId,

  -- * Signed constants
  Signed (..),
  negateSigned,
  evalSigned,
  constants,
  negateConstant,

  -- * Conjunctive Normal Form
  CNF (..),
  Conj (..),
  boolTreeToCNF,

  -- * Disjunctive Normal Form
  Disj (..),
  DNF (..),
  boolTreeToDNF,

  -- * Other transformations
  dualize,
  fromBoolExpr,
  pushNotInwards,
)
where

import Control.Monad (ap)
import Data.Aeson
import Data.Char (toLower)
import Data.Traversable
import GHC.Generics (Generic)

-- | Signed values are either positive or negative.
data Signed a = Positive a | Negative a
  deriving (Eq, Ord, Generic, Show, Read)

instance Functor Signed where
  fmap f (Positive x) = Positive (f x)
  fmap f (Negative x) = Negative (f x)

instance Traversable Signed where
  traverse f (Positive x) = Positive <$> f x
  traverse f (Negative x) = Negative <$> f x

instance Foldable Signed where
  foldMap = foldMapDefault

instance Applicative Signed where
  pure = Positive
  (<*>) = ap

instance Monad Signed where
  Positive x >>= f = f x
  Negative x >>= f = negateSigned $ f x

infix 9 /\
infix 9 \/

-- | A boolean type class.
class Boolean f where
  (/\) :: f a -> f a -> f a
  (\/) :: f a -> f a -> f a
  bNot :: f a -> f a
  bTrue :: f a
  bFalse :: f a
  bConst :: Signed a -> f a

-- | Syntax of boolean expressions parameterized over a
-- set of leaves, named constants.
data BoolExpr a
  = BAnd (BoolExpr a) (BoolExpr a)
  | BOr (BoolExpr a) (BoolExpr a)
  | BNot (BoolExpr a)
  | BTrue
  | BFalse
  | BConst (Signed a)
  deriving (Eq, Ord, Generic, Show {-! derive : Arbitrary !-})

encodingOptions :: Options
encodingOptions =
  defaultOptions
    { sumEncoding = ObjectWithSingleField
    , constructorTagModifier = map toLower
    }

instance (ToJSON a) => ToJSON (Signed a) where
  toJSON = genericToJSON encodingOptions

instance (ToJSON a) => ToJSON (BoolExpr a) where
  toJSON = genericToJSON encodingOptions

instance (ToJSON a) => ToJSON (DNF a) where
  toJSON = genericToJSON encodingOptions

instance (ToJSON a) => ToJSON (CNF a) where
  toJSON = genericToJSON encodingOptions

instance (ToJSON a) => ToJSON (Conj a) where
  toJSON = genericToJSON encodingOptions

instance (ToJSON a) => ToJSON (Disj a) where
  toJSON = genericToJSON encodingOptions

instance Functor BoolExpr where
  fmap f (BAnd a b) = BAnd (fmap f a) (fmap f b)
  fmap f (BOr a b) = BOr (fmap f a) (fmap f b)
  fmap f (BNot t) = BNot (fmap f t)
  fmap _ BTrue = BTrue
  fmap _ BFalse = BFalse
  fmap f (BConst x) = BConst (fmap f x)

instance Traversable BoolExpr where
  traverse f (BAnd a b) = BAnd <$> traverse f a <*> traverse f b
  traverse f (BOr a b) = BOr <$> traverse f a <*> traverse f b
  traverse f (BNot t) = BNot <$> traverse f t
  traverse _ BTrue = pure BTrue
  traverse _ BFalse = pure BFalse
  traverse f (BConst x) = BConst <$> traverse f x

instance Foldable BoolExpr where
  foldMap = foldMapDefault

instance Boolean BoolExpr where
  (/\) = BAnd
  (\/) = BOr
  bNot = BNot
  bTrue = BTrue
  bFalse = BFalse
  bConst = BConst

newtype Eval b a = Eval {runEval :: (a -> b) -> b}

runEvalId :: Eval a a -> a
runEvalId e = runEval e id

instance b ~ Bool => Boolean (Eval b) where
  (/\) = liftE2 (&&)
  (\/) = liftE2 (||)
  bNot = liftE not
  bTrue = Eval $ const True
  bFalse = Eval $ const False
  bConst = Eval . flip evalSigned

liftE :: (b -> b) -> Eval b a -> Eval b a
liftE f (Eval x) = Eval (f . x)

liftE2 :: (b -> b -> b) -> Eval b a -> Eval b a -> Eval b a
liftE2 f (Eval x) (Eval y) = Eval (\e -> f (x e) (y e))

-- | Turns a boolean tree into any boolean type.
fromBoolExpr :: Boolean f => BoolExpr a -> f a
fromBoolExpr (BAnd l r) = fromBoolExpr l /\ fromBoolExpr r
fromBoolExpr (BOr l r) = fromBoolExpr l \/ fromBoolExpr r
fromBoolExpr (BNot t) = bNot $ fromBoolExpr t
fromBoolExpr BTrue = bTrue
fromBoolExpr BFalse = bFalse
fromBoolExpr (BConst c) = bConst c

--- | Disjunction of atoms ('a')
newtype Disj a = Disj {unDisj :: [a]}
  deriving (Show, Generic, Functor, Semigroup, Monoid)

--- | Conjunction of atoms ('a')
newtype Conj a = Conj {unConj :: [a]}
  deriving (Show, Generic, Functor, Semigroup, Monoid)

--- | Conjunctive Normal Form
newtype CNF a = CNF {unCNF :: Conj (Disj (Signed a))}
  deriving (Show, Generic, Semigroup, Monoid)

--- | Disjunctive Normal Form
newtype DNF a = DNF {unDNF :: Disj (Conj (Signed a))}
  deriving (Show, Generic, Semigroup, Monoid)

instance Functor CNF where
  fmap f (CNF x) = CNF (fmap (fmap (fmap f)) x)

instance Boolean CNF where
  l /\ r = l `mappend` r
  l \/ r =
    CNF $
      Conj
        [ x `mappend` y | x <- unConj $ unCNF l, y <- unConj $ unCNF r
        ]
  bNot = error "bNot on CNF"
  bTrue = CNF $ Conj []
  bFalse = CNF $ Conj [Disj []]
  bConst x = CNF $ Conj [Disj [x]]

instance Functor DNF where
  fmap f (DNF x) = DNF (fmap (fmap (fmap f)) x)

instance Boolean DNF where
  l /\ r =
    DNF $
      Disj
        [ x `mappend` y | x <- unDisj $ unDNF l, y <- unDisj $ unDNF r
        ]
  l \/ r = l `mappend` r
  bNot = error "bNot on CNF"
  bTrue = DNF $ Disj [Conj []]
  bFalse = DNF $ Disj []
  bConst x = DNF $ Disj [Conj [x]]

-- | Reduce a boolean tree annotated by booleans to a single boolean.
reduceBoolExpr :: BoolExpr Bool -> Bool
reduceBoolExpr = evalBoolExpr id

-- Given a evaluation function of constants, returns an evaluation
-- function over boolean trees.
--
-- Note that since 'BoolExpr' is a functor, one can simply use
-- 'reduceBoolExpr':
--
-- @
-- evalBoolExpr f = reduceBoolExpr . fmap (f$)
-- @
evalBoolExpr :: (a -> Bool) -> (BoolExpr a -> Bool)
evalBoolExpr env expr = runEval (fromBoolExpr expr) env

-- | Returns constants used in a given boolean tree, these
-- constants are returned signed depending one how many
-- negations stands over a given constant.
constants :: BoolExpr a -> [Signed a]
constants = go True
 where
  go sign (BAnd a b) = go sign a ++ go sign b
  go sign (BOr a b) = go sign a ++ go sign b
  go sign (BNot t) = go (not sign) t
  go _ BTrue = []
  go _ BFalse = []
  go sign (BConst x) = [if sign then x else negateSigned x]

dualize :: Boolean f => BoolExpr a -> f a
dualize (BAnd l r) = dualize l \/ dualize r
dualize (BOr l r) = dualize l /\ dualize r
dualize BTrue = bFalse
dualize BFalse = bTrue
dualize (BConst c) = negateConstant c
dualize (BNot e) = fromBoolExpr e

-- When dualize is used by pushNotInwards not BNot remain,
-- hence it makes sense to assert that dualize does not
-- have to work on BNot. However `dualize` can be freely
-- used as a fancy `bNot`.
-- dualize (BNot _)   = error "dualize: impossible"

-- | Push the negations inwards as much as possible.
-- The resulting boolean tree no longer use negations.
pushNotInwards :: Boolean f => BoolExpr a -> f a
pushNotInwards (BAnd l r) = pushNotInwards l /\ pushNotInwards r
pushNotInwards (BOr l r) = pushNotInwards l \/ pushNotInwards r
pushNotInwards (BNot t) = dualize $ pushNotInwards t
pushNotInwards BTrue = bTrue
pushNotInwards BFalse = bFalse
pushNotInwards (BConst c) = bConst c

-- | Convert a boolean tree to a conjunctive normal form.
boolTreeToCNF :: BoolExpr a -> CNF a
boolTreeToCNF = pushNotInwards

-- | Convert a boolean tree to a disjunctive normal form.
boolTreeToDNF :: BoolExpr a -> DNF a
boolTreeToDNF = pushNotInwards

evalSigned :: (a -> Bool) -> Signed a -> Bool
evalSigned f (Positive x) = f x
evalSigned f (Negative x) = not $ f x

negateSigned :: Signed a -> Signed a
negateSigned (Positive x) = Negative x
negateSigned (Negative x) = Positive x

negateConstant :: Boolean f => Signed a -> f a
negateConstant = bConst . negateSigned