prosidyc-0.3.0.0: src/Data/Either/Valid.hs
{-|
Module : Data.Either.Valid
Description : 'Either', but accumulates its errors.
Copyright : ©2020 James Alexander Feldman-Crough
License : MPL-2.0
Maintainer : alex@fldcr.com
-}
{-# LANGUAGE LambdaCase #-}
module Data.Either.Valid
( Valid(..)
, fromEither
, toEither
, valid
) where
import Data.Bifunctor (Bifunctor(..))
import Control.Applicative (Alternative(..))
-- | Like the 'Either' type, but its instances accumulates its errors. As such,
-- there is no 'Monad' instance for 'Valid'.
--
-- The 'Invalid' constructor takes precedence over 'Valid' when used with
-- classes that combine two values.
--
-- Note: There are a /lot/ of packages that implement this data type, but
-- finding a well-maintained one with minimal dependencies proved difficult.
data Valid e a =
Invalid !e
| Valid !a
deriving (Eq, Show, Ord)
instance (Semigroup e, Semigroup a) => Semigroup (Valid e a) where
Valid lhs <> Valid rhs = Valid (lhs <> rhs)
Invalid lhs <> Invalid rhs = Invalid (lhs <> rhs)
lhs@Invalid{} <> _ = lhs
_ <> rhs = rhs
instance (Semigroup e, Monoid a) => Monoid (Valid e a) where
mempty = Valid mempty
instance Bifunctor Valid where
bimap f g = valid (Invalid . f) (Valid . g)
{-# INLINABLE bimap #-}
instance Functor (Valid e) where
fmap = second
{-# INLINE fmap #-}
instance Semigroup e => Applicative (Valid e) where
pure = Valid
Valid fn <*> Valid x = Valid (fn x)
Invalid lhs <*> Invalid rhs = Invalid (lhs <> rhs)
Invalid lhs <*> _ = Invalid lhs
_ <*> Invalid rhs = Invalid rhs
instance Monoid e => Alternative (Valid e) where
empty = Invalid mempty
lhs@Valid{} <|> _ = lhs
Invalid lhs <|> Invalid rhs = Invalid (lhs <> rhs)
Invalid{} <|> rhs = rhs
-- | Convert an 'Either' value to 'Valid'.
fromEither :: Either e a -> Valid e a
fromEither = either Invalid Valid
{-# INLINE fromEither #-}
-- | Convert a 'Valid' value to 'Either'.
toEither :: Valid e a -> Either e a
toEither = valid Left Right
{-# INLINE toEither #-}
-- | Consume a 'Valid' by handling errors and valid values.
valid :: (e -> r) -> (a -> r) -> Valid e a -> r
valid l r = \case
Invalid e -> l e
Valid a -> r a
{-# INLINE valid #-}