ideas-0.5.8: src/Common/Transformation.hs
{-# LANGUAGE ExistentialQuantification #-}
-----------------------------------------------------------------------------
-- Copyright 2009, Open Universiteit Nederland. This file is distributed
-- under the terms of the GNU General Public License. For more information,
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer : bastiaan.heeren@ou.nl
-- Stability : provisional
-- Portability : portable (depends on ghc)
--
-- This module defines transformations. Given a term, a transformation returns a list of
-- results (often a singleton list or the empty list). A transformation can be parameterized
-- with one or more arguments. A rule is in essence just a transformation with a name (which
-- should be unique). Both transformations and rules can be lifted to work on more complex domains.
--
-----------------------------------------------------------------------------
module Common.Transformation
( -- * Transformations
Transformation(RewriteRule), makeTrans, makeTransList
, inverseTrans, getPatternPair
-- * Arguments
, ArgDescr(..), defaultArgDescr, Argument(..)
, supply1, supply2, supply3, supplyLabeled1, supplyLabeled2, supplyLabeled3, supplyWith1
, hasArguments, expectedArguments, getDescriptors, useArguments
-- * Rules
, Rule, name, isMinorRule, isMajorRule, isBuggyRule, isRewriteRule
, ruleGroups, addRuleToGroup
, rule, ruleList, ruleListF
, makeRule, makeRuleList, makeSimpleRule, makeSimpleRuleList
, idRule, emptyRule, minorRule, buggyRule, doBefore, doAfter
, transformations, getRewriteRules
-- * Lifting
, LiftPair, liftPairGet, liftPairSet, liftPairChange, makeLiftPair, Lift(..)
, ruleOnce, ruleOnce2, ruleMulti, ruleMulti2, ruleSomewhere
-- * QuickCheck
, checkRule, checkRuleSmart
) where
import Common.Apply
import Common.Rewriting
import Common.Traversable
import Common.Uniplate (Uniplate, somewhereM)
import Common.Utils
import Control.Monad
import Data.Char
import Data.List
import Data.Maybe
import Data.Ratio
import Test.QuickCheck hiding (arguments)
-----------------------------------------------------------
--- Transformations
-- | Abstract data type for representing transformations
data Transformation a
= Function String (a -> [a])
| RewriteRule (RewriteRule a)
| forall b . Abstraction (ArgumentList b) (a -> Maybe b) (b -> Transformation a)
| forall b . Lift (LiftPair b a) (Transformation b)
instance Apply Transformation where
applyAll (Function _ f) = f
applyAll (RewriteRule r) = rewriteM r
applyAll (Abstraction _ f g) = \a -> maybe [] (\b -> applyAll (g b) a) (f a)
applyAll (Lift lp t ) = \b -> maybe [] (map (\new -> liftPairSet lp new b) . applyAll t) (liftPairGet lp b)
-- | Turn a function (which returns its result in the Maybe monad) into a transformation
makeTrans :: String -> (a -> Maybe a) -> Transformation a
makeTrans s f = makeTransList s (maybe [] return . f)
-- | Turn a function (which returns a list of results) into a transformation
makeTransList :: String -> (a -> [a]) -> Transformation a
makeTransList = Function
-- | Return the inverse of a transformation. Only transformation that are constructed with (|-)
-- can be inversed
inverseTrans :: Transformation a -> Maybe (Transformation a)
inverseTrans trans =
case trans of
RewriteRule r -> fmap RewriteRule (inverse r)
Lift lp t -> fmap (Lift lp) (inverseTrans t)
_ -> Nothing
getPatternPair :: a -> Transformation a -> Maybe (a, a)
getPatternPair _ (RewriteRule r) = let a :~> b = rulePair r 0 in Just (a, b)
getPatternPair a (Lift lp t) = do
let f t = liftPairSet lp t a
b <- liftPairGet lp a
(x, y) <- getPatternPair b t
return (f x, f y)
getPatternPair _ _ = Nothing
-----------------------------------------------------------
--- Arguments
-- | A data type for describing an argument of a parameterized transformation
data ArgDescr a = ArgDescr
{ labelArgument :: String -- ^ Label that is shown to the user when asked to supply the argument
, defaultArgument :: Maybe a -- ^ Default value that can be used
, parseArgument :: String -> Maybe a -- ^ A parser
, showArgument :: a -> String -- ^ A pretty-printer
, genArgument :: Gen a -- ^ An arbitrary argument generator
}
-- | Constructor function for an argument descriptor that uses the Show and Read type classes
defaultArgDescr :: (Show a, Read a, Arbitrary a) => String -> ArgDescr a
defaultArgDescr descr = ArgDescr descr Nothing parse show arbitrary
where
parse s = case reads s of
[(a, xs)] | all isSpace xs -> return a
_ -> Nothing
-- | A type class for types which have an argument descriptor
class Arbitrary a => Argument a where
makeArgDescr :: String -> ArgDescr a -- ^ The first argument is the label of the argument descriptor
instance Argument Int where
makeArgDescr = defaultArgDescr
instance Argument Integer where
makeArgDescr = defaultArgDescr
instance (Integral a, Arbitrary a) => Argument (Ratio a) where
makeArgDescr = ratioArgDescr
-- | Parameterization with one argument using a default label
supply1 :: Argument x =>
(a -> Maybe x) -> (x -> Transformation a) -> Transformation a
supply1 = supplyLabeled1 "argument 1"
-- | Parameterization with two arguments using default labels
supply2 :: (Argument x, Argument y) =>
(a -> Maybe (x, y)) -> (x -> y -> Transformation a) -> Transformation a
supply2 = supplyLabeled2 ("argument 1", "argument 2")
-- | Parameterization with three arguments using default labels
supply3 :: (Argument x, Argument y, Argument z) =>
(a -> Maybe (x, y, z)) -> (x -> y -> z -> Transformation a) -> Transformation a
supply3 = supplyLabeled3 ("argument 1", "argument 2", "argument 3")
-- | Parameterization with one argument using the provided label
supplyLabeled1 :: Argument x
=> String -> (a -> Maybe x)
-> (x -> Transformation a) -> Transformation a
supplyLabeled1 s f t =
let args = cons (makeArgDescr s) nil
nest a = (a, ())
in Abstraction args (fmap nest . f) (\(a, ()) -> t a)
supplyWith1 :: ArgDescr x -> (a -> Maybe x)
-> (x -> Transformation a) -> Transformation a
supplyWith1 descr f t =
let args = cons descr nil
nest a = (a, ())
in Abstraction args (fmap nest . f) (\(a, ()) -> t a)
-- | Parameterization with two arguments using the provided labels
supplyLabeled2 :: (Argument x, Argument y)
=> (String, String) -> (a -> Maybe (x, y))
-> (x -> y -> Transformation a) -> Transformation a
supplyLabeled2 (s1, s2) f t =
let args = cons (makeArgDescr s1) (cons (makeArgDescr s2) nil)
nest (a, b) = (a, (b, ()))
in Abstraction args (fmap nest . f) (\(a, (b, ())) -> t a b)
-- | Parameterization with three arguments using the provided labels
supplyLabeled3 :: (Argument x, Argument y, Argument z)
=> (String, String, String) -> (a -> Maybe (x, y, z))
-> (x -> y -> z -> Transformation a) -> Transformation a
supplyLabeled3 (s1, s2, s3) f t =
let args = cons (makeArgDescr s1) (cons (makeArgDescr s2) (cons (makeArgDescr s3) nil))
nest (a, b, c) = (a, (b, (c, ())))
in Abstraction args (fmap nest . f) (\(a, (b, (c, ()))) -> t a b c)
-- | Checks whether a rule is parameterized
hasArguments :: Rule a -> Bool
hasArguments = not . null . getDescriptors
-- | Returns a list of argument descriptors
getDescriptors :: Rule a -> [Some ArgDescr]
getDescriptors rule =
case transformations rule of
[Abstraction args _ _] -> someArguments args
[Lift _ t] -> getDescriptors $ rule
{ transformations = [t]
, doBeforeHook = id
, doAfterHook = id
}
_ -> []
-- | Returns a list of pretty-printed expected arguments. Nothing indicates that there are no such arguments
expectedArguments :: Rule a -> a -> Maybe [String]
expectedArguments rule a =
case transformations rule of
[Abstraction args f _] ->
fmap (showArguments args) (f a)
[Lift lp t] -> do
b <- liftPairGet lp a
expectedArguments rule
{ transformations = [t]
, doBeforeHook = id
, doAfterHook = id
} b
_ -> Nothing
-- | Transform a rule and use a list of pretty-printed arguments. Nothing indicates that the arguments are
-- invalid (not parsable), or that the wrong number of arguments was supplied
useArguments :: [String] -> Rule a -> Maybe (Rule a)
useArguments list rule =
case transformations rule of
[t] -> do new <- make t
return rule {transformations = [new]}
_ -> Nothing
where
make :: Transformation a -> Maybe (Transformation a)
make trans =
case trans of
Abstraction args _ g -> fmap g (parseArguments args list)
Lift lp t -> fmap (Lift lp) (make t)
_ -> Nothing
-----------------------------------------------------------
--- Internal machinery for arguments
data ArgumentList a
= Nil a
| forall b c . Cons ((b, c) -> a, a -> (b, c)) (ArgDescr b) (ArgumentList c)
-- smart constructor
nil :: ArgumentList ()
nil = Nil ()
-- smart constructor (provides the isomorphism proofs)
cons :: ArgDescr a -> ArgumentList b -> ArgumentList (a, b)
cons = Cons (id, id)
showArguments :: ArgumentList a -> a -> [String]
showArguments (Nil _) _ = []
showArguments (Cons (_, f) arg list) a =
let (b, c) = f a
in showArgument arg b : showArguments list c
parseArguments :: ArgumentList a -> [String] -> Maybe a
parseArguments (Nil a) [] = Just a
parseArguments (Cons (f, _) arg list) (x:xs) = do
b <- parseArgument arg x
c <- parseArguments list xs
return $ f (b, c)
parseArguments _ _ = Nothing
someArguments :: ArgumentList a -> [Some ArgDescr]
someArguments (Nil _) = []
someArguments (Cons _ arg list) = Some arg : someArguments list
ratioArgDescr :: (Integral a, Arbitrary a) => String -> ArgDescr (Ratio a)
ratioArgDescr descr = ArgDescr descr Nothing parseRatio showRatio arbitrary
where
showRatio r = show (numerator r) ++ if denominator r == 1 then "" else '/' : show (denominator r)
parseRatio s =
let readDivOp s =
case dropWhile isSpace s of
('/':rest) -> return rest
[] -> return "1"
_ -> fail "no (/) operator"
in safeHead [ fromInteger x / fromInteger y
| (x, s1) <- reads s
, s2 <- readDivOp s1
, (y, s3) <- reads s2
, y /= 0
, all isSpace s3
]
-----------------------------------------------------------
--- Rules
-- | Abstract data type for representing rules
data Rule a = Rule
{ name :: String -- ^ Returns the name of the rule (should be unique)
, transformations :: [Transformation a]
, isBuggyRule :: Bool -- ^ Inspect whether or not the rule is buggy (unsound)
, isMinorRule :: Bool -- ^ Returns whether or not the rule is minor (i.e., an administrative step that is automatically performed by the system)
, ruleGroups :: [String]
, doBeforeHook :: a -> a -- ^ Hook to perform an action before the rule is fired
, doAfterHook :: a -> a -- ^ Hook to perform an action after the rule has been fired
}
instance Show (Rule a) where
show = name
instance Eq (Rule a) where
r1 == r2 = name r1 == name r2
instance Apply Rule where
applyAll r a = do
let b = doBeforeHook r a
t <- transformations r
c <- applyAll t b
return (doAfterHook r c)
-- | Returns whether or not the rule is major (i.e., not minor)
isMajorRule :: Rule a -> Bool
isMajorRule = not . isMinorRule
isRewriteRule :: Rule a -> Bool
isRewriteRule = all p . transformations
where
p :: Transformation a -> Bool
p (RewriteRule _) = True
p (Lift _ t) = p t
p _ = False
addRuleToGroup :: String -> Rule a -> Rule a
addRuleToGroup group r = r { ruleGroups = group : ruleGroups r }
ruleList :: Builder f a => String -> [f] -> Rule a
ruleList s = makeRuleList s . map (RewriteRule . rewriteRule s)
ruleListF :: BuilderList f a => String -> f -> Rule a
ruleListF s = makeRuleList s . map RewriteRule . rewriteRules s
rule :: Builder f a => String -> f -> Rule a
rule s = makeRule s . RewriteRule . rewriteRule s
-- | Turn a transformation into a rule: the first argument is the rule's name
makeRule :: String -> Transformation a -> Rule a
makeRule n = makeRuleList n . return
-- | Turn a list of transformations into a single rule: the first argument is the rule's name
makeRuleList :: String -> [Transformation a] -> Rule a
makeRuleList n ts = Rule n ts False False [] id id
-- | Turn a function (which returns its result in the Maybe monad) into a rule: the first argument is the rule's name
makeSimpleRule :: String -> (a -> Maybe a) -> Rule a
makeSimpleRule n = makeRule n . makeTrans n
-- | Turn a function (which returns a list of results) into a rule: the first argument is the rule's name
makeSimpleRuleList :: String -> (a -> [a]) -> Rule a
makeSimpleRuleList n = makeRule n . makeTransList n
-- | A special (minor) rule that always returns the identity
idRule :: Rule a
idRule = minorRule $ makeSimpleRule "Identity" return
-- | A special (minor) rule that is never applicable (i.e., this rule always fails)
emptyRule :: Rule a
emptyRule = minorRule $ makeSimpleRule "Empty" (const Nothing)
-- | Mark the rule as minor (by default, rules are not minor)
minorRule :: Rule a -> Rule a
minorRule r = r {isMinorRule = True}
-- | Mark the rule as buggy (by default, rules are supposed to be sound)
buggyRule :: Rule a -> Rule a
buggyRule r = r {isBuggyRule = True}
-- | Perform the function before the rule has been fired
doBefore :: (a -> a) -> Rule a -> Rule a
doBefore f r = r { doBeforeHook = f }
-- | Perform the function after the rule has been fired
doAfter :: (a -> a) -> Rule a -> Rule a
doAfter f r = r { doAfterHook = f }
getRewriteRules :: Rule a -> [(Some RewriteRule, Bool)]
getRewriteRules r = concatMap f (transformations r)
where
f :: Transformation a -> [(Some RewriteRule, Bool)]
f trans =
case trans of
RewriteRule rr -> [(Some rr, not $ isBuggyRule r)]
Lift _ t -> f t
_ -> []
-----------------------------------------------------------
--- Lifting
-- | A lift pair consists of two functions: the first to access a value in a context (this can fail,
-- hence the Maybe), the second to update the value in its context
data LiftPair a b = LiftPair
{ liftPairGet :: b -> Maybe a -- ^ Returns the accessor function of a lift pair
, liftPairSet :: a -> b -> b -- ^ Returns the update function of a lift pair
}
-- | Update a value in a context
liftPairChange :: LiftPair a b -> (a -> Maybe a) -> b -> Maybe b
liftPairChange lp f b = do
a <- liftPairGet lp b
new <- f a
return (liftPairSet lp new b)
-- | Constructor for a lift pair
makeLiftPair :: (b -> Maybe a) -> (a -> b -> b) -> LiftPair a b
makeLiftPair = LiftPair
-- | A type class for functors that can be lifted with a lift pair
class Lift f where
lift :: LiftPair a b -> f a -> f b
instance Lift Transformation where
lift = Lift
instance Lift Rule where
lift lp r = r
{ transformations = map (lift lp) (transformations r)
, doBeforeHook = liftFunction lp (doBeforeHook r)
, doAfterHook = liftFunction lp (doAfterHook r)
}
liftFunction :: LiftPair a b -> (a -> a) -> b -> b
liftFunction lp f a =
case liftPairGet lp a of
Just b -> liftPairSet lp (f b) a
Nothing -> a
-- | Lift a rule using the Once type class
ruleOnce :: Once f => Rule a -> Rule (f a)
ruleOnce r = makeSimpleRuleList (name r) $ onceM $ applyAll r
-- | Apply a rule once (in two functors)
ruleOnce2 :: (Once f, Once g) => Rule a -> Rule (f (g a))
ruleOnce2 = ruleOnce . ruleOnce
-- | Apply at multiple locations, but at least once
ruleMulti :: (Switch f, Crush f) => Rule a -> Rule (f a)
ruleMulti r = makeSimpleRuleList (name r) $ multi $ applyAll r
-- | Apply at multiple locations, but at least once (in two functors)
ruleMulti2 :: (Switch f, Crush f, Switch g, Crush g) => Rule a -> Rule (f (g a))
ruleMulti2 = ruleMulti . ruleMulti
multi :: (Switch f, Crush f) => (a -> [a]) -> f a -> [f a]
multi f a =
let g a = case f a of
[] -> [(False, a)]
xs -> zip (repeat True) xs
xs = switch (fmap g a)
p = any fst . crush
in map (fmap snd) (filter p xs)
ruleSomewhere :: Uniplate a => Rule a -> Rule a
ruleSomewhere r = makeSimpleRuleList (name r) $ somewhereM $ applyAll r
-----------------------------------------------------------
--- QuickCheck
-- | Check the soundness of a rule: the equality function is passed explicitly
checkRule :: (Arbitrary a, Show a) => (a -> a -> Bool) -> Rule a -> IO ()
checkRule eq rule =
quickCheck (propRule eq rule arbitrary)
-- | Check the soundness of a rule and use a "smart generator" for this. The smart generator
-- behaves differently on transformations constructed with a (|-), and for these transformations,
-- the left-hand side patterns are used (meta variables are instantiated with random terms)
checkRuleSmart :: Show a => (a -> a -> Bool) -> Rule a -> Gen a -> IO ()
checkRuleSmart eq rule gen =
quickCheck (propRule eq rule (smartGen rule gen))
propRule :: Show a => (a -> a -> Bool) -> Rule a -> Gen a -> Property
propRule eq rule gen =
forAll gen $ \a ->
forAll (smartApplyRule rule a) $ \ma ->
isJust ma ==> (a `eq` fromJust ma)
smartGen :: Rule a -> Gen a -> Gen a
smartGen r gen = frequency [(2, gen), (1, smart)]
where
smart = gen >>= \a ->
oneof (gen : mapMaybe (smartGenTrans a) (transformations r))
smartGenTrans :: a -> Transformation a -> Maybe (Gen a)
smartGenTrans a trans =
case trans of
RewriteRule r -> return (smartGenerator r)
Lift lp t -> do
b <- liftPairGet lp a
gen <- smartGenTrans b t
return $ liftM (\c -> liftPairSet lp c a) gen
_ -> Nothing
smartApplyRule :: Rule a -> a -> Gen (Maybe a)
smartApplyRule r a = do
xss <- mapM (`smartApplyTrans` a) (transformations r)
case concat xss of
[] -> return Nothing
xs -> oneof $ map (return . Just) xs
smartApplyTrans :: Transformation a -> a -> Gen [a]
smartApplyTrans trans a =
case trans of
Abstraction args _ g -> smartArgs args >>= \b -> smartApplyTrans (g b) a
_ -> return (applyAll trans a)
smartArgs :: ArgumentList a -> Gen a
smartArgs (Nil a) = return a
smartArgs (Cons (f, _) descr xs) = liftM2 (curry f) (genArgument descr) (smartArgs xs)