dao-0.1.0.1: src/Dao/Predicate.hs
-- "src/Dao/Predicate.hs" provides 'PredicateT' which combines the
-- Maybe and Either types into a single monad.
--
-- Copyright (C) 2008-2014 Ramin Honary.
-- This file is part of the Dao System.
--
-- The Dao System is free software: you can redistribute it and/or
-- modify it under the terms of the GNU General Public License as
-- published by the Free Software Foundation, either version 3 of the
-- License, or (at your option) any later version.
--
-- The Dao System is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program (see the file called "LICENSE"). If not, see
-- <http://www.gnu.org/licenses/agpl.html>.
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FunctionalDependencies #-}
-- | Provides a monad that essentially combines the monadic functionality of 'Prelude.Maybe' and
-- 'Prelude.Either' into a single monad 'Predicate. Both the 'Prelude.Maybe' and 'Prelude.Either'
-- data types are both monads but it is convenient to have a single data type combining the two.
-- 'PFail' is analogous to @'Prelude.Left'@, 'Backtrack' is analogous to
-- @'Prelude.Right' $ 'Prelude.Nothing'@, and 'OK' is analogous to
-- @'Prelude.Right' . 'Prelude.Just'@. All relevant monad transformers are instnatiated, including
-- 'Control.Applicative.Applicative', 'Control.Applicative.Alternative', 'Control.Monad.MonadPlus',
-- and 'Control.Monad.Error.MonadError'.
--
-- A new monad transformer 'PredicateT' is also introduced which lifts the 'Predicate' monad into
-- another monad and wraps it into the 'PredicateT' data type which instantiates the
-- 'Control.Monad.Trans.MonadTrans' class. Further, a new class 'MonadPlusError' is defined which
-- allows you to directly manipulate the 'Predicate' value of a 'PredicateT' transformer.
--
-- Here is a simple example of how to use this module.
-- > newtype MyErr = MyErr String
-- > newtype MyIO a = WrapMyIO { unwrapMyIO :: 'PredicateT' MyErr IO a }
-- > deriving (Functor, Applicative, Alternative)
-- >
-- > instance 'Control.Monad.Monad' MyIO where -- this instance can also be derived
-- > 'Control.Monad.return' = WrapMyIO . 'Control.Monad.return'
-- > f 'Control.Monad.>>= bindTo = WrapMyIO $ unwrapMyIO f 'Control.Monad.>>=' unwrapMyIO . bindTo
-- > 'Control.Monad.fail' message = WrapMyIO $ 'PFail' (MyErr message)
-- >
-- > instance 'Control.Monad.MonadPlus' MyIO where -- this instance can also be derived
-- > 'Control.Monad.mzero' = WrapMyIO 'mzero'
-- > 'Control.Monad.mplus' (WrapMyIO try1) (WrapMyIO try2) = WrapMyIO ('mplus' try1 try2)
-- >
-- > instance 'Control.Monad.Error.Class.MonadError' MyErr MyIO where
-- > 'Control.Monad.Error.Class.MonadError.throwError' = WrapMyIO . 'Control.Monad.Error.Class.MonadError.throwError'
-- > 'Control.Monad.Error.Class.MonadError.catchError' (WrapMyIO try) catch = WrapMyIO ('catchError' try (unwrapMyIO . catch))
-- >
-- > instance 'Control.Monad.IO.Class.MonadIO' MyIO where
-- > 'Control.Monad.IO.Class.liftIO' = WrapMyIO . 'liftIO'
-- >
-- > doStep :: MyIO ()
-- > doStep = ...
-- >
-- > doJump :: MyIO ()
-- > doJump = ...
module Dao.Predicate where
import Control.Applicative
import Control.Monad
import Control.Monad.Error
import Data.Monoid
-- | 'Predicate' is a data type that combines 'Prelude.Maybe' and 'Prelude.Either' into a single
-- type. The 'Predicate' monad allows you to construct predicate functions that evaluate to 'OK'
-- (true) 'Backtrack' (false), and also provides a 'PFail' constructor for indicating a "does not
-- make sense" condition.
--
-- The truth condition 'OK' can 'Control.Monad.return' a value which makes it a 'Data.Functor', an
-- 'Control.Applicative.Applicative', and a 'Control.Monad.Monad'. The false condition 'Backtrack'
-- serves as the 'Control.Monad.mzero' for 'Control.Monad.MonadPlus' and 'Control.Applicative.empty'
-- value for 'Control.Applicative.Alternative'. The 'PFail' condition, like 'Prelude.Left', can be
-- used as an error condition in the 'Control.Monad.Error.ErrorClass' which can be caught by
-- 'Control.Monad.Error.catchError'.
--
-- This data type was originally intended for use in the Dao parser, but it is now used in several
-- contexts throughout the program.
data Predicate err ok
= Backtrack -- ^ analogous to 'Prelude.Nothing'
| PFail err -- ^ analogous to 'Prelude.Left'
| OK ok -- ^ OK is "just right" i.e. it is analogous to 'Prelude.Just' and 'Prelude.Right'.
deriving (Eq, Ord, Show)
instance Functor (Predicate err) where
fmap fn (OK a) = OK (fn a)
fmap _ (PFail u) = PFail u
fmap _ Backtrack = Backtrack
instance Monad (Predicate err) where
return = OK
ma >>= mfn = case ma of
OK ok -> mfn ok
PFail err -> PFail err
Backtrack -> Backtrack
instance MonadPlus (Predicate err) where
mzero = Backtrack
mplus Backtrack b = b
mplus a _ = a
instance MonadError err (Predicate err) where
throwError = PFail
catchError try catch = case try of
PFail err -> catch err
try -> try
instance Applicative (Predicate err) where { pure = return; (<*>) = ap; }
instance Alternative (Predicate err) where { empty = mzero; (<|>) = mplus; }
instance Monoid ok => Monoid (Predicate err ok) where
mempty = Backtrack
mappend (OK a) (OK b) = OK(a<>b)
mappend a _ = a
----------------------------------------------------------------------------------------------------
-- | A monad transformer lifting 'Predicate' into an outer monad. Use 'runPreicateT' to remove the
-- 'PredicateT' outer monad and retrieve the inner 'Predictate' value.
newtype PredicateT err m ok = PredicateT { runPredicateT :: m (Predicate err ok) }
instance Monad m => Monad (PredicateT err m) where
return a = PredicateT (return (OK a))
PredicateT ma >>= fma = PredicateT $ do
a <- ma
case a of
Backtrack -> return Backtrack
PFail u -> return (PFail u)
OK o -> runPredicateT (fma o)
PredicateT ma >> PredicateT mb = PredicateT $ do
a <- ma
case a of
Backtrack -> return Backtrack
PFail u -> return (PFail u)
OK _ -> mb
fail msg = PredicateT{ runPredicateT = return (PFail (error msg)) }
instance Functor m => Functor (PredicateT err m) where
fmap f (PredicateT ma) = PredicateT (fmap (fmap f) ma)
instance Monad m => MonadPlus (PredicateT err m) where
mzero = PredicateT (return Backtrack)
mplus (PredicateT a) (PredicateT b) = PredicateT $ do
result <- a
case result of
Backtrack -> b
PFail u -> return (PFail u)
OK o -> return (OK o)
instance Monad m => MonadError err (PredicateT err m) where
throwError msg = PredicateT{ runPredicateT = return (PFail msg) }
catchError ptrans catcher = PredicateT $ do
value <- runPredicateT ptrans
case value of
Backtrack -> return Backtrack
PFail u -> runPredicateT (catcher u)
OK a -> return (OK a)
instance (Functor m, Monad m) => Applicative (PredicateT err m) where { pure = return; (<*>) = ap; }
instance (Functor m, Monad m) => Alternative (PredicateT err m) where { empty = mzero; (<|>) = mplus; }
instance MonadTrans (PredicateT err) where { lift m = PredicateT(m >>= return . OK) }
instance MonadIO m => MonadIO (PredicateT err m) where { liftIO = PredicateT . liftIO . fmap OK }
----------------------------------------------------------------------------------------------------
-- | Often it is necessary to evaluate a sub-predicate monad within the 'Predicate' or 'PredicateT'
-- monads within the current 'Predicate' monad. Simply evaluating the sub-predicate would cause the
-- current predicate monad to evaluates to 'PFail' or 'Backtrack' if the sub-predicate evaluates to
-- either of these values. But using 'catchPredicate', it is possible to safely evaluate the
-- sub-predicate and capture it's 'Predicate' result, where you can then make a decision on how to
-- behave.
-- > do p <- 'catchPredicate' couldFailOrBacktrack
-- > case p of
-- > 'OK' rval -> useReturnValue rval -- use the return value from couldFailOrBacktrack
-- > 'PFail' msg -> printMyError msg -- report the error from couldFailOrBacktrack
-- > 'Backtrack' -> return () -- ignore backtracking
-- The above procedure prints the error message created if the sub-predicate evaluated to 'PFail'.
-- If you would like to "re-throw" a 'Predicate' that you have received you can use the 'predicate'
-- function. For example, this line of code could be added to the above procedure:
-- > predicate p
-- and the function will evaluate to the same exact 'Predicate' value that @couldFailOrBacktrack@
-- had produced after the necessary response to the failure has been made, e.g. after the error
-- message has been printed.
class MonadPlusError err m | m -> err where
-- | Unlifts the whole 'Predicate' value, unlike 'catchError' which only catches the value stored
-- in a 'PFail' constructor.
catchPredicate :: m a -> m (Predicate err a)
-- | This will force the 'Predicate' value of the current computation. The following should
-- generally be true for all instances of 'MonadPlusError'.
-- > 'Control.Monad.return' = 'predicate' . 'OK'
-- > 'Control.Monad.Error.State.throwError' = 'predicate' . 'PFail'
-- > 'Control.Monad.mzero' = 'predicate' 'Backtrack'
predicate :: Predicate err a -> m a
instance MonadPlusError err (Predicate err) where { catchPredicate = OK; predicate = id; }
instance Monad m => MonadPlusError err (PredicateT err m) where
catchPredicate (PredicateT fn) = PredicateT{ runPredicateT = fn >>= \o -> return (OK o) }
predicate pval = PredicateT (return pval)
-- | Evaluates to an empty list if the given 'Predicate' is 'Backtrack' or 'PFail', otherwise returns a
-- list containing the value in the 'OK' value.
okToList :: Predicate err o -> [o]
okToList pval = case pval of
OK o -> [o]
Backtrack -> []
PFail _ -> []
-- | Like 'okToList', but evaluates to 'Data.Maybe.Nothing' if the given 'Predicate' is 'Backtrack' or
-- 'PFail', or 'Data.Maybe.Just' containing the value in the 'OK' value.
okToJust :: Predicate err o -> Maybe o
okToJust pval = case pval of
OK o -> Just o
Backtrack -> Nothing
PFail _ -> Nothing
-- | If given 'Data.Maybe.Nothing', evaluates to 'PFail' with the given error information.
-- Otherwise, evaluates to 'OK'.
maybeToPFail :: err -> Maybe o -> Predicate err o
maybeToPFail err o = case o of
Nothing -> PFail err
Just ok -> OK ok
-- | Like 'Prelude.fmap' but operates on the error report data of the 'Predicate'.
fmapPFail :: (errA -> errB) -> Predicate errA o -> Predicate errB o
fmapPFail f pval = case pval of
OK o -> OK o
Backtrack -> Backtrack
PFail err -> PFail (f err)
-- | Like 'Data.Either.partitionEithers', but operates on a list of 'Predicates'.
partitionPredicates :: [Predicate err o] -> ([err], [o])
partitionPredicates = loop [] [] where
loop errs oks ox = case ox of
[] -> (errs, oks)
OK o : ox -> loop errs (oks++[o]) ox
PFail err : ox -> loop (errs++[err]) oks ox
Backtrack : ox -> loop errs oks ox