blob: 42ddf78da46b324141ce507f4ef5d30c4a4339cf [file] [log] [blame] [edit]
-- |
-- Module : Minigent.Syntax.Utils.Rewrite
-- Copyright : (c) Data61 2018-2019
-- Commonwealth Science and Research Organisation (CSIRO)
-- ABN 41 687 119 230
-- License : BSD3
--
-- Contains a mini-library for rewrites, that is, partial functions
-- from some type to the same type, which may access some external effects (such as fresh names)
-- through an underlying monad.
--
-- It can be imported qualified or unqualified.
module Minigent.Syntax.Utils.Rewrite
( -- * Types
Rewrite
, run
, Rewrite' (..)
, lift
, -- * Composition
andThen
, untilFixedPoint
, -- * Pre and Postprocessing
pre
, post
, -- * Making Rewrites
-- ** Pure Rewrites
rewrite
, pickOne
-- ** Effectful Rewrites
, rewrite'
, pickOne'
, withTransform
, -- * Debugging
debug
, debugNewline
) where
import Control.Monad.Identity
import Control.Monad.Trans.Maybe
import Control.Applicative
import Debug.Trace
-- | Intuitively a @Rewrite a@ is a partial function from @a@ to @a@.
-- It can be composed disjuctively using the 'Semigroup' instance, or
-- sequentially using the 'andThen' function.
type Rewrite a = Rewrite' Identity a
-- | A @Rewrite' m a@ may in full generality access the effects of a monad @m@ while
-- attempting to rewrite values of type @a@.
newtype Rewrite' m a = Rewrite { run' :: a -> MaybeT m a }
-- | Disjunctive composition, that is: @r <> s@ will first attempt to rewrite with @r@.
-- If @r@ successfully rewrites, then the result of @r@ is returned. If @r@ does not
-- rewrite (i.e. it returns @Nothing@), then the second rewrite @s@ is attempted instead.
instance Monad m => Semigroup (Rewrite' m a) where
Rewrite f <> Rewrite g = Rewrite (\a -> f a <|> g a)
-- | The 'mempty' is the rewrite that never successfully rewrites any term.
instance Monad m => Monoid (Rewrite' m a) where
mempty = Rewrite (const empty)
-- | Run a function to post-process a rewrite's output.
post :: (Functor m) => (a -> a) -> Rewrite' m a -> Rewrite' m a
post op (Rewrite f) = Rewrite (fmap op . f)
-- | Run a function to pre-process a rewrite's input.
pre :: (Functor m) => (a -> a) -> Rewrite' m a -> Rewrite' m a
pre op (Rewrite f) = Rewrite (f . op)
-- | Sequential composition, that is: @r `andThen` s@ will first rewrite with @r@ and, if that
-- succeeds, rewrite the result with @s@. If either @r@ or @s@ fails to rewrite, the whole thing
-- fails to rewrite.
andThen :: Monad m => Rewrite' m a -> Rewrite' m a -> Rewrite' m a
andThen (Rewrite f) (Rewrite g) = Rewrite $ \x -> f x >>= g
-- | Given a partial function from @a@ to @a@, produce a @Rewrite'@ value.
rewrite :: Applicative m => (a -> Maybe a) -> Rewrite' m a
rewrite f = Rewrite (MaybeT . pure . f)
-- | Given a partial, effectful function from @a@ to @a@ in some monad @m@,
-- produce a @Rewrite'@ value.
rewrite' :: Applicative m => (a -> MaybeT m a) -> Rewrite' m a
rewrite' = Rewrite
-- | Given a non-effectful rewrite, returns a partial function.
-- For effectful rewrites, the 'run'' field can be used.
run :: Rewrite a -> a -> Maybe a
run rw = runIdentity . runMaybeT . run' rw
-- | A rewrite that exhausts itself only when it cannot rewrite anymore
untilFixedPoint :: Monad m => Rewrite' m a -> Rewrite' m a
untilFixedPoint rw = (rw `andThen` untilFixedPoint rw) <> rw
-- | A somewhat niche function. Given a /selector function/
-- which extracts a special value @a@ from a collection of items of type @x@,
-- rewrite that @a@ value into a new collection of @x@ values.
--
-- This function is usually applied to collections of constraints.
-- Given a set of constraints, we can identify soluble subproblems in the constraint
-- set, along with the set of other constraints that are not part of that subproblem.
-- This is the selector function.
--
-- After identifying the subproblem, we can reduce it into a smaller set of constraints
-- which are then merged with the leftover constraints from the selector function to
-- form the new constraint set.
withTransform :: Monad m => ([x] -> Maybe (a, [x])) -> (a -> MaybeT m [x]) -> Rewrite' m [x]
withTransform transform f = Rewrite $ \cs -> do
(c, cs1) <- MaybeT (pure (transform cs))
cs2 <- f c
pure (cs1 ++ cs2)
-- | Given a function that, given a value of type @x@, possibly returns many values of type @x@,
-- produce a rewrite that applies this wherever possible to a list of values of type @x@, merging
-- the results back into the list.
--
-- Typically applied to constraints, where individual reducible constraints are picked out
-- of the set and broken down into some subconstraints.
pickOne :: (x -> Maybe [x]) -> Rewrite [x]
pickOne f = pickOne' (MaybeT . pure . f)
-- | Just as 'pickOne', but with monadic effects like fresh names.
pickOne' :: Monad m => (x -> MaybeT m [x]) -> Rewrite' m [x]
pickOne' f = Rewrite each
where
each [] = empty
each (c:cs) = MaybeT $ do
m <- runMaybeT (f c)
case m of
Nothing -> fmap (c:) <$> runMaybeT (each cs)
Just cs' -> pure (Just (cs' ++ cs))
-- | Given a pure 'Rewrite', produce an effectful rewrite in any monad.
lift :: Applicative m => Rewrite a -> Rewrite' m a
lift (Rewrite f) = rewrite (runIdentity . runMaybeT . f)
-- | For debugging, prints the contents of the rewrite to the console, with a string prefix.
debug :: (Monad m) => String -> (a -> String) -> Rewrite' m a
debug pfx show = Rewrite (\cs -> case () of () | trace (pfx ++ ": " ++ show cs) False -> undefined
| otherwise -> empty )
-- | For debugging, prints the contents of the rewrite to the console, with a string prefix.
debugNewline :: (Monad m) => String -> (a -> String) -> Rewrite' m a
debugNewline pfx show = Rewrite (\cs -> case () of () | trace (pfx ++ ":\n " ++ show cs) False -> undefined
| otherwise -> empty )