validation-1.3.0: src/Data/Validation/Validation.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_GHC -Wall #-}
-- \$setup
-- >>> import Prelude hiding (either, id, (.))
-- >>> import Control.Lens((^?), (#), review, view, from, set)
-- >>> import Data.Functor.Alt(Alt((<!>)))
-- >>> import Data.Functor.Apply(Apply((<.>)))
-- >>> import Control.DeepSeq(rnf)
-- >>> import Control.Category(id, (.))
-- >>> import Control.Selective(Selective(select))
-- >>> import Data.Bifunctor(Bifunctor(bimap))
-- >>> import Data.Bifoldable(Bifoldable(bifoldr))
-- >>> import Data.Bitraversable(Bitraversable(bitraverse))
-- >>> import Data.Bifunctor.Swap(Swap(swap))
-- >>> :set -XNoMonomorphismRestriction -w
-- | A data type similar to @Data.Either@ that accumulates failures.
module Data.Validation.Validation (
-- * Data type
Validation (..),
-- * Catamorphism
foldValidation,
-- * Optics
-- ** Classy lenses
GetValidation (..),
HasValidation (..),
-- ** Classy prisms
ReviewValidation (..),
AsValidation (..),
-- ** Prisms
__Failure,
__Success,
-- ** Isomorphisms
Data.Validation.Validation.either,
codiagonal,
) where
import Control.Applicative (Alternative (empty, (<|>)))
import Control.Category (Category (..))
import Control.DeepSeq (NFData (rnf))
import Control.Lens (Getter, Lens', Prism, Prism', Review, from, prism, unto)
import Control.Lens.Iso (Iso, iso)
import Control.Selective (Selective (..))
import Data.Bifoldable (Bifoldable (bifoldr))
import Data.Bifoldable1 (Bifoldable1 (bifoldMap1))
import Data.Bifunctor (Bifunctor (bimap))
import Data.Bifunctor.Assoc (Assoc (assoc, unassoc))
import Data.Bifunctor.Swap (Swap (..))
import Data.Bitraversable (Bitraversable (bitraverse))
import Data.Bool (bool)
import Data.Data (Data)
import qualified Data.Either as Either
import Data.Functor.Alt (Alt ((<!>)))
import Data.Functor.Apply (Apply ((<.>)))
import Data.Functor.Classes (Eq1 (liftEq), Eq2 (liftEq2), Ord1 (liftCompare), Ord2 (liftCompare2), Show1 (liftShowsPrec), Show2 (liftShowsPrec2), showsUnaryWith)
import Data.Functor.Extend (Extend (extended))
import Data.Functor.Plus (Plus (zero))
import Data.Semigroup.Traversable.Class (Bitraversable1 (bitraverse1))
import Data.Typeable (Typeable)
import GHC.Generics (Generic, Generic1)
import Prelude hiding (either, id, (.))
{- | 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 '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.<*>'
See the <https://github.com/system-f/validation README> for usage examples.
-}
data Validation err a
= Failure err
| Success a
deriving (Data, Eq, Generic, Generic1, Ord, Show, Typeable)
instance Eq2 Validation where
liftEq2 f _ (Failure a) (Failure b) = f a b
liftEq2 _ g (Success a) (Success b) = g a b
liftEq2 _ _ _ _ = False
{-# INLINE liftEq2 #-}
instance (Eq err) => Eq1 (Validation err) where
liftEq = liftEq2 (==)
{-# INLINE liftEq #-}
instance Ord2 Validation where
liftCompare2 f _ (Failure a) (Failure b) = f a b
liftCompare2 _ _ (Failure _) (Success _) = LT
liftCompare2 _ _ (Success _) (Failure _) = GT
liftCompare2 _ g (Success a) (Success b) = g a b
{-# INLINE liftCompare2 #-}
instance (Ord err) => Ord1 (Validation err) where
liftCompare = liftCompare2 compare
{-# INLINE liftCompare #-}
instance Show2 Validation where
liftShowsPrec2 sp1 _ _ _ d (Failure a) = showsUnaryWith sp1 "Failure" d a
liftShowsPrec2 _ _ sp2 _ d (Success a) = showsUnaryWith sp2 "Success" d a
{-# INLINE liftShowsPrec2 #-}
instance (Show err) => Show1 (Validation err) where
liftShowsPrec = liftShowsPrec2 showsPrec showList
{-# INLINE liftShowsPrec #-}
{- |
>>> fmap (+1) (Success 2 :: Validation String Int)
Success 3
>>> fmap (+1) (Failure "err" :: Validation String Int)
Failure "err"
-}
instance Functor (Validation err) where
fmap _ (Failure e) =
Failure e
fmap f (Success a) =
Success (f a)
{-# INLINE fmap #-}
{- | Accumulates errors on the left using 'Semigroup'.
>>> import Data.Functor.Apply(Apply((<.>)))
>>> Success (+1) <.> Success 2 :: Validation [String] Int
Success 3
>>> Failure ["e1"] <.> Success 2 :: Validation [String] Int
Failure ["e1"]
>>> Success (+1) <.> Failure ["e2"] :: Validation [String] Int
Failure ["e2"]
>>> Failure ["e1"] <.> Failure ["e2"] :: Validation [String] Int
Failure ["e1","e2"]
-}
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 (<.>) #-}
{- | Delegates to the 'Apply' instance, accumulating errors with '<>'.
>>> pure (+1) <*> pure 2 :: Validation [String] Int
Success 3
>>> Failure ["e1"] <*> Failure ["e2"] :: Validation [String] Int
Failure ["e1","e2"]
-}
instance (Semigroup err) => Applicative (Validation err) where
pure =
Success
{-# INLINE pure #-}
(<*>) =
(<.>)
{-# INLINE (<*>) #-}
{- | Tries the left, then the right, accumulating errors on two failures.
>>> import Data.Functor.Alt(Alt((<!>)))
>>> Success 1 <!> Success 2 :: Validation [String] Int
Success 1
>>> Failure ["e1"] <!> Success 2 :: Validation [String] Int
Success 2
>>> Success 1 <!> Failure ["e2"] :: Validation [String] Int
Success 1
>>> Failure ["e1"] <!> Failure ["e2"] :: Validation [String] Int
Failure ["e1","e2"]
-}
instance (Semigroup err) => Alt (Validation err) where
Failure e1 <!> Failure e2 =
Failure (e1 <> e2)
Failure _ <!> Success a =
Success a
Success a <!> _ =
Success a
{-# INLINE (<!>) #-}
instance (Monoid err) => Plus (Validation err) where
zero = Failure mempty
{-# INLINE zero #-}
instance (Monoid err) => Alternative (Validation err) where
empty = zero
{-# INLINE empty #-}
(<|>) = (<!>)
{-# INLINE (<|>) #-}
{- | Skips the second effect on 'Failure'.
>>> import Control.Selective(Selective(select))
>>> select (Success (Right 1)) (Success (+1)) :: Validation [String] Int
Success 1
>>> select (Success (Left 1)) (Success (+1)) :: Validation [String] Int
Success 2
>>> select (Failure ["e1"]) (Success (+1)) :: Validation [String] Int
Failure ["e1"]
>>> select (Failure ["e1"]) (Failure ["e2"]) :: Validation [String] Int
Failure ["e1"]
-}
instance (Semigroup err) => Selective (Validation err) where
select (Failure e) _ = Failure e
select (Success x) f = Either.either (\a -> ($ a) <$> f) Success x
{-# INLINE select #-}
{- |
>>> foldr (:) [] (Success 1 :: Validation String Int)
[1]
>>> foldr (:) [] (Failure "err" :: Validation String Int)
[]
-}
instance Foldable (Validation err) where
foldr f x (Success a) =
f a x
foldr _ x (Failure _) =
x
{-# INLINE foldr #-}
{- |
>>> traverse (\x -> [x, x+1]) (Success 1 :: Validation String Int)
[Success 1,Success 2]
>>> traverse (\x -> [x, x+1]) (Failure "err" :: Validation String Int)
[Failure "err"]
-}
instance Traversable (Validation err) where
traverse f (Success a) =
Success <$> f a
traverse _ (Failure e) =
pure (Failure e)
{-# INLINE traverse #-}
{- |
>>> import Data.Bifunctor(Bifunctor(bimap))
>>> bimap show (+1) (Failure 1 :: Validation Int Int)
Failure "1"
>>> bimap show (+1) (Success 1 :: Validation Int Int)
Success 2
-}
instance Bifunctor Validation where
bimap f _ (Failure e) =
Failure (f e)
bimap _ g (Success a) =
Success (g a)
{-# INLINE bimap #-}
{- |
>>> import Data.Bifoldable(Bifoldable(bifoldr))
>>> bifoldr (\e r -> show e ++ r) (\a r -> show a ++ r) "" (Failure 1 :: Validation Int Int)
"1"
>>> bifoldr (\e r -> show e ++ r) (\a r -> show a ++ r) "" (Success 2 :: Validation Int Int)
"2"
-}
instance Bifoldable Validation where
bifoldr _ g x (Success a) =
g a x
bifoldr f _ x (Failure e) =
f e x
{-# INLINE bifoldr #-}
instance Bifoldable1 Validation where
bifoldMap1 f _ (Failure e) = f e
bifoldMap1 _ g (Success a) = g a
{-# INLINE bifoldMap1 #-}
{- |
>>> import Data.Bitraversable(Bitraversable(bitraverse))
>>> bitraverse (\e -> [e, e+1]) (\a -> [a, a*2]) (Failure 1 :: Validation Int Int)
[Failure 1,Failure 2]
>>> bitraverse (\e -> [e, e+1]) (\a -> [a, a*2]) (Success 3 :: Validation Int Int)
[Success 3,Success 6]
-}
instance Bitraversable Validation where
bitraverse _ g (Success a) =
Success <$> g a
bitraverse f _ (Failure e) =
Failure <$> f e
{-# INLINE bitraverse #-}
instance Bitraversable1 Validation where
bitraverse1 f _ (Failure e) = Failure <$> f e
bitraverse1 _ g (Success a) = Success <$> g a
{-# INLINE bitraverse1 #-}
{- | First 'Success' wins; two 'Failure's are combined with '<>'.
>>> Failure ["e1"] <> Failure ["e2"] :: Validation [String] Int
Failure ["e1","e2"]
>>> Failure ["e1"] <> Success 2 :: Validation [String] Int
Success 2
>>> Success 1 <> Failure ["e2"] :: Validation [String] Int
Success 1
>>> Success 1 <> Success 2 :: Validation [String] Int
Success 1
-}
instance (Semigroup e) => Semigroup (Validation e a) where
Failure e1 <> Failure e2 = Failure (e1 <> e2)
Failure _ <> Success a = Success a
Success a <> _ = Success a
{-# INLINE (<>) #-}
{- |
>>> mempty :: Validation [String] Int
Failure []
-}
instance (Monoid e) => Monoid (Validation e a) where
mempty =
Failure mempty
{-# INLINE mempty #-}
{- |
>>> import Data.Bifunctor.Swap(Swap(swap))
>>> swap (Failure "err" :: Validation String Int)
Success "err"
>>> swap (Success 1 :: Validation String Int)
Failure 1
-}
instance Swap Validation where
swap v =
case v of
Failure e -> Success e
Success a -> Failure a
{-# INLINE swap #-}
instance Assoc Validation where
assoc (Failure (Failure a)) = Failure a
assoc (Failure (Success b)) = Success (Failure b)
assoc (Success c) = Success (Success c)
{-# INLINE assoc #-}
unassoc (Failure a) = Failure (Failure a)
unassoc (Success (Failure b)) = Failure (Success b)
unassoc (Success (Success c)) = Success c
{-# INLINE unassoc #-}
{- |
>>> import Control.DeepSeq(rnf)
>>> rnf (Success 1 :: Validation String Int)
()
>>> rnf (Failure "err" :: Validation String Int)
()
-}
instance (NFData e, NFData a) => NFData (Validation e a) where
rnf v =
case v of
Failure e -> rnf e
Success a -> rnf a
{-# INLINE rnf #-}
instance Extend (Validation err) where
extended _ (Failure e) = Failure e
extended f w@(Success _) = Success (f w)
{-# INLINE extended #-}
{- | Catamorphism for 'Validation'.
>>> foldValidation show show (Failure 1 :: Validation Int Int)
"1"
>>> foldValidation show show (Success 2 :: Validation Int Int)
"2"
-}
foldValidation :: (a -> x) -> (b -> x) -> Validation a b -> x
foldValidation f _ (Failure a) = f a
foldValidation _ s (Success b) = s b
{-# INLINE foldValidation #-}
{- | Polymorphic 'Prism' targeting the 'Failure' constructor.
>>> import Control.Lens((^?), review)
>>> review __Failure "err" :: Validation String Int
Failure "err"
>>> (Failure "err" :: Validation String Int) ^? __Failure
Just "err"
>>> (Success 1 :: Validation String Int) ^? __Failure
Nothing
-}
__Failure :: Prism (Validation a b) (Validation a' b) a a'
__Failure =
prism
Failure
( \case
Failure a -> Right a
Success b -> Left (Success b)
)
{-# INLINE __Failure #-}
{- | Polymorphic 'Prism' targeting the 'Success' constructor.
>>> import Control.Lens((^?), review)
>>> review __Success 1 :: Validation String Int
Success 1
>>> (Success 1 :: Validation String Int) ^? __Success
Just 1
>>> (Failure "err" :: Validation String Int) ^? __Success
Nothing
-}
__Success :: Prism (Validation a b) (Validation a b') b b'
__Success =
prism
Success
( \case
Failure a -> Left (Failure a)
Success b -> Right b
)
{-# INLINE __Success #-}
{- | Isomorphism between 'Validation' and 'Either'.
>>> import Control.Lens(view)
>>> view either (Failure "err" :: Validation String Int)
Left "err"
>>> view either (Success 1 :: Validation String Int)
Right 1
-}
either :: Iso (Validation a b) (Validation a' b') (Either a b) (Either a' b')
either =
iso
(foldValidation Left Right)
(Either.either Failure Success)
{-# INLINE either #-}
{- | Isomorphism between @Validation a a@ and @(Bool, a)@, where 'False' corresponds to 'Failure'.
>>> import Control.Lens(view)
>>> view codiagonal (Failure "x" :: Validation String String)
(False,"x")
>>> view codiagonal (Success "x" :: Validation String String)
(True,"x")
-}
codiagonal :: Iso (Validation a a) (Validation a' a') (Bool, a) (Bool, a')
codiagonal =
iso
(foldValidation (False,) (True,))
(\(p, a) -> bool (Failure a) (Success a) p)
{-# INLINE codiagonal #-}
-- | Class for types that have a 'Getter' to a 'Validation'.
class GetValidation s err a | s -> err a where
getValidation :: Getter s (Validation err a)
instance GetValidation (Validation err a) err a where
getValidation = id
{-# INLINE getValidation #-}
{- |
>>> import Control.Lens(view)
>>> view getValidation (Left "err" :: Either String Int)
Failure "err"
>>> view getValidation (Right 1 :: Either String Int)
Success 1
-}
instance GetValidation (Either err a) err a where
getValidation = from Data.Validation.Validation.either
{-# INLINE getValidation #-}
-- | Class for types that have a 'Lens'' to a 'Validation' (as generated by @makeClassy@).
class (GetValidation s err a) => HasValidation s err a | s -> err a where
validation :: Lens' s (Validation err a)
instance HasValidation (Validation err a) err a where
validation = id
{-# INLINE validation #-}
{- |
>>> import Control.Lens(view, set)
>>> view validation (Left "err" :: Either String Int)
Failure "err"
>>> set validation (Success 2 :: Validation String Int) (Left "err" :: Either String Int)
Right 2
-}
instance HasValidation (Either err a) err a where
validation = from Data.Validation.Validation.either
{-# INLINE validation #-}
-- | Class for types that have a 'Review' to a 'Validation'.
class ReviewValidation s err a | s -> err a where
reviewValidation :: Review s (Validation err a)
reviewFailure :: Review s err
reviewFailure = reviewValidation . reviewFailure
{-# INLINE reviewFailure #-}
reviewSuccess :: Review s a
reviewSuccess = reviewValidation . reviewSuccess
{-# INLINE reviewSuccess #-}
instance ReviewValidation (Validation err a) err a where
reviewValidation = id
{-# INLINE reviewValidation #-}
reviewFailure = unto Failure
{-# INLINE reviewFailure #-}
reviewSuccess = unto Success
{-# INLINE reviewSuccess #-}
{- |
>>> import Control.Lens((#))
>>> reviewValidation # (Failure "err" :: Validation String Int) :: Either String Int
Left "err"
>>> reviewValidation # (Success 1 :: Validation String Int) :: Either String Int
Right 1
-}
instance ReviewValidation (Either err a) err a where
reviewValidation = from Data.Validation.Validation.either
{-# INLINE reviewValidation #-}
-- | Class for types that have a 'Prism'' to a 'Validation' (as generated by @makeClassyPrisms@).
class (ReviewValidation s err a) => AsValidation s err a | s -> err a where
_Validation :: Prism' s (Validation err a)
_Failure :: Prism' s err
_Failure = _Validation . _Failure
{-# INLINE _Failure #-}
_Success :: Prism' s a
_Success = _Validation . _Success
{-# INLINE _Success #-}
instance AsValidation (Validation err a) err a where
_Validation = id
{-# INLINE _Validation #-}
_Failure = __Failure
{-# INLINE _Failure #-}
_Success = __Success
{-# INLINE _Success #-}
{- |
>>> import Control.Lens((^?), (#))
>>> _Validation # (Failure "err" :: Validation String Int) :: Either String Int
Left "err"
>>> (Left "err" :: Either String Int) ^? _Validation
Just (Failure "err")
>>> (Right 1 :: Either String Int) ^? _Validation
Just (Success 1)
-}
instance AsValidation (Either err a) err a where
_Validation = from Data.Validation.Validation.either
{-# INLINE _Validation #-}