CLASS AND INSTANCE DECLARATIONS

class Eq a where
  (==), (/=) :: a -> a -> Bool
  x /= y = not (x == y)
instance Eq Bool where
  False == False = True
  True  == True  = True
  _     == _     = False

Bool is an instance of the Eq class, with (==) defined as above.

Only types that are defined using the data or newtype mechanisms can be made instances of classes.

Classes can also be extended to form new classes. For example, the class Ord of types whose values are totally ordered is declared as an extension of Eq class.

In [2]:
class Eq a => Ord a where
  (<), (<=), (>), (>=) :: a -> a -> Bool
  min, max             :: a -> a -> a
  min x y | x <= y    = x
          | otherwise = y
  max x y | x <= y    = y
          | otherwise = x
In [3]:
instance Ord Bool where
  False < True = True
  _     < _    = False
  
  b <= c = (b < c) || (b == c)
  b >  c = c < b
  b >= c = c <= b

Derived instances

When new types are declared, it is usually appropriate to make them instances of a number of built-in classes using the "deriving" clause.

data Bool = False | True deriving (Eq, Ord, Show, Read)
In [4]:
False == False
Redundant ==
Found:
False == False
Why Not:
not False
Redundant ==
Found:
False == False
Why Not:
not False
True
In [5]:
False < True
True
In [6]:
show False
"False"
In [7]:
read "False" :: Bool
False

The use of :: Bool in the above example is necessary as the read function needs to know what type it needs to convert the string "False" into.

For the purposes of deriving instances of the Ord type, the ordering of the constructors of a type is determined by the position in the declaration. So, False would be "less-than" True

In the case of constructors that have arguments, the types of these arguments should also be instance of any derived classes.

For example, in

data Shape = Circle Float | Rect Float Float

to derive Shape as an equality type requires Float also as deriving equality type.

or in

data Maybe a = Nothing | Just a

to derive Maybe as an equality type, the type argument a should also derive equality.

In [4]:
data Shape = Circle Float | Rectangle Float Float deriving (Eq, Ord, Show)
In [5]:
Rectangle 1.0 4.0 == Rectangle 2.0 3.0
False
In [7]:
Rectangle 1.0 4.0 > Rectangle 2.0 3.0
False
In [58]:
Circle 3.6 == Circle 5.5
False
In [59]:
show (Circle 4.4)
"Circle 4.4"

TAUTOLOGY CHECKER

Consider a language of propositions built up from basic values (False, True) and variables (A,B,...,Z) using "not", "and", "implication" and parentheses. For example the following are propositions:

A and not A
(A and B) -> A
A -> (A and B)
(A and (A -> B)) -> B

Recall from your Discrete Math class the meaning of such propositions (truth tables)

Step 1: Declare type for propositions

In [9]:
data Prop = 
    Const Bool
  | Var Char
  | Not Prop
  | And Prop Prop
  | Imply Prop Prop deriving Show

Note that we do not need to define parentheses as we can use the inbuilt Haskell parentheses. Using this type, we can instantiate above propositions as follows:

In [11]:
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')
In [12]:
show p4
"Imply (And (Var 'A') (Imply (Var 'A') (Var 'B'))) (Var 'B')"

Step 2: Define substitutions and eval

