Tonλ's blog May the λ be with you

PIH - ch10 - 2/3 - Declaring types and classes - exercises

by @ardumont on

Here is the second part of the exercise from the chapter 10. The first part is here.

Tautology

Extend the tautology checker to support the use of logical disjunction (v) and equivalence (<=>) in propositions.

Here are the adaptation to the Prop definition regarding the logical disjunction, and the equivalence:

data Prop = Const Bool
          | Var Char
          | Not Prop
          | And Prop Prop
          | Imply Prop Prop
          | Or Prop Prop
          | Equiv Prop Prop
          deriving Show

Here are some examples:

p0 :: Prop
p0 = And (Var 'A') (Var 'B')

p1 :: Prop
p1 = And (Var 'A') (Not (Var 'A'))

p2 :: Prop
p2 = Imply (And (Var 'A') (Var 'B')) (Var 'A')

p3 :: Prop
p3 = Imply (Var 'A') (And (Var 'A') (Var 'B'))

p4 :: Prop
p4 = Imply (And (Var 'A') (Imply
      (Var 'A') (Var 'B'))) (Var 'B')

p5 :: Prop
p5 = Or (Var 'A') (And (Var 'B') (Var 'C'))

p6 :: Prop
p6 = Equiv (Var 'a') (Var 'b')

p7 :: Prop
p7 = Not p6

p8 :: Prop
p8 = Imply p6 p7

p9 :: Prop
p9 = Equiv (Var 'A') (And (Var 'B') (Var 'C'))

This impacts the eval function, we just add the 2 new definitions:

type Subst = Assoc Char Bool

eval :: Subst -> Prop -> Bool
eval _ (Const b)   = b
eval s (Var v)     = find v s
eval s (Not b)     = not $ eval s b
eval s (And a b)   = eval s a && eval s b
eval s (Imply a b) = eval s a <= eval s b
eval s (Or a b)    = eval s a || eval s b
eval s (Equiv a b) = eval s $ And (Imply a b) (Imply b a)

We also need to compute the possible variables on the Prop:

vars :: Prop -> [Char]
vars (Const _)   = []
vars (Var v)     = [v]
vars (Not b)     = vars b
vars (And a b)   = vars a ++ vars b
vars (Or a b)    = vars a ++ vars b
vars (Imply a b) = vars a ++ vars b
vars (Equiv a b) = vars a ++ vars b

Some output:

*Ch10> vars p1
"AA"
*Ch10> vars p2
"ABA"
*Ch10> vars p3
"AAB"
*Ch10> vars p4
"AABB"
*Prop> vars p5
"ABC"

Here is the remaining code:

bools :: Int -> [[Bool]]
bools 0 = [[]]
bools 1 = [[True], [False]]
bools n = map (False:) bn ++ map (True:) bn
          where bn = bools (n-1)

-- *Ch10> bools 0
-- []
-- *Ch10> bools 1
-- [[True],[False]]
-- *Ch10> bools 2
-- [[False,True],[False,False],[True,True],[True,False]]
-- *Ch10> bools 3
-- [[False,False,True],[False,False,False],[False,True,True],[False,True,False],[True,False,True],[True,False,False],[True,True,True],[True,True,False]]

substs :: Prop -> [Subst]
substs p = map (zip vs) (bools (length vs))
  where vs = rmdups (vars p)

-- *Prop> substs p1
-- [[('A',True)],[('A',False)]]
-- *Prop> substs p2
-- [[('A',False),('B',True)],[('A',False),('B',False)],[('A',True),('B',True)],[('A',True),('B',False)]]
-- *Prop> substs p3
-- [[('A',False),('B',True)],[('A',False),('B',False)],[('A',True),('B',True)],[('A',True),('B',False)]]
-- *Prop> substs p4
-- [[('A',False),('B',True)],[('A',False),('B',False)],[('A',True),('B',True)],[('A',True),('B',False)]]

isTaut :: Prop -> Bool
isTaut p = and [ eval s p | s <- substs p ]

Some output examples:

*Prop> isTaut $ Or (Var 'a') (Not (Var 'a'))
True
*Prop> isTaut $ Or (Var 'a') (Var 'a')
False
*Prop> isTaut $ Equiv (Var 'a') (Not (Var 'a'))
False
*Prop> isTaut $ Equiv (Var 'a') (Var 'a')
True
*Prop> isTaut $ Equiv (Var 'a') (Var 'b')
False

Interactive tautology checker

Using the function isTaut together with the parsing and interaction libraries from the previous two chapters, define an interactive tautology checker that allows propositions to be entered from the keyboard in a user-friendly syntax.

Hint: Build a parser for propositions by modifying the parser for arithmetic expressions given in chapter 8.

Recall the definition of the Prop type:

data Prop = Const Bool
          | Var Char
          | Not Prop
          | And Prop Prop
          | Imply Prop Prop
          | Or Prop Prop
          | Equiv Prop Prop
          deriving Show

And now, the program, we first define the module and import the needed library:

module PropParsers where

import Parsers
import Prop

Then, to solve such problem, we created a parser of type Parser Prop. Here is the main parser (as haskell is compiled, we can use the other parsers even though they are not yet defined):

prop :: Parser Prop
prop = propConst +++
         propVar +++
         propNot +++
         propAnd +++
         propOr  +++
         propImply +++
         propEquiv

To simplify the writing, we define 't' and 'f' as the symbol for True and False:

mapBool :: [(String, Bool)]
mapBool = [("t", True), ("f", False)]

propConst :: Parser Prop
propConst = do b <- symbol "t" +++ symbol "f"
               let bool = (lookup b mapBool) in
                 case bool of
                   Just v -> return $ Const v

Possible output:

