twee-2.0: src/Twee/Term.hs
-- Terms and substitutions, implemented using flatterms.
-- This module implements the usual term manipulation stuff
-- (matching, unification, etc.) on top of the primitives
-- in Twee.Term.Core.
{-# LANGUAGE BangPatterns, PatternSynonyms, ViewPatterns, TypeFamilies, OverloadedStrings, ScopedTypeVariables #-}
module Twee.Term(
module Twee.Term,
-- Stuff from Twee.Term.Core.
Term, TermList, at, lenList,
isSubtermOfList, isVarOf,
pattern Empty, pattern Cons, pattern ConsSym,
pattern UnsafeCons, pattern UnsafeConsSym,
Fun, fun, fun_id, fun_value, Var(..), pattern Var, pattern App, singleton, Builder) where
import Prelude hiding (lookup)
import Twee.Term.Core
import Data.List hiding (lookup, find)
import Data.Maybe
import Data.Monoid
import Data.IntMap.Strict(IntMap)
import qualified Data.IntMap.Strict as IntMap
--------------------------------------------------------------------------------
-- A type class for builders.
--------------------------------------------------------------------------------
class Build a where
type BuildFun a
builder :: a -> Builder (BuildFun a)
instance Build (Builder f) where
type BuildFun (Builder f) = f
builder = id
instance Build (Term f) where
type BuildFun (Term f) = f
builder = emitTerm
instance Build (TermList f) where
type BuildFun (TermList f) = f
builder = emitTermList
instance Build a => Build [a] where
type BuildFun [a] = BuildFun a
{-# INLINE builder #-}
builder = mconcat . map builder
{-# INLINE build #-}
build :: Build a => a -> Term (BuildFun a)
build x =
case buildList x of
Cons t Empty -> t
{-# INLINE buildList #-}
buildList :: Build a => a -> TermList (BuildFun a)
buildList x = {-# SCC buildList #-} buildTermList (builder x)
{-# INLINE con #-}
con :: Fun f -> Builder f
con x = emitApp x mempty
{-# INLINE app #-}
app :: Build a => Fun (BuildFun a) -> a -> Builder (BuildFun a)
app f ts = emitApp f (builder ts)
var :: Var -> Builder f
var = emitVar
--------------------------------------------------------------------------------
-- Functions for substitutions.
--------------------------------------------------------------------------------
{-# INLINE listSubstList #-}
listSubstList :: Subst f -> [(Var, TermList f)]
listSubstList (Subst sub) = [(V x, t) | (x, t) <- IntMap.toList sub]
{-# INLINE listSubst #-}
listSubst :: Subst f -> [(Var, Term f)]
listSubst sub = [(x, t) | (x, Cons t Empty) <- listSubstList sub]
{-# INLINE foldSubst #-}
foldSubst :: (Var -> TermList f -> b -> b) -> b -> Subst f -> b
foldSubst op e !sub = foldr (uncurry op) e (listSubstList sub)
{-# INLINE allSubst #-}
allSubst :: (Var -> TermList f -> Bool) -> Subst f -> Bool
allSubst p = foldSubst (\x t y -> p x t && y) True
{-# INLINE forMSubst_ #-}
forMSubst_ :: Monad m => Subst f -> (Var -> TermList f -> m ()) -> m ()
forMSubst_ sub f = foldSubst (\x t m -> do { f x t; m }) (return ()) sub
{-# INLINE substDomain #-}
substDomain :: Subst f -> [Var]
substDomain (Subst sub) = map V (IntMap.keys sub)
--------------------------------------------------------------------------------
-- Substitution.
--------------------------------------------------------------------------------
class Substitution s where
type SubstFun s
evalSubst :: s -> Var -> Builder (SubstFun s)
{-# INLINE substList #-}
substList :: s -> TermList (SubstFun s) -> Builder (SubstFun s)
substList sub ts = aux ts
where
aux Empty = mempty
aux (Cons (Var x) ts) = evalSubst sub x <> aux ts
aux (Cons (App f ts) us) = app f (aux ts) <> aux us
instance (Build a, v ~ Var) => Substitution (v -> a) where
type SubstFun (v -> a) = BuildFun a
{-# INLINE evalSubst #-}
evalSubst sub x = builder (sub x)
instance Substitution (Subst f) where
type SubstFun (Subst f) = f
{-# INLINE evalSubst #-}
evalSubst sub x =
case lookupList x sub of
Nothing -> var x
Just ts -> builder ts
{-# INLINE subst #-}
subst :: Substitution s => s -> Term (SubstFun s) -> Builder (SubstFun s)
subst sub t = substList sub (singleton t)
newtype Subst f =
Subst {
unSubst :: IntMap (TermList f) }
deriving Eq
{-# INLINE substSize #-}
substSize :: Subst f -> Int
substSize (Subst sub)
| IntMap.null sub = 0
| otherwise = fst (IntMap.findMax sub) + 1
-- Look up a variable.
{-# INLINE lookupList #-}
lookupList :: Var -> Subst f -> Maybe (TermList f)
lookupList x (Subst sub) = IntMap.lookup (var_id x) sub
-- Add a new binding to a substitution.
{-# INLINE extendList #-}
extendList :: Var -> TermList f -> Subst f -> Maybe (Subst f)
extendList x !t (Subst sub) =
case IntMap.lookup (var_id x) sub of
Nothing -> Just $! Subst (IntMap.insert (var_id x) t sub)
Just u
| t == u -> Just (Subst sub)
| otherwise -> Nothing
-- Remove a binding from a substitution.
{-# INLINE retract #-}
retract :: Var -> Subst f -> Subst f
retract x (Subst sub) = Subst (IntMap.delete (var_id x) sub)
-- Add a new binding to a substitution.
-- Overwrites any existing binding.
{-# INLINE unsafeExtendList #-}
unsafeExtendList :: Var -> TermList f -> Subst f -> Subst f
unsafeExtendList x !t (Subst sub) = Subst (IntMap.insert (var_id x) t sub)
-- Composition of substitutions.
substCompose :: Substitution s => Subst (SubstFun s) -> s -> Subst (SubstFun s)
substCompose (Subst !sub1) !sub2 =
Subst (IntMap.map (buildList . substList sub2) sub1)
-- Are two substitutions compatible?
substCompatible :: Subst f -> Subst f -> Bool
substCompatible (Subst !sub1) (Subst !sub2) =
IntMap.null (IntMap.mergeWithKey f g h sub1 sub2)
where
f _ t u
| t == u = Nothing
| otherwise = Just t
g _ = IntMap.empty
h _ = IntMap.empty
-- Take the union of two substitutions, which must be compatible.
substUnion :: Subst f -> Subst f -> Subst f
substUnion (Subst !sub1) (Subst !sub2) =
Subst (IntMap.union sub1 sub2)
-- Is a substitution idempotent?
{-# INLINE idempotent #-}
idempotent :: Subst f -> Bool
idempotent !sub = allSubst (\_ t -> sub `idempotentOn` t) sub
-- Does a substitution affect a term?
{-# INLINE idempotentOn #-}
idempotentOn :: Subst f -> TermList f -> Bool
idempotentOn !sub = aux
where
aux Empty = True
aux (ConsSym App{} t) = aux t
aux (Cons (Var x) t) = isNothing (lookupList x sub) && aux t
-- Iterate a substitution to make it idempotent.
close :: TriangleSubst f -> Subst f
close (Triangle sub)
| idempotent sub = sub
| otherwise = close (Triangle (substCompose sub sub))
-- Return a substitution for canonicalising a list of terms.
canonicalise :: [TermList f] -> Subst f
canonicalise [] = emptySubst
canonicalise (t:ts) = loop emptySubst vars t ts
where
n = maximum (V 0:map boundList (t:ts))
vars =
buildTermList $
mconcat [emitVar x | x <- [V 0..n]]
loop !_ !_ !_ !_ | False = undefined
loop sub _ Empty [] = sub
loop sub vs Empty (t:ts) = loop sub vs t ts
loop sub vs (ConsSym App{} t) ts = loop sub vs t ts
loop sub vs0@(Cons v vs) (Cons (Var x) t) ts =
case extend x v sub of
Just sub -> loop sub vs t ts
Nothing -> loop sub vs0 t ts
-- The empty substitution.
{-# NOINLINE emptySubst #-}
emptySubst = Subst IntMap.empty
-- Turn a substitution list into a substitution.
flattenSubst :: [(Var, Term f)] -> Maybe (Subst f)
flattenSubst sub = matchList pat t
where
pat = buildList (map (var . fst) sub)
t = buildList (map snd sub)
--------------------------------------------------------------------------------
-- Matching.
--------------------------------------------------------------------------------
{-# INLINE match #-}
match :: Term f -> Term f -> Maybe (Subst f)
match pat t = matchList (singleton pat) (singleton t)
{-# INLINE matchIn #-}
matchIn :: Subst f -> Term f -> Term f -> Maybe (Subst f)
matchIn sub pat t = matchListIn sub (singleton pat) (singleton t)
{-# INLINE matchList #-}
matchList :: TermList f -> TermList f -> Maybe (Subst f)
matchList pat t = matchListIn emptySubst pat t
matchListIn :: Subst f -> TermList f -> TermList f -> Maybe (Subst f)
matchListIn !sub !pat !t
| lenList t < lenList pat = Nothing
| otherwise =
let loop !_ !_ !_ | False = undefined
loop sub Empty _ = Just sub
loop _ _ Empty = undefined -- implies lenList t < lenList pat
loop sub (ConsSym (App f _) pat) (ConsSym (App g _) t)
| f == g = loop sub pat t
loop sub (Cons (Var x) pat) (Cons t u) = do
sub <- extend x t sub
loop sub pat u
loop _ _ _ = Nothing
in {-# SCC match #-} loop sub pat t
--------------------------------------------------------------------------------
-- Unification.
--------------------------------------------------------------------------------
newtype TriangleSubst f = Triangle { unTriangle :: Subst f }
deriving Show
instance Substitution (TriangleSubst f) where
type SubstFun (TriangleSubst f) = f
{-# INLINE evalSubst #-}
evalSubst (Triangle sub) x =
case lookupList x sub of
Nothing -> var x
Just ts -> substList (Triangle sub) ts
-- Redefine substList to get better inlining behaviour
{-# INLINE substList #-}
substList (Triangle sub) ts = aux ts
where
aux Empty = mempty
aux (Cons (Var x) ts) = auxVar x <> aux ts
aux (Cons (App f ts) us) = app f (aux ts) <> aux us
auxVar x =
case lookupList x sub of
Nothing -> var x
Just ts -> aux ts
unify :: Term f -> Term f -> Maybe (Subst f)
unify t u = unifyList (singleton t) (singleton u)
unifyList :: TermList f -> TermList f -> Maybe (Subst f)
unifyList t u = do
sub <- unifyListTri t u
return $! close sub
unifyTri :: Term f -> Term f -> Maybe (TriangleSubst f)
unifyTri t u = unifyListTri (singleton t) (singleton u)
unifyListTri :: TermList f -> TermList f -> Maybe (TriangleSubst f)
unifyListTri !t !u = fmap Triangle ({-# SCC unify #-} loop emptySubst t u)
where
loop !_ !_ !_ | False = undefined
loop sub Empty _ = Just sub
loop _ _ Empty = error "funny term in unification"
-- could happen if input lists have different lengths,
-- or a function is used with inconsistent arities
loop sub (ConsSym (App f _) t) (ConsSym (App g _) u)
| f == g = loop sub t u
loop sub (Cons (Var x) t) (Cons u v) = do
sub <- var sub x u
loop sub t v
loop sub (Cons t u) (Cons (Var x) v) = do
sub <- var sub x t
loop sub u v
loop _ _ _ = Nothing
var sub x t =
case lookupList x sub of
Just u -> loop sub u (singleton t)
Nothing -> var1 sub x t
var1 sub x t@(Var y)
| x == y = return sub
| otherwise =
case lookup y sub of
Just t -> var1 sub x t
Nothing -> extend x t sub
var1 sub x t = do
occurs sub x (singleton t)
extend x t sub
occurs !_ !_ Empty = Just ()
occurs sub x (ConsSym App{} t) = occurs sub x t
occurs sub x (ConsSym (Var y) t)
| x == y = Nothing
| otherwise = do
occurs sub x t
case lookupList y sub of
Nothing -> Just ()
Just u -> occurs sub x u
--------------------------------------------------------------------------------
-- Miscellaneous stuff.
--------------------------------------------------------------------------------
empty :: forall f. TermList f
empty = buildList (mempty :: Builder f)
children :: Term f -> TermList f
children t =
case singleton t of
UnsafeConsSym _ ts -> ts
unpack :: TermList f -> [Term f]
unpack t = unfoldr op t
where
op Empty = Nothing
op (Cons t ts) = Just (t, ts)
instance Show (Term f) where
show (Var x) = show x
show (App f Empty) = show f
show (App f ts) = show f ++ "(" ++ intercalate "," (map show (unpack ts)) ++ ")"
instance Show (TermList f) where
show = show . unpack
instance Show (Subst f) where
show subst =
show
[ (i, t)
| i <- [0..substSize subst-1],
Just t <- [lookup (V i) subst] ]
{-# INLINE lookup #-}
lookup :: Var -> Subst f -> Maybe (Term f)
lookup x s = do
Cons t Empty <- lookupList x s
return t
{-# INLINE extend #-}
extend :: Var -> Term f -> Subst f -> Maybe (Subst f)
extend x t sub = extendList x (singleton t) sub
{-# INLINE len #-}
len :: Term f -> Int
len = lenList . singleton
{-# INLINE emitTerm #-}
emitTerm :: Term f -> Builder f
emitTerm t = emitTermList (singleton t)
-- Find the lowest-numbered variable that doesn't appear in a term.
{-# INLINE bound #-}
bound :: Term f -> Var
bound t = boundList (singleton t)
{-# INLINE boundList #-}
boundList :: TermList f -> Var
boundList t = aux (V 0) t
where
aux n Empty = n
aux n (ConsSym App{} t) = aux n t
aux n (ConsSym (Var x) t)
| x >= n = aux (succ x) t
| otherwise = aux n t
-- Check if a variable occurs in a term.
{-# INLINE occurs #-}
occurs :: Var -> Term f -> Bool
occurs x t = occursList x (singleton t)
{-# INLINE occursList #-}
occursList :: Var -> TermList f -> Bool
occursList !x = aux
where
aux Empty = False
aux (ConsSym App{} t) = aux t
aux (ConsSym (Var y) t) = x == y || aux t
{-# INLINE termListToList #-}
termListToList :: TermList f -> [Term f]
termListToList Empty = []
termListToList (Cons t ts) = t:termListToList ts
-- The empty term list.
{-# NOINLINE emptyTermList #-}
emptyTermList :: TermList f
emptyTermList = buildList (mempty :: Builder f)
{-# INLINE subtermsList #-}
subtermsList :: TermList f -> [Term f]
subtermsList t = unfoldr op t
where
op Empty = Nothing
op (ConsSym t u) = Just (t, u)
{-# INLINE subterms #-}
subterms :: Term f -> [Term f]
subterms = subtermsList . singleton
{-# INLINE properSubterms #-}
properSubterms :: Term f -> [Term f]
properSubterms = subtermsList . children
isApp :: Term f -> Bool
isApp App{} = True
isApp _ = False
isVar :: Term f -> Bool
isVar Var{} = True
isVar _ = False
isInstanceOf :: Term f -> Term f -> Bool
t `isInstanceOf` pat = isJust (match pat t)
isVariantOf :: Term f -> Term f -> Bool
t `isVariantOf` u = t `isInstanceOf` u && u `isInstanceOf` t
isSubtermOf :: Term f -> Term f -> Bool
t `isSubtermOf` u = t `isSubtermOfList` singleton u
mapFun :: (Fun f -> Fun g) -> Term f -> Builder g
mapFun f = mapFunList f . singleton
mapFunList :: (Fun f -> Fun g) -> TermList f -> Builder g
mapFunList f ts = aux ts
where
aux Empty = mempty
aux (Cons (Var x) ts) = var x `mappend` aux ts
aux (Cons (App ff ts) us) = app (f ff) (aux ts) `mappend` aux us
{-# INLINE replacePosition #-}
replacePosition :: (Build a, BuildFun a ~ f) => Int -> a -> TermList f -> Builder f
replacePosition n !x = aux n
where
aux !_ !_ | False = undefined
aux _ Empty = mempty
aux 0 (Cons _ t) = builder x `mappend` builder t
aux n (Cons (Var x) t) = var x `mappend` aux (n-1) t
aux n (Cons t@(App f ts) u)
| n < len t =
app f (aux (n-1) ts) `mappend` builder u
| otherwise =
builder t `mappend` aux (n-len t) u
{-# INLINE replacePositionSub #-}
replacePositionSub :: (Substitution sub, SubstFun sub ~ f) => sub -> Int -> TermList f -> TermList f -> Builder f
replacePositionSub sub n !x = aux n
where
aux !_ !_ | False = undefined
aux _ Empty = mempty
aux n (Cons t u)
| n < len t = inside n t `mappend` outside u
| otherwise = outside (singleton t) `mappend` aux (n-len t) u
inside 0 _ = outside x
inside n (App f ts) = app f (aux (n-1) ts)
inside _ _ = undefined -- implies n >= len t
outside t = substList sub t
-- Convert a position in a term into a path.
positionToPath :: Term f -> Int -> [Int]
positionToPath t n = term t n
where
term _ 0 = []
term t n = list 0 (children t) (n-1)
list _ Empty _ = error "bad position"
list k (Cons t u) n
| n < len t = k:term t n
| otherwise = list (k+1) u (n-len t)
-- Convert a path in a term into a position.
pathToPosition :: Term f -> [Int] -> Int
pathToPosition t ns = term 0 t ns
where
term k _ [] = k
term k t (n:ns) = list (k+1) (children t) n ns
list _ Empty _ _ = error "bad path"
list k (Cons t _) 0 ns = term k t ns
list k (Cons t u) n ns =
list (k+len t) u (n-1) ns
pattern F :: f -> Fun f
pattern F x <- (fun_value -> x)
(<<) :: Ord f => Fun f -> Fun f -> Bool
f << g = fun_value f < fun_value g