atp-0.1.0.0: src/ATP/FirstOrder/Occurrence.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE FlexibleInstances #-}
{-|
Module : ATP.FirstOrder.Occurrence
Description : Occurrences of variables in first-order expressions.
Copyright : (c) Evgenii Kotelnikov, 2019-2021
License : GPL-3
Maintainer : evgeny.kotelnikov@gmail.com
Stability : experimental
-}
module ATP.FirstOrder.Occurrence (
-- * Occurrence
FirstOrder(..),
closed,
close,
unprefix
) where
import Prelude hiding (lookup)
import Control.Monad (liftM2, zipWithM, when)
import Data.Function (on)
#if !MIN_VERSION_base(4, 11, 0)
import Data.Semigroup (Semigroup(..))
#endif
import qualified Data.Set as S (insert, delete, member, null, singleton)
import Data.Set (Set)
import ATP.FirstOrder.Core
import ATP.FirstOrder.Alpha
-- $setup
-- >>> :load Property.Generators
-- * Occurrence
infix 5 ~=
-- | A class of first-order expressions, i.e. expressions that might contain
-- variables. @t'Formula'@s, @'Literal'@s and @'Term'@s are first-order expressions.
--
-- A variable can occur both as free and bound, therefore
-- @'free' e@ and @'bound' e@ are not necessarily disjoint and
-- @v `freeIn` e@ and @v `boundIn` e@ are not necessarily musually exclusive.
--
-- @'vars'@, @'free'@ and @'bound'@ are connected by the following property.
--
-- > free e <> bound e == vars e
--
-- @'occursIn'@, @'freeIn'@ and @'boundIn'@ are connected by the following property.
--
-- > v `freeIn` e || v `boundIn` e == v `occursIn` e
--
class FirstOrder e where
-- | The set of all variables that occur anywhere in the given expression.
vars :: e -> Set Var
-- | The set of variables that occur freely in the given expression,
-- i.e. are not bound by any quantifier inside the expression.
free :: e -> Set Var
-- | The set of variables that occur bound in the given expression,
-- i.e. are bound by a quantifier inside the expression.
bound :: e -> Set Var
-- | Check whether the given variable occurs anywhere in the given expression.
occursIn :: Var -> e -> Bool
v `occursIn` e = v `S.member` vars e
-- | Check whether the given variable occurs freely anywhere in the given expression.
freeIn :: Var -> e -> Bool
v `freeIn` e = v `S.member` free e
-- | Check whether the given variable occurs bound anywhere in the given expression.
boundIn :: Var -> e -> Bool
v `boundIn` e = v `S.member` bound e
-- | Check whether the given expression is ground, i.e. does not contain
-- any variables.
--
-- Note that /ground formula/ is sometimes understood as /formula that does/
-- /not contain any free variables/. In this library such formulas are called
-- @'closed'@.
ground :: e -> Bool
ground = S.null . vars
-- | Check whether two given expressions are alpha-equivalent, that is
-- equivalent up to renaming of variables.
--
-- '(~=)' is an equivalence relation.
--
-- __Symmetry__
--
-- > e ~= e
--
-- __Reflexivity__
--
-- > a ~= b == b ~= a
--
-- __Transitivity__
--
-- > a ~= b && b ~= c ==> a ~= c
--
(~=) :: e -> e -> Bool
a ~= b = evalAlpha (a ?= b)
-- | A helper function calculating alpha-equivalence using the 'Alpha' monad stack.
(?=) :: e -> e -> Alpha Bool
alpha :: MonadAlpha m => e -> AlphaT m e
instance FirstOrder LogicalExpression where
vars = \case
Clause c -> vars c
Formula f -> vars f
free = \case
Clause c -> free c
Formula f -> free f
bound = \case
Clause c -> bound c
Formula f -> bound f
occursIn v = \case
Clause c -> occursIn v c
Formula f -> occursIn v f
freeIn v = \case
Clause c -> freeIn v c
Formula f -> freeIn v f
boundIn v = \case
Clause c -> boundIn v c
Formula f -> boundIn v f
ground = \case
Clause c -> ground c
Formula f -> ground f
Clause c ?= Clause c' = c ?= c'
Formula f ?= Formula f' = f ?= f'
_ ?= _ = return False
alpha = \case
Clause c -> Clause <$> alpha c
Formula f -> Formula <$> alpha f
instance FirstOrder Formula where
vars = \case
Atomic l -> vars l
Negate f -> vars f
Connected _ f g -> vars f <> vars g
Quantified _ _ f -> vars f
free = \case
Atomic l -> free l
Negate f -> free f
Connected _ f g -> free f <> free g
Quantified _ v f -> S.delete v (free f)
bound = \case
Atomic{} -> mempty
Negate f -> bound f
Connected _ f g -> bound f <> bound g
Quantified _ v f -> if v `freeIn` f then S.insert v (bound f) else bound f
Atomic l ?= Atomic l' = l ?= l'
Negate f ?= Negate f' = f ?= f'
Connected c f g ?= Connected c' f' g' | c == c' = liftM2 (&&) (f ?= f') (g ?= g')
Quantified q v f ?= Quantified q' v' f' | q == q' = enter v v' (f ?= f')
_ ?= _ = return False
alpha = \case
Atomic l -> Atomic <$> alpha l
Negate f -> Negate <$> alpha f
Connected c f g -> Connected c <$> alpha f <*> alpha g
Quantified q v f -> do
v' <- binding v
f' <- enter v v' (alpha f)
return (Quantified q v' f')
instance FirstOrder Clause where
vars = vars . getLiterals
free = vars
bound _ = mempty
(~=) = (~=) `on` getLiterals
(?=) = (?=) `on` getLiterals
alpha = fmap Literals . traverse alpha . getLiterals
instance FirstOrder e => FirstOrder (Signed e) where
vars = vars . unsign
free = free . unsign
bound = bound . unsign
occursIn v = occursIn v . unsign
freeIn v = freeIn v . unsign
boundIn v = boundIn v . unsign
ground = ground . unsign
(~=) = (~=) `on` unsign
(?=) = (?=) `on` unsign
alpha = traverse alpha
instance FirstOrder Literal where
vars = \case
Propositional{} -> mempty
Predicate _ ts -> vars ts
Equality a b -> vars a <> vars b
free = vars
bound _ = mempty
Propositional b ?= Propositional b' = return (b == b')
Predicate p ts ?= Predicate p' ts' | p == p' = ts ?= ts'
Equality a b ?= Equality a' b' = liftM2 (&&) (a ?= a') (b ?= b')
_ ?= _ = return False
alpha = \case
Propositional b -> return (Propositional b)
Predicate p ts -> Predicate p <$> traverse alpha ts
Equality a b -> Equality <$> alpha a <*> alpha b
instance FirstOrder Term where
vars = \case
Variable v -> vars v
Function _ ts -> vars ts
free = vars
bound _ = mempty
Variable v ?= Variable v' = v ?= v'
Function f ts ?= Function f' ts' | f == f' = ts ?= ts'
_ ?= _ = return False
alpha = \case
Function f ts -> Function f <$> traverse alpha ts
Variable v -> Variable <$> alpha v
instance FirstOrder Var where
vars = S.singleton
free = vars
bound _ = mempty
v ?= v' = lookup v >>= \case
Just w' -> return (w' == v')
Nothing -> do
vs <- scope
let f = v' `notElem` vs
when f (share v v')
return f
alpha v = lookup v >>= \case
Just v' -> occurrence v'
Nothing -> do { v' <- binding v; share v v'; return v' }
instance FirstOrder e => FirstOrder [e] where
vars = mconcat . fmap vars
free = vars
bound = mempty
es ?= es' | length es == length es' = and <$> zipWithM (?=) es es'
_ ?= _ = return False
alpha = traverse alpha
-- | Check whether the given formula is closed, i.e. does not contain any free
-- variables.
closed :: Formula -> Bool
closed = S.null . free
-- | Make any given formula closed by adding a top-level universal quantifier
-- for each of its free variables.
--
-- @'close'@ and @'unprefix'@ are connected by the following property.
--
-- prop> unprefix (close f) === f
--
close :: Formula -> Formula
close f = foldl (flip $ Quantified Forall) f (free f)
-- | Remove the top-level quantifiers.
--
-- >>> unprefix (Quantified Forall 1 (Quantified Exists 2 (Atomic (Equality (Variable 1) (Variable 2)))))
-- Atomic (Equality (Variable 1) (Variable 2))
--
unprefix :: Formula -> Formula
unprefix = \case
Quantified _ _ f -> unprefix f
f -> f