*PropParsers> parse propConst "t"
[(Const True,"")]
*PropParsers> parse propConst "f"
[(Const False,"")]
*PropParsers> parse propConst "a"
[]

Var (just a char):

propVar :: Parser Prop
propVar = do l <- token letter
             return $ Var l

Possible output:

*PropParsers> parse propVar "a"
[(Var 'a',"")]
*PropParsers> parse propVar "t"
[(Var 't',"")]
*PropParsers> parse propVar "na"
[(Var 'n',"a")]
*PropParsers> parse propVar "1"
[]

not (!):

propNot :: Parser Prop
propNot = do symbol "!"
             a <- prop
             return (Not a)

Output:

*PropParsers> parse propNot "! a"
[(Not (Var 'a'),"")]
*PropParsers> parse propNot "! asldfsd"
[(Not (Var 'a'),"sldfsd")]
*PropParsers> parse propNot "! t"
[(Not (Const True),"")]
*PropParsers> parse propNot " t"
[]

and (&):

propAnd :: Parser Prop
propAnd = do symbol "&"
             a <- prop
             b <- prop
             return (And a b)

Output:

*PropParsers> parse propAnd "& a b"
[(And (Var 'a') (Var 'b'),"")]
*PropParsers> parse propAnd "& a ! b"
[(And (Var 'a') (Not (Var 'b')),"")]
*PropParsers> parse propAnd "& a ! t"
[(And (Var 'a') (Not (Const True)),"")]
*PropParsers> parse propAnd "& a ! t"
[(And (Var 'a') (Not (Const True)),"")]
*PropParsers> parse propAnd "& a "
[]

Or (|):

propOr :: Parser Prop
propOr = do symbol "|"
            a <- prop
            b <- prop
            return (Or a b)

Output:

*PropParsers> parse propOr "| a "
[]
*PropParsers> parse propOr "| a b"
[(Or (Var 'a') (Var 'b'),"")]
*PropParsers> parse propOr "| a ! b"
[(Or (Var 'a') (Not (Var 'b')),"")]
*PropParsers> parse propOr "| t ! b"
[(Or (Const True) (Not (Var 'b')),"")]
*PropParsers> parse propOr "| t ! & a c"
[(Or (Const True) (Not (And (Var 'a') (Var 'c'))),"")]

Imply (=>):

propImply :: Parser Prop
propImply = do symbol "=>"
               a <- prop
               b <- prop
               return (Imply a b)

Output:

*PropParsers> parse propImply "=> t ! & a c"
[(Imply (Const True) (Not (And (Var 'a') (Var 'c'))),"")]
*PropParsers> parse propImply "=> t "
[]

Equivalence (<=>):

propEquiv :: Parser Prop
propEquiv = do symbol "<=>"
               a <- prop
               b <- prop
               return (Equiv a b)

Output:

*PropParsers> parse propEquiv "<=> t "
[]
*PropParsers> parse propEquiv "<=> t ! & a c"
[(Equiv (Const True) (Not (And (Var 'a') (Var 'c'))),"")]

The main evaluation program that will wrap the resulting of evaluating the expression into a Maybe Prop. If the evaluation fails, this will return Nothing. Otherwise, return Just the proposition successfully parsed.

propEval :: String -> (Maybe Prop)
propEval s = case parse prop s of
  [(p, "")] -> Just p
  _         -> Nothing
*PropParsers> propEval "f"
Just (Const False)
*PropParsers> propEval "f"
Just (Const False)
*PropParsers> propEval "! f"
Just (Not (Const False))
*PropParsers> propEval "! a"
Just (Not (Var 'a'))
*PropParsers> propEval "& ! a b"
Just (And (Not (Var 'a')) (Var 'b'))
*PropParsers> propEval "& ! a t"
Just (And (Not (Var 'a')) (Const True))
*PropParsers> propEval "& ! a <=> t f"
Just (And (Not (Var 'a')) (Equiv (Const True) (Const False)))

At last, the main program which is in charge of asking the user for a proposition. This will check the input, if incorrect, this will relaunch the main program. Or else, this will check if it's a tautology. It it is, this will print "tautology!", otherwise "not a tautology!" and relaunch the main program.

prompt :: [String]
prompt = ["keywords: &, !, t, f, =>, <=>, and any other character",
          "Enter a Proposition:"]

askUser :: IO ()
askUser =
    do mapM_ putStrLn prompt
       p <- getLine
       let r = propEval p in
         do putStrLn (case r of
                         Just v -> (if isTaut v
                                    then "tautology!"
                                    else "Not a tautology!")
                         _    ->  "Invalid input!")
            askUser

main :: IO ()
main = askUser

Here is a run sample output:

*PropParsers> main
keywords: &, !, t, f, =>, <=>, and any other character
Enter a Proposition:
a
Not a tautology!
keywords: &, !, t, f, =>, <=>, and any other character
Enter a Proposition:
b
Not a tautology!
keywords: &, !, t, f, =>, <=>, and any other character
Enter a Proposition:
t
tautology!
keywords: &, !, t, f, =>, <=>, and any other character
Enter a Proposition:
f
Not a tautology!
keywords: &, !, t, f, =>, <=>, and any other character
Enter a Proposition:
! f
tautology!
keywords: &, !, t, f, =>, <=>, and any other character
Enter a Proposition:
& t t
tautology!
keywords: &, !, t, f, =>, <=>, and any other character
Enter a Proposition:
& t ! f
tautology!
keywords: &, !, t, f, =>, <=>, and any other character
Enter a Proposition:
| a ! a
tautology!
keywords: &, !, t, f, =>, <=>, and any other character
Enter a Proposition:
| a ! b
Not a tautology!
keywords: &, !, t, f, =>, <=>, and any other character
Enter a Proposition:

Latest posts