Lab03
In this scenario we investigate how to represent and manipulate formulas of first-order logic in Haskell.
Imports and utility functions
We begin with importing some basic modules and defining some utility functions that will be used throughout.
import Data.List
import Control.Monad
import Test.QuickCheck hiding (Fun)
partitions :: [a] -> [[[a]]]
partitions [] = [[]]
partitions (x:xs) = [[x]:yss | yss <- partitions xs] ++ [(x:ys):yss | (ys:yss) <- partitions xs]
-- all possible ways to split n into the sum of stricly positive integers
catalan :: Int -> [[Int]]
catalan n = map (map length) $ partitions [1..n]
todo = undefined
Syntax of first-order logic
Names
For simplicity we use plain String
s to represent variable, function, and relation names.
type VarName = String
type FunName = String
type RelName = String
-- enumerate variants of a variable name
variants :: VarName -> [VarName]
variants x = x : [x ++ show n | n <- [0..]]
For the purpose of testing, we want to generate just few distinct names:
instance Arbitrary VarName where
arbitrary = elements ["x", "y", "z", "t"]
instance Arbitrary FunName where
arbitrary = elements ["f", "g", "h", "i"]
instance Arbitrary RelName where
arbitrary = elements ["R", "S", "T", "U"]
Terms
Terms are defined by structural induction. In the base case we have just a variable. In the inductive case, we have a functional symbol together with a (possibly empty) list of arguments.
data Term = Var VarName | Fun FunName [Term] deriving (Eq, Show)
For instance, we can compute the list of variables appearing in a term.
varsT :: Term -> [VarName]
varsT (Var x) = [x]
varsT (Fun _ ts) = nub $ concatMap varsT ts
Generation of random terms, useful for testing:
instance Arbitrary Term where
arbitrary = resize 8 $ sized f where
f size | size == 0 || size == 1 = do x <- arbitrary
return $ Var x
| otherwise = frequency [ (1, go sizes) | sizes <- catalan size]
where go sizes = do ts <- sequence $ map f sizes
return $ Fun "f" ts
Formulas
Formulas are also defined by structural induction. In the base case we have atomic formulas F
, T
, and relations applied to a list of terms Rel RelName [Term]
. We also have the usual boolean connectives Not
, And
, Or
, Implies
, and Iff
.
Finally, we have quantifiers. The existentially quantified formula inline_formula not implemented is represented by Exists x phi
, where x
is a variable name and phi
is a formula. The case of the universal quantifier is similar.
data Formula =
T |
F |
Rel RelName [Term] |
Not Formula |
And Formula Formula |
Or Formula Formula |
Implies Formula Formula |
Iff Formula Formula |
Exists VarName Formula |
Forall VarName Formula deriving (Eq, Show)
infixr 8 /\, ∧
(/\) = And
(∧) = And
infixr 5 \/, ∨, ==>
(\/) = Or
(∨) = Or -- input with "\or"
(==>) = Implies
infixr 4 <==>, ⇔
(<==>) = Iff
(⇔) = Iff -- input with "\lr"
Generation of random formulas for the purpose of testing:
instance Arbitrary Formula where
arbitrary = resize 30 $ sized f where
f 0 = do ts <- arbitrary
return $ Rel "R" ts
f size = frequency [
(1, liftM Not (f (size - 1))),
(4, do
size' <- choose (0, size - 1)
conn <- oneof $ map return [And, Or, Implies, Iff]
left <- f $ size'
right <- f $ size - size' - 1
return $ conn left right),
(5, do conn <- oneof $ map return [Exists, Forall]
phi <- f $ size - 1
x <- arbitrary
return $ conn x phi)
]
Variables
The following function collects all variables appearing in a formula:
vars :: Formula -> [VarName]
vars T = []
vars F = []
vars (Rel _ ts) = varsT (Fun "dummy" ts)
vars (Not phi) = vars phi
vars (And phi psi) = nub $ vars phi ++ vars psi
vars (Or phi psi) = nub $ vars phi ++ vars psi
vars (Implies phi psi) = nub $ vars phi ++ vars psi
vars (Iff phi psi) = nub $ vars phi ++ vars psi
vars (Exists x phi) = nub $ x : vars phi
vars (Forall x phi) = nub $ x : vars phi
A variable is fresh in a formula if it does not appear therein:
freshIn :: VarName -> Formula -> Bool
x `freshIn` phi = not $ x `elem` vars phi
The following function returns a variable which is fresh in a given formula. This will be useful later to avoid variable capture when working with substitution.
freshVariant :: VarName -> Formula -> VarName
freshVariant x phi = head [ y | y <- variants x, y `freshIn` phi ]
Variable renaming
The following function replaces free occurrences of a variable name with another one.
renameT :: VarName -> VarName -> Term -> Term
renameT x y (Var z)
| z == x = Var y
| otherwise = Var z
renameT x y (Fun f ts) = Fun f (map (renameT x y) ts)
rename :: VarName -> VarName -> Formula -> Formula
rename x y T = T
rename x y F = F
rename x y (Rel r ts) = Rel r (map (renameT x y) ts)
rename x y (Not phi) = Not (rename x y phi)
rename x y (And phi psi) = And (rename x y phi) (rename x y psi)
rename x y (Or phi psi) = Or (rename x y phi) (rename x y psi)
rename x y (Implies phi psi) = Implies (rename x y phi) (rename x y psi)
rename x y (Iff phi psi) = Iff (rename x y phi) (rename x y psi)
rename x y (Forall z phi)
| z == x = Forall z phi
| otherwise = Forall z (rename x y phi)
rename x y (Exists z phi)
| z == x = Exists z phi
| otherwise = Exists z (rename x y phi)
Example formulas
We can construct and print some example formulas. Drinker's paradox:
d x = Rel "d" [Var x]
drinker = Exists "x" (d "x" ==> Forall "y" (d "y"))
print drinker
A version of the law of the excluded middle:
t x y = Rel "t" [Var x, Var y]
lem = Forall "x" $ Forall "y" $ t "x" "y" ∨ (Not $ t "x" "y")
print lem
flip = Exists "x" (Forall "y" $ t "x" "y") ==> Forall "y" (Exists "x" $ t "x" "y")
print flip
Normal forms
We are now ready to investigate normal form transformations.
Negation normal form (Exercise)
In the negation normal form (NNF) negation is applied only to atomic formulas (i.e., formulas of the form Rel r ts
) and neither implication Implies
nor bi-implication Iff
is allowed*:*
is_nnf :: Formula -> Bool
is_nnf T = True
is_nnf F = True
is_nnf (Rel _ _) = True
is_nnf (Not (Rel _ _)) = True
is_nnf (And phi psi) = is_nnf phi && is_nnf psi
is_nnf (Or phi psi) = is_nnf phi && is_nnf psi
is_nnf (Implies phi psi) = False
is_nnf (Iff phi psi) = False
is_nnf (Not _) = False
is_nnf (Exists _ phi) = is_nnf phi
is_nnf (Forall _ phi) = is_nnf phi
Define a function that transforms a given formula of first-order logic into a logically equivalent formula in NNF. Hint: Push the negation inside the formula based on de Morgan’s laws. For the universal quantifier, we have inline_formula not implemented, and dually for the existential quantifier.
nnf :: Formula -> Formula
nnf = todo
We can test that nnf
produces formulas in NNF:
nnfProp :: Formula -> Bool
nnfProp phi = is_nnf (nnf phi)
-- quickCheck nnfProp
Prenex normal form (Exercise)
A formula is in prenex normal form (PNF) if its quantifiers appear at the beginning of the formula:
is_pnf :: Formula -> Bool
is_pnf (Forall _ phi) = is_pnf phi
is_pnf (Exists _ phi) = is_pnf phi
is_pnf phi = qf phi
-- check whether the formula is quantifier-free
qf :: Formula -> Bool
qf (Rel _ _) = True
qf (Not phi) = qf phi
qf (And phi psi) = qf phi && qf psi
qf (Or phi psi) = qf phi && qf psi
qf (Implies phi psi) = qf phi && qf psi
qf (Iff phi psi) = qf phi && qf psi
qf _ = False
Write a function that transforms every formula of first-order logic into a logically equivalent formula in PNF.
Hint: Apply NNF first. Then pull the quantifiers outside inside-out (i.e., starting from the innermost formulas) using the following equivalences:
formula not implementedSimilar equivalences hold for extracting a quantifier on the right, and for the existential quantifier (eight equivalences in total). The side condition inline_formula not implemented requires that inline_formula not implemented is fresh in inline_formula not implemented. If inline_formula not implemented is not fresh in inline_formula not implemented, then:
We need to find a variable inline_formula not implemented which is fresh in inline_formula not implemented and inline_formula not implemented. We can use
freshVariant
for this.We need to replace every free occurrence of inline_formula not implemented in inline_formula not implemented with inline_formula not implemented. We can use
rename
for this.
TODO: discuss optimisations pertaining pulling two quantifiers at once when applicable.
pnf :: Formula -> Formula
pnf = todo
Tests:
pnfProp = is_pnf . pnf
-- quickCheck pnfProp
Runtime setup (IGNORE ME)
stack install QuickCheck