-- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. {-# LANGUAGE TypeOperators #-} module Data.Predicate ( -- * Predicate Predicate , constant , failure , true , false , and , or , orElse , (.&.) , (.|.) , (|||) , opt , def , mapOkay , mapFail , exec -- * Result , module Data.Predicate.Result -- * Product , module Data.Predicate.Product ) where import Control.Monad import Data.Predicate.Product import Data.Predicate.Result import Prelude hiding (and, or) -- | A predicate is a function of some value of type @a@ to a 'Result', -- i.e. a 'Bool'-like value with 'Okay' as 'True' and 'Fail' as 'False', -- which carries additional data in each branch. type Predicate a f t = a -> Result f t -- | A predicate which always returns @Okay@ with the given -- value as metadata. constant :: t -> Predicate a f t constant t _ = return t true :: Predicate a f () true = constant () -- | A predicate which always returns @Fail@ with the given -- value as metadata. failure :: f -> Predicate a f t failure f _ = Fail f false :: Predicate a () t false = failure () infixr 3 .&. infixr 2 .|. infixr 2 ||| -- | A predicate corresponding to the logical AND connective -- of two predicate. and :: Predicate a f t -> Predicate a f t' -> Predicate a f (t ::: t') and f g x = f x `cmp` g x where cmp (Okay d y) (Okay w z) = Okay (d + w) (y ::: z) cmp (Okay _ _) (Fail y) = Fail y cmp (Fail y) _ = Fail y -- | A predicate corresponding to the logical -- OR connective of two predicates. It requires the -- metadata of each @Okay@ branch to be of the same type. -- -- If both arguments evaluate to @Okay@ the one with the -- smaller \"delta\" will be preferred, or--if equal--the -- left-hand argument. or :: Predicate a f t -> Predicate a f t -> Predicate a f t or f g x = f x `cmp` g x where cmp a@(Okay d _) b@(Okay w _) = if w < d then b else a cmp a@(Okay _ _) (Fail _) = a cmp (Fail _) b@(Okay _ _) = b cmp (Fail _) b@(Fail _) = b -- | A predicate corresponding to the logical -- OR connective of two predicates. The metadata of -- each @Okay@ branch can be of different types. -- -- If both arguments evaluate to @Okay@ the one with the -- smaller \"delta\" will be preferred, or--if equal--the -- left-hand argument. orElse :: Predicate a f t -> Predicate a f t' -> Predicate a f (Either t t') orElse f g x = f x `cmp` g x where cmp (Okay d y) (Okay w z) = if w < d then Okay w (Right z) else Okay d (Left y) cmp (Okay d y) (Fail _) = Okay d (Left y) cmp (Fail _) (Okay d y) = Okay d (Right y) cmp (Fail _) (Fail y) = Fail y -- | Alias of 'and'. (.&.) :: Predicate a f t -> Predicate a f t' -> Predicate a f (t ::: t') (.&.) = and -- | Alias of 'or'. (.|.) :: Predicate a f t -> Predicate a f t -> Predicate a f t (.|.) = or -- | Alias of 'orElse'. (|||) :: Predicate a f t -> Predicate a f t' -> Predicate a f (Either t t') (|||) = orElse -- | Like 'fmap', but only maps the @Okay@ metadata to another result. mapOkay :: (t -> Result f t') -> Predicate a f t -> Predicate a f t' mapOkay f p a = case p a of Okay _ x -> f x Fail x -> Fail x -- | Like 'mapOkay', but for the @Fail@ case. mapFail :: (f -> Result f' t) -> Predicate a f t -> Predicate a f' t mapFail f p a = case p a of Fail x -> f x Okay d x -> Okay d x -- | A predicate modifier which makes the given predicate optional, -- i.e. the @Okay@ metadata type becomes a 'Maybe' and in the failure-case -- 'Nothing' is returned. opt :: Predicate a f t -> Predicate a f (Maybe t) opt = fmap (result (const $ return Nothing) (\d x -> Okay d (Just x))) -- | A predicate modifier which returns as @Okay@ metadata the provided default -- value if the given predicate fails. def :: t -> Predicate a f t -> Predicate a f t def t = fmap (result (const $ return t) Okay) exec :: Predicate a f t -> a -> (f -> b) -> (t -> b) -> b exec p a g f = case p a of Okay _ x -> f x Fail x -> g x