unpacked-validation-0.1.0.0: src/Data/Validation/Unpacked.hs
--------------------------------------------------------------------------------
-- 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 <https://github.com/qfpl/validation/blob/master/examples/src/Email.hs here>.
--
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 #-}