validation 0.6.3 → 1
raw patch · 5 files changed
+159/−155 lines, 5 filesdep ~basedep ~hedgehog
Dependency ranges changed: base, hedgehog
Files
- changelog +4/−0
- src/Data/Validation.hs +132/−132
- test/hedgehog_tests.hs +6/−6
- test/hunit_tests.hs +12/−12
- validation.cabal +5/−5
changelog view
@@ -1,3 +1,7 @@+1++* Rename `AccValidation` to `Validation`+ 0.6.3 * Add `Generic` and `NFData` instances
src/Data/Validation.hs view
@@ -11,7 +11,7 @@ module Data.Validation ( -- * Data type- AccValidation(..)+ Validation(..) -- * Constructing validations , validate , validationNel@@ -28,7 +28,7 @@ , bindValidation -- * Prisms -- | These prisms are useful for writing code which is polymorphic in its- -- choice of Either or AccValidation. This choice can then be made later by a+ -- choice of Either or Validation. This choice can then be made later by a -- user, depending on their needs. -- -- An example of this style of usage can be found@@ -71,19 +71,19 @@ import Prelude(Show) --- | An @AccValidation@ is either a value of the type @err@ or @a@, similar to 'Either'. However,--- the 'Applicative' instance for @AccValidation@ /accumulates/ errors using a 'Semigroup' on @err@.+-- | An @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 @AccValidation@ has no 'Data.Functor.Bind.Bind' or 'Control.Monad.Monad' instance. This is because+-- A consequence of this is that @Validation@ has no 'Data.Functor.Bind.Bind' or '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 AccValidation err a =- AccFailure err- | AccSuccess a+data Validation err a =+ Failure err+ | Success a deriving ( Eq, Ord, Show, Data, Typeable #if __GLASGOW_HASKELL__ >= 702@@ -91,123 +91,123 @@ #endif ) -instance Functor (AccValidation err) where- fmap _ (AccFailure e) =- AccFailure e- fmap f (AccSuccess a) =- AccSuccess (f a)+instance Functor (Validation err) where+ fmap _ (Failure e) =+ Failure e+ fmap f (Success a) =+ Success (f a) {-# INLINE fmap #-} -instance Semigroup err => Apply (AccValidation err) where- AccFailure e1 <.> b = AccFailure $ case b of- AccFailure e2 -> e1 <> e2- AccSuccess _ -> e1- AccSuccess _ <.> AccFailure e2 =- AccFailure e2- AccSuccess f <.> AccSuccess a =- AccSuccess (f a)+instance Semigroup err => Apply (Validation err) where+ Failure e1 <.> b = Failure $ case b of+ Failure e2 -> e1 <> e2+ Success _ -> e1+ Success _ <.> Failure e2 =+ Failure e2+ Success f <.> Success a =+ Success (f a) {-# INLINE (<.>) #-} -instance Semigroup err => Applicative (AccValidation err) where+instance Semigroup err => Applicative (Validation err) where pure =- AccSuccess+ Success (<*>) = (<.>) -instance Alt (AccValidation err) where- AccFailure _ <!> x =+instance Alt (Validation err) where+ Failure _ <!> x = x- AccSuccess a <!> _ =- AccSuccess a+ Success a <!> _ =+ Success a {-# INLINE (<!>) #-} -instance Foldable (AccValidation err) where- foldr f x (AccSuccess a) =+instance Foldable (Validation err) where+ foldr f x (Success a) = f a x- foldr _ x (AccFailure _) =+ foldr _ x (Failure _) = x {-# INLINE foldr #-} -instance Traversable (AccValidation err) where- traverse f (AccSuccess a) =- AccSuccess <$> f a- traverse _ (AccFailure e) =- pure (AccFailure e)+instance Traversable (Validation err) where+ traverse f (Success a) =+ Success <$> f a+ traverse _ (Failure e) =+ pure (Failure e) {-# INLINE traverse #-} -instance Bifunctor AccValidation where- bimap f _ (AccFailure e) =- AccFailure (f e)- bimap _ g (AccSuccess a) =- AccSuccess (g a)+instance Bifunctor Validation where+ bimap f _ (Failure e) =+ Failure (f e)+ bimap _ g (Success a) =+ Success (g a) {-# INLINE bimap #-} -instance Bifoldable AccValidation where- bifoldr _ g x (AccSuccess a) =+instance Bifoldable Validation where+ bifoldr _ g x (Success a) = g a x- bifoldr f _ x (AccFailure e) =+ bifoldr f _ x (Failure e) = f e x {-# INLINE bifoldr #-} -instance Bitraversable AccValidation where- bitraverse _ g (AccSuccess a) =- AccSuccess <$> g a- bitraverse f _ (AccFailure e) =- AccFailure <$> f e+instance Bitraversable Validation where+ bitraverse _ g (Success a) =+ Success <$> g a+ bitraverse f _ (Failure e) =+ Failure <$> f e {-# INLINE bitraverse #-} -appAccValidation ::+appValidation :: (err -> err -> err)- -> AccValidation err a- -> AccValidation err a- -> AccValidation err a-appAccValidation m (AccFailure e1) (AccFailure e2) =- AccFailure (e1 `m` e2)-appAccValidation _ (AccFailure _) (AccSuccess a2) =- AccSuccess a2-appAccValidation _ (AccSuccess a1) (AccFailure _) =- AccSuccess a1-appAccValidation _ (AccSuccess a1) (AccSuccess _) =- AccSuccess a1-{-# INLINE appAccValidation #-}+ -> Validation err a+ -> Validation err a+ -> Validation err a+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+{-# INLINE appValidation #-} -instance Semigroup e => Semigroup (AccValidation e a) where+instance Semigroup e => Semigroup (Validation e a) where (<>) =- appAccValidation (<>)+ appValidation (<>) {-# INLINE (<>) #-} -instance Monoid e => Monoid (AccValidation e a) where+instance Monoid e => Monoid (Validation e a) where mappend =- appAccValidation mappend+ appValidation mappend {-# INLINE mappend #-} mempty =- AccFailure mempty+ Failure mempty {-# INLINE mempty #-} -instance Swapped AccValidation where+instance Swapped Validation where swapped = iso (\v -> case v of- AccFailure e -> AccSuccess e- AccSuccess a -> AccFailure a)+ Failure e -> Success e+ Success a -> Failure a) (\v -> case v of- AccFailure a -> AccSuccess a- AccSuccess e -> AccFailure e)+ Failure a -> Success a+ Success e -> Failure e) {-# INLINE swapped #-} -instance (NFData e, NFData a) => NFData (AccValidation e a) where+instance (NFData e, NFData a) => NFData (Validation e a) where rnf v = case v of- AccFailure e -> rnf e- AccSuccess a -> rnf a+ Failure e -> rnf e+ Success a -> rnf a -- | 'validate's the @a@ with the given predicate, returning @e@ if the predicate does not hold. -- -- This can be thought of as having the less general type: -- -- @--- validate :: e -> (a -> Bool) -> a -> AccValidation e a+-- validate :: e -> (a -> Bool) -> a -> Validation e a -- @ validate :: Validate v => e -> (a -> Bool) -> a -> v e a validate e p a =@@ -215,54 +215,54 @@ -- | 'validationNel' is 'liftError' specialised to 'NonEmpty' lists, since -- they are a common semigroup to use.-validationNel :: Either e a -> AccValidation (NonEmpty e) a+validationNel :: Either e a -> Validation (NonEmpty e) a validationNel = liftError pure --- | Converts from 'Either' to 'AccValidation'.-fromEither :: Either e a -> AccValidation e a+-- | Converts from 'Either' to 'Validation'.+fromEither :: Either e a -> Validation e a fromEither = liftError id --- | 'liftError' is useful for converting an 'Either' to an 'AccValidation'+-- | '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 -> AccValidation e a-liftError f = either (AccFailure . f) AccSuccess+liftError :: (b -> e) -> Either b a -> Validation e a+liftError f = either (Failure . f) Success --- | 'validation' is the catamorphism for @AccValidation@.-validation :: (e -> c) -> (a -> c) -> AccValidation e a -> c+-- | 'validation' is the catamorphism for @Validation@.+validation :: (e -> c) -> (a -> c) -> Validation e a -> c validation ec ac v = case v of- AccFailure e -> ec e- AccSuccess a -> ac a+ Failure e -> ec e+ Success a -> ac a --- | Converts from 'AccValidation' to 'Either'.-toEither :: AccValidation e a -> Either e a+-- | Converts from 'Validation' to 'Either'.+toEither :: Validation e a -> Either e a toEither = validation Left Right --- | @v 'orElse' a@ returns @a@ when @v@ is AccFailure, and the @a@ in @AccSuccess a@.+-- | @v 'orElse' a@ returns @a@ when @v@ is Failure, and the @a@ in @Success a@. -- -- This can be thought of as having the less general type: -- -- @--- orElse :: AccValidation e a -> a -> a+-- orElse :: Validation e a -> a -> a -- @ orElse :: Validate v => v e a -> a -> a-orElse v a = case v ^. _AccValidation of- AccFailure _ -> a- AccSuccess x -> x+orElse v a = case v ^. _Validation of+ Failure _ -> a+ Success x -> x -- | Return the @a@ or run the given function over the @e@. -- -- This can be thought of as having the less general type: -- -- @--- valueOr :: (e -> a) -> AccValidation e a -> a+-- valueOr :: (e -> a) -> Validation e a -> a -- @ valueOr :: Validate v => (e -> a) -> v e a -> a-valueOr ea v = case v ^. _AccValidation of- AccFailure e -> ea e- AccSuccess a -> a+valueOr ea v = case v ^. _Validation of+ Failure e -> ea e+ Success a -> a -- | 'codiagonal' gets the value out of either side.-codiagonal :: AccValidation a a -> a+codiagonal :: Validation a a -> a codiagonal = valueOr id -- | 'ensure' leaves the validation unchanged when the predicate holds, or@@ -271,79 +271,79 @@ -- This can be thought of as having the less general type: -- -- @--- ensure :: e -> (a -> Bool) -> AccValidation e a -> AccValidation e a+-- ensure :: e -> (a -> Bool) -> Validation e a -> Validation e a -- @ ensure :: Validate v => e -> (a -> Bool) -> v e a -> v e a ensure e p =- over _AccValidation $ \v -> case v of- AccFailure x -> AccFailure x- AccSuccess a -> validate e p a+ over _Validation $ \v -> case v of+ Failure x -> Failure x+ Success a -> validate e p a -- | Run a function on anything with a Validate instance (usually Either)--- as if it were a function on AccValidation+-- as if it were a function on Validation -- -- This can be thought of as having the type ----- @(Either e a -> Either e' a') -> AccValidation e a -> AccValidation e' a'@-validationed :: Validate v => (v e a -> v e' a') -> AccValidation e a -> AccValidation e' a'-validationed f = under _AccValidation f+-- @(Either e a -> Either e' a') -> Validation e a -> Validation e' a'@+validationed :: Validate v => (v e a -> v e' a') -> Validation e a -> Validation e' a'+validationed f = under _Validation f --- | @bindValidation@ binds through an AccValidation, which is useful for--- composing AccValidations sequentially. Note that despite having a bind--- function of the correct type, AccValidation is not a monad.+-- | @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 :: AccValidation e a -> (a -> AccValidation e b) -> AccValidation e b+bindValidation :: Validation e a -> (a -> Validation e b) -> Validation e b bindValidation v f = case v of- AccFailure e -> AccFailure e- AccSuccess a -> f a+ Failure e -> Failure e+ Success a -> f a -- | The @Validate@ class carries around witnesses that the type @f@ is isomorphic--- to AccValidation, and hence isomorphic to Either.+-- to Validation, and hence isomorphic to Either. class Validate f where- _AccValidation ::- Iso (f e a) (f g b) (AccValidation e a) (AccValidation g b)+ _Validation ::+ Iso (f e a) (f g b) (Validation e a) (Validation g b) _Either :: Iso (f e a) (f g b) (Either e a) (Either g b) _Either = iso- (\x -> case x ^. _AccValidation of- AccFailure e -> Left e- AccSuccess a -> Right a)- (\x -> _AccValidation # case x of- Left e -> AccFailure e- Right a -> AccSuccess a)+ (\x -> case x ^. _Validation of+ Failure e -> Left e+ Success a -> Right a)+ (\x -> _Validation # case x of+ Left e -> Failure e+ Right a -> Success a) {-# INLINE _Either #-} -instance Validate AccValidation where- _AccValidation =+instance Validate Validation where+ _Validation = id- {-# INLINE _AccValidation #-}+ {-# INLINE _Validation #-} _Either = iso (\x -> case x of- AccFailure e -> Left e- AccSuccess a -> Right a)+ Failure e -> Left e+ Success a -> Right a) (\x -> case x of- Left e -> AccFailure e- Right a -> AccSuccess a)+ Left e -> Failure e+ Right a -> Success a) {-# INLINE _Either #-} instance Validate Either where- _AccValidation =+ _Validation = iso fromEither toEither- {-# INLINE _AccValidation #-}+ {-# INLINE _Validation #-} _Either = id {-# INLINE _Either #-} --- | This prism generalises 'Control.Lens.Prism._Left'. It targets the failure case of either 'Either' or 'AccValidation'.+-- | This prism generalises 'Control.Lens.Prism._Left'. It targets the failure case of either 'Either' or 'Validation'. _Failure :: Validate f => Prism (f e1 a) (f e2 a) e1 e2@@ -355,7 +355,7 @@ Right a -> Left (_Either # Right a)) {-# INLINE _Failure #-} --- | This prism generalises 'Control.Lens.Prism._Right'. It targets the success case of either 'Either' or 'AccValidation'.+-- | This prism generalises 'Control.Lens.Prism._Right'. It targets the success case of either 'Either' or 'Validation'. _Success :: Validate f => Prism (f e a) (f e b) a b@@ -369,5 +369,5 @@ -- | 'revalidate' converts between any two instances of 'Validate'. revalidate :: (Validate f, Validate g) => Iso (f e1 s) (f e2 t) (g e1 s) (g e2 t)-revalidate = _AccValidation . from _AccValidation+revalidate = _Validation . from _Validation
test/hedgehog_tests.hs view
@@ -9,7 +9,7 @@ import System.IO (BufferMode(..), hSetBuffering, stdout, stderr) import System.Exit (exitFailure) -import Data.Validation (AccValidation (AccSuccess, AccFailure))+import Data.Validation (Validation (Success, Failure)) main :: IO () main = do@@ -26,17 +26,17 @@ unless result $ exitFailure -genAccValidation :: Gen e -> Gen a -> Gen (AccValidation e a)-genAccValidation e a = Gen.choice [fmap AccFailure e, fmap AccSuccess a]+genValidation :: Gen e -> Gen a -> Gen (Validation e a)+genValidation e a = Gen.choice [fmap Failure e, fmap Success a] -testGen :: Gen (AccValidation [String] Int)+testGen :: Gen (Validation [String] Int) testGen = let range = Range.linear 1 50 string = Gen.string range Gen.unicode strings = Gen.list range string- in genAccValidation strings Gen.enumBounded+ in genValidation strings Gen.enumBounded -mkAssoc :: (AccValidation [String] Int -> AccValidation [String] Int -> AccValidation [String] Int) -> Property+mkAssoc :: (Validation [String] Int -> Validation [String] Int -> Validation [String] Int) -> Property mkAssoc f = let g = forAll testGen assoc = \x y z -> ((x `f` y) `f` z) === (x `f` (y `f` z))
test/hunit_tests.hs view
@@ -9,7 +9,7 @@ import Control.Monad (when) import Data.Foldable (length) import Data.Proxy (Proxy (Proxy))-import Data.Validation (AccValidation (AccSuccess, AccFailure), Validate, _Failure, _Success, ensure,+import Data.Validation (Validation (Success, Failure), Validate, _Failure, _Success, ensure, orElse, validate, validation, validationNel) import System.Exit (exitFailure) @@ -21,26 +21,26 @@ testYY :: Test testYY =- let subject = _Success # (+1) <*> _Success # seven :: AccValidation String Int- expected = AccSuccess 8+ let subject = _Success # (+1) <*> _Success # seven :: Validation String Int+ expected = Success 8 in TestCase (assertEqual "Success <*> Success" subject expected) testNY :: Test testNY =- let subject = _Failure # ["f1"] <*> _Success # seven :: AccValidation [String] Int- expected = AccFailure ["f1"]+ let subject = _Failure # ["f1"] <*> _Success # seven :: Validation [String] Int+ expected = Failure ["f1"] in TestCase (assertEqual "Failure <*> Success" subject expected) testYN :: Test testYN =- let subject = _Success # (+1) <*> _Failure # ["f2"] :: AccValidation [String] Int- expected = AccFailure ["f2"]+ let subject = _Success # (+1) <*> _Failure # ["f2"] :: Validation [String] Int+ expected = Failure ["f2"] in TestCase (assertEqual "Success <*> Failure" subject expected) testNN :: Test testNN =- let subject = _Failure # ["f1"] <*> _Failure # ["f2"] :: AccValidation [String] Int- expected = AccFailure ["f1","f2"]+ let subject = _Failure # ["f1"] <*> _Failure # ["f2"] :: Validation [String] Int+ expected = Failure ["f1","f2"] in TestCase (assertEqual "Failure <*> Failure" subject expected) testValidationNel :: Test@@ -87,20 +87,20 @@ testValidateTrue :: Test testValidateTrue = let subject = validate three (const True) seven- expected = AccSuccess seven+ expected = Success seven in TestCase (assertEqual "testValidateTrue" subject expected) testValidateFalse :: Test testValidateFalse = let subject = validate three (const False) seven- expected = AccFailure three+ expected = Failure three in TestCase (assertEqual "testValidateFalse" subject expected) tests :: Test tests = let eitherP :: Proxy Either eitherP = Proxy- validationP :: Proxy AccValidation+ validationP :: Proxy Validation validationP = Proxy generals :: forall v. (Validate v, Eq (v Int Int), Show (v Int Int)) => [Proxy v -> Test] generals =
validation.cabal view
@@ -1,5 +1,5 @@ name: validation-version: 0.6.3+version: 1 license: BSD3 license-file: LICENCE author: Tony Morris <ʇǝu˙sıɹɹoɯʇ@ןןǝʞsɐɥ> <dibblego>, Nick Partridge <nkpart>@@ -19,15 +19,15 @@ `lens`-related functions for converting between each and abstracting over their similarities. .- * `AccValidation`+ * `Validation` .- The `AccValidation` data type is isomorphic to `Either`, but has an instance+ The `Validation` data type is isomorphic to `Either`, but has an instance of `Applicative` that accumulates on the error side. That is to say, if two (or more) errors are encountered, they are appended using a `Semigroup` operation. . As a consequence of this `Applicative` instance, there is no corresponding- `Bind` or `Monad` instance. `AccValidation` is an example of, "An applicative+ `Bind` or `Monad` instance. `Validation` is an example of, "An applicative functor that is not a monad." homepage: https://github.com/qfpl/validation@@ -46,7 +46,7 @@ Haskell2010 build-depends:- base >= 3 && < 5+ base >= 4.5 && < 5 , deepseq >= 1.2 && < 1.5 , semigroups >= 0.8 && < 1 , semigroupoids >= 5 && < 6