-------------------------------------------------------------------------------- -- Copyright © 2018 chessai -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions are met: -- -- * Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- -- * Redistributions in binary form must reproduce the above -- copyright notice, this list of conditions and the following -- disclaimer in the documentation and/or other materials provided -- with the distribution. -- -- * Neither the name of chessai nor the names of other -- contributors may be used to endorse or promote products derived -- from this software without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- {-# OPTIONS_GHC -Wall -O2 #-} -------------------------------------------------------------------------------- {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE UnboxedSums #-} {-# LANGUAGE UnboxedTuples #-} -------------------------------------------------------------------------------- {-| -} module Data.Validation.Unpacked ( -- * Data type Validation(Validation, Failure, Success) -- * Construction , failure , success , validate , fromEither , liftError -- * Functions on validations , validation , toEither , orElse , valueOr , ensure , codiagonal , bindValidation -- * Conversion , fromBaseValidation , toBaseValidation ) where -------------------------------------------------------------------------------- import Prelude () import Control.Applicative (Applicative((<*>), pure)) import Control.DeepSeq (NFData(rnf)) import Control.Monad (return) import Data.Bifoldable (Bifoldable(bifoldr)) import Data.Bifunctor (Bifunctor(bimap)) import Data.Bitraversable (Bitraversable(bitraverse)) import Data.Bool (Bool(False)) import Data.Either.Unpacked (Either(Left, Right), either) import Data.Eq (Eq((==))) import Data.Foldable (Foldable(foldr)) import Data.Function ((.), id) import Data.Functor (Functor(fmap), (<$>)) import Data.Monoid (Monoid(mempty,mappend)) import Data.Ord (Ord(compare, (>=)), Ordering(GT, LT)) import Data.Semigroup (Semigroup((<>))) import Data.Traversable (Traversable(traverse)) import qualified Data.Validation as BaseValidation import GHC.Read (Read(readPrec, readList), expectP) import GHC.Show (Show(showsPrec, showList), showString, showParen, showList__) import Text.Read (parens, Lexeme(Ident), (+++), readListPrec, readListDefault, readListPrecDefault) import Text.ParserCombinators.ReadPrec (prec, step) -------------------------------------------------------------------------------- -- | A @Validation@ is either a value of the type @err@ or @a@, similar to 'Either'. However, -- the 'Applicative' instance for @Validation@ /accumulates/ errors using a 'Semigroup' on @err@. -- In contrast, the @Applicative@ for @Either@ returns only the first error. -- -- A consequence of this is that @Validation@ has no 'Control.Monad.Monad' instance. This is because -- such an instance would violate the law that a Monad's 'Control.Monad.ap' must equal the -- @Applicative@'s 'Control.Applicative.<*>' -- -- An example of typical usage can be found . -- data Validation err a = Validation (# err | a #) pattern Failure :: err -> Validation err a pattern Failure err = Validation (# err | #) pattern Success :: a -> Validation err a pattern Success a = Validation (# | a #) {-# COMPLETE Failure, Success #-} -- | This is the same as 'Failure'. failure :: err -> Validation err a {-# INLINE failure #-} failure err = Validation (# err | #) -- | This is the same as 'Success'. success :: a -> Validation err a {-# INLINE success #-} success a = Validation (# | a #) appValidation :: (err -> err -> err) -> Validation err a -> Validation err a -> Validation err a {-# INLINE appValidation #-} appValidation m (Failure e1) (Failure e2) = Failure (e1 `m` e2) appValidation _ (Failure _) (Success a2) = Success a2 appValidation _ (Success a1) (Failure _) = Success a1 appValidation _ (Success a1) (Success _) = Success a1 -- | 'validate's the @a@ with the given predicate, returning @e@ if the predicate does not hold. validate :: err -> (a -> Bool) -> a -> Validation err a {-# INLINE validate #-} validate e p a = if p a then Success a else Failure e -- | Case analysis on 'Validation'. validation :: (e -> c) -> (a -> c) -> Validation e a -> c {-# INLINE validation #-} validation ec _ (Failure e) = ec e validation _ ac (Success a) = ac a -- | 'liftError' is useful for converting an 'Either' to an 'Validation' -- when the @Left@ of the 'Either' needs to be lifted into a 'Semigroup'. liftError :: (b -> e) -> Either b a -> Validation e a {-# INLINE liftError #-} liftError f = either (Failure . f) Success -- | Converts from 'Either' to 'Validation'. fromEither :: Either e a -> Validation e a {-# INLINE fromEither #-} fromEither = liftError id -- | Converts from 'Validation' to 'Either'. toEither :: Validation e a -> Either e a {-# INLINE toEither #-} toEither = validation Left Right -- | @v 'orElse' a@ returns @a@ when @v@ is Failure, and the @a@ in @Success a@. orElse :: Validation e a -> a -> a {-# INLINE orElse #-} orElse v a = validation (\_ -> a) id v -- | Return the @a@ or run the given function over the @e@. valueOr :: (e -> a) -> Validation e a -> a {-# INLINE valueOr #-} valueOr ea (Failure e) = ea e valueOr _ (Success a) = a -- | 'codiagonal' gets the value out of either side. codiagonal :: Validation a a -> a {-# INLINE codiagonal #-} codiagonal = valueOr id -- | 'ensure' leaves the validation unchanged when the predicate holds, or -- fails with @e@ otherwise. ensure :: e -> (a -> Bool) -> Validation e a -> Validation e a {-# INLINE ensure #-} ensure _ _ (Failure x) = Failure x ensure e p (Success a) = validate e p a -- | @bindValidation@ binds through an Validation, which is useful for -- composing Validations sequentially. Note that despite having a bind -- function of the correct type, Validation is not a monad. -- The reason is, this bind does not accumulate errors, so it does not -- agree with the Applicative instance. -- -- There is nothing wrong with using this function, it just does not make a -- valid @Monad@ instance. bindValidation :: Validation e a -> (a -> Validation e b) -> Validation e b bindValidation (Failure e) _ = Failure e bindValidation (Success a) f = f a -- | Convert validation's 'BaseValidation.Validation' to a 'Validation'. fromBaseValidation :: BaseValidation.Validation a b -> Validation a b fromBaseValidation (BaseValidation.Failure a) = failure a fromBaseValidation (BaseValidation.Success b) = success b -- | Convert a 'Validation' to validation's 'BaseValidation.Validation'. toBaseValidation :: Validation a b -> BaseValidation.Validation a b toBaseValidation (Failure a) = BaseValidation.Failure a toBaseValidation (Success b) = BaseValidation.Success b -------------------------------------------------------------------------------- -- this is what happens when you can't derive things instance (Eq a, Eq b) => Eq (Validation a b) where Failure a == Failure b = a == b Success a == Success b = a == b _ == _ = False {-# INLINE (==) #-} -- this is what happens when you can't derive things instance (Ord a, Ord b) => Ord (Validation a b) where compare x y = case x of Failure a -> case y of Failure b -> compare a b _ -> LT Success a -> case y of Success b -> compare a b _ -> GT {-# INLINE compare #-} -- this is what happens when you can't derive things instance (Read a, Read b) => Read (Validation a b) where readPrec = parens (prec 10 (do expectP (Ident "Failure") a <- step readPrec return (Failure a)) +++ prec 10 (do expectP (Ident "Success") b <- step readPrec return (Success b))) readList = readListDefault readListPrec = readListPrecDefault -- this is what happens when you can't derive things instance (Show b, Show a) => Show (Validation a b) where showsPrec i (Failure a) = showParen (i >= 11) ((.) (showString "Failure ") (showsPrec 11 a)) showsPrec i (Success b) = showParen (i >= 11) ((.) (showString "Success ") (showsPrec 11 b)) showList = showList__ (showsPrec 0) instance Functor (Validation err) where fmap _ (Failure e) = Failure e fmap f (Success a) = Success (f a) {-# INLINE fmap #-} instance Semigroup err => Applicative (Validation err) where pure = Success {-# INLINE pure #-} Failure e1 <*> Failure e2 = Failure (e1 <> e2) Failure e1 <*> Success _ = Failure e1 Success _ <*> Failure e2 = Failure e2 Success f <*> Success a = Success (f a) {-# INLINE (<*>) #-} instance Foldable (Validation err) where foldr f x (Success a) = f a x foldr _ x (Failure _) = x {-# INLINE foldr #-} instance Traversable (Validation err) where traverse f (Success a) = Success <$> f a traverse _ (Failure e) = pure (Failure e) {-# INLINE traverse #-} instance Bifunctor Validation where bimap f _ (Failure e) = Failure (f e) bimap _ g (Success a) = Success (g a) {-# INLINE bimap #-} instance Bifoldable Validation where bifoldr _ g x (Success a) = g a x bifoldr f _ x (Failure e) = f e x {-# INLINE bifoldr #-} instance Bitraversable Validation where bitraverse _ g (Success a) = Success <$> g a bitraverse f _ (Failure e) = Failure <$> f e {-# INLINE bitraverse #-} instance Semigroup e => Semigroup (Validation e a) where (<>) = appValidation (<>) {-# INLINE (<>) #-} instance Monoid e => Monoid (Validation e a) where mappend = appValidation mappend {-# INLINE mappend #-} mempty = Failure mempty {-# INLINE mempty #-} instance (NFData e, NFData a) => NFData (Validation e a) where rnf (Failure e) = rnf e rnf (Success a) = rnf a {-# INLINE rnf #-}