logic-classes-0.44: Data/Logic/Classes/FirstOrder.hs
{-# LANGUAGE DeriveDataTypeable, FlexibleContexts, FlexibleInstances, FunctionalDependencies,
MultiParamTypeClasses, RankNTypes, ScopedTypeVariables, TemplateHaskell, UndecidableInstances #-}
module Data.Logic.Classes.FirstOrder
( FirstOrderFormula(..)
, Quant(..)
, Predicate(..)
, quant
, (!), (?)
, quant'
, for_all'
, exists'
, freeVars
, withUnivQuants
, quantVars
, allVars
, univquant_free_vars
, substitute
, substitutePairs
, convertFOF
, toPropositional
, disj
, conj
, showFirstOrder
, prettyFirstOrder
) where
import Data.Function (on)
import Data.Generics (Data, Typeable)
import Data.List (intercalate, intersperse)
import Data.Logic.Classes.Arity
import Data.Logic.Classes.Boolean
import Data.Logic.Classes.Logic
import Data.Logic.Classes.Pred
import Data.Logic.Classes.Propositional
import Data.Logic.Classes.Term
import Data.Monoid (mappend)
import Data.SafeCopy (base, deriveSafeCopy)
import qualified Data.Set as S
import Happstack.Data (deriveNewData)
import Text.PrettyPrint (Doc, (<>), (<+>), text, empty, parens, hcat, nest)
-- |The 'FirstOrderFormula' type class. Minimal implementation:
-- @for_all, exists, foldFirstOrder, foldTerm, (.=.), pApp0-pApp7, fApp, var@. The
-- functional dependencies are necessary here so we can write
-- functions that don't fix all of the type parameters. For example,
-- without them the univquant_free_vars function gives the error @No
-- instance for (FirstOrderFormula Formula term V p f)@ because the
-- function doesn't mention the Term type.
class ( Term term v f
, Pred p term formula
, Logic formula -- Basic logic operations
, Data formula -- Allows us to use Data.Generics functions on formulas
, Show v
, Data p
, Boolean p -- To implement true and false below
, Arity p -- To decide how many arguments
, Eq p -- Required during resolution
, Ord p
, Show p
-- , Pretty p
, Ord f
, Show f
) => FirstOrderFormula formula term v p f
| formula -> term
, formula -> v
, formula -> p
, formula -> f
, term -> p where
-- | Universal quantification - for all x (formula x)
for_all :: v -> formula -> formula
-- | Existential quantification - there exists x such that (formula x)
exists :: v -> formula -> formula
-- | A fold function similar to the one in 'PropositionalFormula'
-- but extended to cover both the existing formula types and the
-- ones introduced here. @foldFirstOrder (.~.) quant binOp infixPred pApp@
-- is a no op. The argument order is taken from Logic-TPTP.
foldFirstOrder :: (Quant -> v -> formula -> r)
-> (Combine formula -> r)
-> (Predicate p term -> r)
-> formula
-> r
zipFirstOrder :: (Quant -> v -> formula -> Quant -> v -> formula -> Maybe r)
-> (Combine formula -> Combine formula -> Maybe r)
-> (Predicate p term -> Predicate p term -> Maybe r)
-> formula -> formula -> Maybe r
-- |A temporary type used in the fold method to represent the
-- combination of a predicate and its arguments. This reduces the
-- number of arguments to foldFirstOrder and makes it easier to manage the
-- mapping of the different instances to the class methods.
data Predicate p term
= Equal term term
| NotEqual term term
| Constant Bool
| Apply p [term]
deriving (Eq, Ord, Show, Read, Data, Typeable)
-- |for_all with a list of variables, for backwards compatibility.
for_all' :: FirstOrderFormula formula term v p f => [v] -> formula -> formula
for_all' vs f = foldr for_all f vs
-- |exists with a list of variables, for backwards compatibility.
exists' :: FirstOrderFormula formula term v p f => [v] -> formula -> formula
exists' vs f = foldr for_all f vs
-- | Functions to disjunct or conjunct a list.
disj :: FirstOrderFormula formula term v p f => [formula] -> formula
disj [] = false
disj [x] = x
disj (x:xs) = x .|. disj xs
conj :: FirstOrderFormula formula term v p f => [formula] -> formula
conj [] = true
conj [x] = x
conj (x:xs) = x .&. conj xs
-- | Helper function for building folds.
quant :: FirstOrderFormula formula term v p f =>
Quant -> v -> formula -> formula
quant All v f = for_all v f
quant Exists v f = exists v f
-- |Legacy version of quant from when we supported lists of quantified
-- variables. It also has the virtue of eliding quantifications with
-- empty variable lists (by calling for_all' and exists'.)
quant' :: FirstOrderFormula formula term v p f =>
Quant -> [v] -> formula -> formula
quant' All = for_all'
quant' Exists = exists'
-- |The 'Quant' and 'InfixPred' types, like the BinOp type in
-- 'Data.Logic.Propositional', could be additional parameters to the type
-- class, but it would add additional complexity with unclear
-- benefits.
data Quant = All | Exists deriving (Eq,Ord,Show,Read,Data,Typeable,Enum,Bounded)
-- |Find the free (unquantified) variables in a formula.
freeVars :: FirstOrderFormula formula term v p f => formula -> S.Set v
freeVars f =
foldFirstOrder
(\_ v x -> S.delete v (freeVars x))
(\ cm ->
case cm of
BinOp x _ y -> (mappend `on` freeVars) x y
(:~:) f' -> freeVars f')
(\ pa -> case pa of
Constant _ -> S.empty
Equal t1 t2 -> S.union (freeVarsOfTerm t1) (freeVarsOfTerm t2)
NotEqual t1 t2 -> S.union (freeVarsOfTerm t1) (freeVarsOfTerm t2)
Apply _ ts -> S.unions (fmap freeVarsOfTerm ts))
f
where
freeVarsOfTerm = foldTerm S.singleton (\ _ args -> S.unions (fmap freeVarsOfTerm args))
-- | Examine the formula to find the list of outermost universally
-- quantified variables, and call a function with that list and the
-- formula after the quantifiers are removed.
withUnivQuants :: FirstOrderFormula formula term v p f => ([v] -> formula -> r) -> formula -> r
withUnivQuants fn formula =
doFormula [] formula
where
doFormula vs f =
foldFirstOrder
(doQuant vs)
(\ _ -> fn (reverse vs) f)
(\ _ -> fn (reverse vs) f)
f
doQuant vs All v f = doFormula (v : vs) f
doQuant vs Exists v f = fn (reverse vs) (exists v f)
{-
withImplication :: FirstOrderFormula formula term v p f => r -> (formula -> formula -> r) -> formula -> r
withImplication def fn formula =
foldFirstOrder (\ _ _ _ -> def) c (\ _ -> def) formula
where
c (BinOp a (:=>:) b) = fn a b
c _ = def
-}
-- |Find the variables that are quantified in a formula
quantVars :: FirstOrderFormula formula term v p f => formula -> S.Set v
quantVars =
foldFirstOrder
(\ _ v x -> S.insert v (quantVars x))
(\ cm ->
case cm of
BinOp x _ y -> (mappend `on` quantVars) x y
((:~:) f) -> quantVars f)
(\ _ -> S.empty)
-- |Find the free and quantified variables in a formula.
allVars :: FirstOrderFormula formula term v p f => formula -> S.Set v
allVars f =
foldFirstOrder
(\_ v x -> S.insert v (allVars x))
(\ cm ->
case cm of
BinOp x _ y -> (mappend `on` allVars) x y
(:~:) f' -> freeVars f')
(\ pa -> case pa of
Equal t1 t2 -> S.union (allVarsOfTerm t1) (allVarsOfTerm t2)
NotEqual t1 t2 -> S.union (allVarsOfTerm t1) (allVarsOfTerm t2)
Constant _ -> S.empty
Apply _ ts -> S.unions (fmap allVarsOfTerm ts))
f
where
allVarsOfTerm = foldTerm S.singleton (\ _ args -> S.unions (fmap allVarsOfTerm args))
-- | Universally quantify all free variables in the formula (Name comes from TPTP)
univquant_free_vars :: FirstOrderFormula formula term v p f => formula -> formula
univquant_free_vars cnf' =
if S.null free then cnf' else foldr for_all cnf' (S.toList free)
where free = freeVars cnf'
-- |Replace each free occurrence of variable old with term new.
substitute :: FirstOrderFormula formula term v p f => v -> term -> formula -> formula
substitute old new formula =
foldTerm (\ new' -> if old == new' then formula else substitute' formula)
(\ _ _ -> substitute' formula)
new
where
substitute' =
foldFirstOrder -- If the old variable appears in a quantifier
-- we can stop doing the substitution.
(\ q v f' -> quant q v (if old == v then f' else substitute' f'))
(\ cm -> case cm of
((:~:) f') -> combine ((:~:) (substitute' f'))
(BinOp f1 op f2) -> combine (BinOp (substitute' f1) op (substitute' f2)))
(\ pa -> case pa of
Equal t1 t2 -> (st t1) .=. (st t2)
NotEqual t1 t2 -> (st t1) .!=. (st t2)
Constant x -> pApp0 (fromBool x)
Apply p ts -> pApp p (map st ts))
st t = foldTerm sv (\ func ts -> fApp func (map st ts)) t
sv v = if v == old then new else var v
substitutePairs :: (FirstOrderFormula formula term v p f) => [(v, term)] -> formula -> formula
substitutePairs pairs formula = foldr (\ (old, new) f -> substitute old new f) formula pairs
-- |Convert any instance of a first order logic expression to any other.
convertFOF :: forall formula1 term1 v1 p1 f1 formula2 term2 v2 p2 f2.
(FirstOrderFormula formula1 term1 v1 p1 f1,
FirstOrderFormula formula2 term2 v2 p2 f2) =>
(v1 -> v2) -> (p1 -> p2) -> (f1 -> f2) -> formula1 -> formula2
convertFOF convertV convertP convertF formula =
foldFirstOrder q c p formula
where
convert' = convertFOF convertV convertP convertF
convertTerm' = convertTerm convertV convertF
q x v f = quant x (convertV v) (convert' f)
c (BinOp f1 op f2) = combine (BinOp (convert' f1) op (convert' f2))
c ((:~:) f) = combine ((:~:) (convert' f))
p (Equal t1 t2) = (convertTerm' t1) .=. (convertTerm' t2)
p (NotEqual t1 t2) = (convertTerm' t1) .!=. (convertTerm' t2)
p (Apply x ts) = pApp (convertP x) (map convertTerm' ts)
p (Constant x) = pApp (fromBool x) []
-- |Names for for_all and exists inspired by the conventions of the
-- TPTP project.
(!) :: FirstOrderFormula formula term v p f => v -> formula -> formula
(!) = for_all
(?) :: FirstOrderFormula formula term v p f => v -> formula -> formula
(?) = exists
-- |Try to convert a first order logic formula to propositional. This
-- will return Nothing if there are any quantifiers, or if it runs
-- into an atom that it is unable to convert.
toPropositional :: forall formula1 term v p f formula2 atom.
(FirstOrderFormula formula1 term v p f,
PropositionalFormula formula2 atom) =>
(formula1 -> formula2) -> formula1 -> formula2
toPropositional convertAtom formula =
foldFirstOrder q c p formula
where
convert' = toPropositional convertAtom
q _ _ _ = error "toPropositional: invalid argument"
c (BinOp f1 op f2) = combine (BinOp (convert' f1) op (convert' f2))
c ((:~:) f) = combine ((:~:) (convert' f))
p _ = convertAtom formula
-- | Display a formula in a format that can be read into the interpreter.
showFirstOrder :: forall formula term v p f. (FirstOrderFormula formula term v p f, Show v, Show p, Show f) =>
formula -> String
showFirstOrder formula =
foldFirstOrder q c a formula
where
q All v f = "(for_all " ++ show v ++ " " ++ showFirstOrder f ++ ")"
q Exists v f = "(exists " ++ show v ++ " " ++ showFirstOrder f ++ ")"
c (BinOp f1 op f2) = "(" ++ parenForm f1 ++ " " ++ showCombine op ++ " " ++ parenForm f2 ++ ")"
c ((:~:) f) = "((.~.) " ++ showFirstOrder f ++ ")"
a :: Predicate p term -> String
a (Equal t1 t2) =
"(" ++ parenTerm t1 ++ " .=. " ++ parenTerm t2 ++ ")"
a (NotEqual t1 t2) =
"(" ++ parenTerm t1 ++ " .!=. " ++ parenTerm t2 ++ ")"
a (Constant x) = "pApp' (Constant " ++ show x ++ ")"
a (Apply p ts) = "(pApp" ++ show (length ts) ++ " (" ++ show p ++ ") (" ++ intercalate ") (" (map showTerm ts) ++ "))"
parenForm x = "(" ++ showFirstOrder x ++ ")"
parenTerm :: term -> String
parenTerm x = "(" ++ showTerm x ++ ")"
showCombine (:<=>:) = ".<=>."
showCombine (:=>:) = ".=>."
showCombine (:&:) = ".&."
showCombine (:|:) = ".|."
prettyFirstOrder :: forall formula term v p f. (FirstOrderFormula formula term v p f) =>
(v -> Doc)
-> (p -> Doc)
-> (f -> Doc)
-> Int
-> formula
-> Doc
prettyFirstOrder pv pp pf prec formula =
foldFirstOrder
(\ qop v f -> parensIf (prec > 1) $ prettyQuant qop <> pv v <+> prettyFirstOrder pv pp pf 1 f)
(\ cm ->
case cm of
(BinOp f1 op f2) ->
case op of
(:=>:) -> parensIf (prec > 2) $ (prettyFirstOrder pv pp pf 2 f1 <+> formOp op <+> prettyFirstOrder pv pp pf 2 f2)
(:<=>:) -> parensIf (prec > 2) $ (prettyFirstOrder pv pp pf 2 f1 <+> formOp op <+> prettyFirstOrder pv pp pf 2 f2)
(:&:) -> parensIf (prec > 3) $ (prettyFirstOrder pv pp pf 3 f1 <+> formOp op <+> prettyFirstOrder pv pp pf 3 f2)
(:|:) -> parensIf {-(prec > 4)-} True $ (prettyFirstOrder pv pp pf 4 f1 <+> formOp op <+> prettyFirstOrder pv pp pf 4 f2)
((:~:) f) -> text {-"¬"-} "~" <> prettyFirstOrder pv pp pf 5 f)
pr
formula
where
pr :: Predicate p term -> Doc
pr (Constant x) = text (show x)
pr (Equal t1 t2) = parensIf (prec > 6) (prettyTerm pv pf t1 <+> text "=" <+> prettyTerm pv pf t2)
pr (NotEqual t1 t2) = parensIf (prec > 6) (prettyTerm pv pf t1 <+> text "!=" <+> prettyTerm pv pf t2)
pr (Apply p ts) =
pp p <> case ts of
[] -> empty
_ -> parens (hcat (intersperse (text ",") (map (prettyTerm pv pf) ts)))
parensIf False = id
parensIf _ = parens . nest 1
prettyQuant All = text {-"∀"-} "!"
prettyQuant Exists = text {-"∃"-} "?"
formOp (:<=>:) = text "<=>"
formOp (:=>:) = text "=>"
formOp (:&:) = text "&"
formOp (:|:) = text "|"
$(deriveSafeCopy 1 'base ''Quant)
$(deriveSafeCopy 1 'base ''Predicate)
$(deriveNewData [''Quant])
$(deriveNewData [''Predicate])