In [13]:
type Assoc k v = [(k,v)]
find :: Eq k => k -> Assoc k v -> v
find k t = head [v | (k',v) <- t, k == k']

type Subst = Assoc Char Bool

for example, the substitution [('A',False),('B',True)] assigns boolean values to variables. We can now define the following function, eval, which given a substitution and a proposition, evaluates the proposition based on the substitution to result in a boolean value.

In [14]:
eval :: Subst -> Prop -> Bool
eval _ (Const b)   = b
eval s (Var x)     = find x s
eval s (Not p)     = not (eval s p)
eval s (And p q)   = eval s p && eval s q
eval s (Imply p q) = eval s p <= eval s q
In [15]:
eval [('A',False),('B',True)] p2
True
In [16]:
eval [('A',True),('B',False)] p3
False

Step 3: Generate substitutions

In [18]:
-- function to return all variables in a proposition
vars :: Prop -> [Char]
vars (Const _)   = []
vars (Var x)     = [x]
vars (Not p)     = vars p
vars (And p q)   = vars p ++ vars q
vars (Imply p q) = vars p ++ vars q
Use String
Found:
Prop -> [Char]
Why Not:
Prop -> String
In [20]:
vars p1
vars p2
vars p3
vars p4
"AA"
"ABA"
"AAB"
"AABB"

Note: We will get rid of duplicates later

In [ ]:
-- function to generate all combinations of False and True for n variables
-- method: think of each combination as an integer in binary notation with 0=False,1=True
type Bit = Int
int2bin :: Int -> [Bit]
int2bin 0 = []
int2bin n = n `mod` 2 : int2bin (n `div` 2)

bools1 :: Int -> [[Bool]]
bools1 n = map (reverse . map conv . make n . int2bin) range
  where 
    range     = [0..(2^n)-1]
    make n bs = take n (bs ++ repeat 0)
    conv 0    = False
    conv 1    = True
In [23]:
bools1 4
[[False,False,False,False],[False,False,False,True],[False,False,True,False],[False,False,True,True],[False,True,False,False],[False,True,False,True],[False,True,True,False],[False,True,True,True],[True,False,False,False],[True,False,False,True],[True,False,True,False],[True,False,True,True],[True,True,False,False],[True,True,False,True],[True,True,True,False],[True,True,True,True]]
In [27]:
-- A simpler and recursive definition of bools
bools :: Int -> [[Bool]]
bools 0 = [[]]
bools n = map (False:) bss ++ map (True:) bss
  where bss = bools (n-1)
In [28]:
bools 3
[[False,False,False],[False,False,True],[False,True,False],[False,True,True],[True,False,False],[True,False,True],[True,True,False],[True,True,True]]
bools 2 = map (False:) (bools 1) ++ map (True:) (bools 1) = map (False:) ( map (False:) (bools 0) ++ map (True:) (bools 0) ) ++ map (True:) ( map (False:) (bools 0) ++ map (True:) (bools 0) ) = map (False:) ( map (False:) [[]] ++ map (True:) [[]] ) ++ map (True:) ( map (False:) [[]] ++ map (True:) [[]] ) = map (False:) ( [[False]] ++ [[True]] ) ++ map (True:) ( [[False]] ++ [[True]] ) = [[False,False]] ++ [[False,True]] ++ [[True,False]] ++ [[True,True]] = [[False,False],[False,True],[True,False],[True,True]]
In [29]:
-- now we generate all possible substitions for variables
rmdups :: Eq a => [a] -> [a]
rmdups [] = []
rmdups (x:xs) = x : filter (/= x) (rmdups xs)

substs :: Prop -> [Subst]
substs p = map (zip vs) (bools (length vs))
  where vs = rmdups (vars p)
In [30]:
substs p1
[[('A',False)],[('A',True)]]
In [31]:
substs p2
[[('A',False),('B',False)],[('A',False),('B',True)],[('A',True),('B',False)],[('A',True),('B',True)]]

Step 4: Test tautology

In [32]:
isTaut :: Prop -> Bool
isTaut p = and [eval s p | s <- substs p]
In [33]:
isTaut p1
isTaut p2
isTaut p3
isTaut p4
False
True
False
True

ABSTRACT MACHINE

In [ ]:
data Expr = Val Int | Add Expr Expr

value :: Expr -> Int
value (Val n)   = n
value (Add x y) = value x + value y
In [ ]:
e1 = Add (Add (Val 2) (Val 3)) (Val 4)
value e1
9
  value (Add (Add (Val 2) (Val 3)) (Val 4))
= value (Add (Val 2) (Val 3)) + value (Val 4)
= value (Val 2) + value (Val 3) + value (Val 4)
= (2 + value (Val 3)) + value (Val 4)
= (2 + 3) + value (Val 4)
= 5 + value (Val 4)
= 5 + 4
= 9

Note that the definition of value does not specify the order of operations; So, Haskell selects its own order; in this case left to right.

We introduce a method that uses a control stack containing operations that introduce a particular order of operation.

In [ ]:
type Cont = [Op]
data Op = EVAL Expr | ADD Int

The meaning of EVAL and ADD will be explained soon.

We define two mutually recursive functions:

  • eval: function evaluates an expression in the context of a control stack:
  • exec: executes operations on stack with respect to an integer
In [ ]:
eval :: Expr -> Cont -> Int
eval (Val n) c   = exec c n             -- if expression is an integer value then execute stack operations.
eval (Add x y) c = eval x (EVAL y : c)  -- otherwise, put EVAL y on stack (postpone!) until x is evaluated!

exec :: Cont -> Int -> Int
exec [] n           = n                   -- no more operations on stack; return n
exec (EVAL y : c) n = eval y (ADD n : c)  -- if top of stack contains EVAL y, then 
                                          -- call eval y with ADD n pushed to stack
exec (ADD n : c)  m = exec c (n + m)      -- if top of stack is ADD n, then two operands are ready to be added!
                                          -- so, add them, and execute rest of operations on stack

eval proceeds downwards in the parse tree to the leftmost integer in the expression maintaining a trail of pending RHS operands.

exec proceeds upwards through the trail tranferring control to eval and performing additions as needed.

The following functions gets the computation started:

In [ ]:
value1 :: Expr -> Int
value1 e = eval e []
In [ ]:
value1 e1  -- e1 = Add (Add (Val 2) (Val 3)) (Val 4)
9
  value1 (Add (Add (Val 2) (Val 3)) (Val 4))
= eval (Add (Add (Val 2) (Val 3)) (Val 4)) []
= eval (Add (Val 2) (Val 3)) [EVAL (Val 4)]
= eval (Val 2) [EVAL (Val 3), EVAL (Val 4)]
= exec [EVAL (Val 3), EVAL (Val 4)] 2
= eval (Val 3) [ADD 2, EVAL (Val 4)]
= exec [ADD 2, EVAL (Val 4)] 3
= exec [EVAL (Val 4)] 5
= eval (Val 4) [ADD 5]
= exec [ADD 5] 4
= exec [] 9
= 9