vary-0.1.1.2: src/Vary/VEither.hs
{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
module Vary.VEither (
-- * General Usage
-- $setup
-- * Core type definition
VEither(VLeft, VRight),
-- * Conversion
toVary,
fromVary,
fromLeft,
fromRight,
toEither,
fromEither,
veither,
intoOnly,
-- * case analysis ("pattern matching"):
-- |
--
-- Besides the 'VLeft' and 'VRight' patterns,
-- 'VEither' supports a bunch of handy combinator functions,
-- similar to "Vary".'Vary.on' and co.
onLeft,
onRight,
handle,
-- * Transforming
mapLeftOn,
mapLeft,
mapRight,
morph,
morphed,
) where
import Control.Category ((>>>))
import Control.DeepSeq (NFData (..))
import qualified Data.Either
import Data.Kind (Type)
import Vary.Core (Vary(..))
import Vary.Utils (Subset, Mappable)
import Vary ((:|))
import qualified Vary
import GHC.Generics
# ifdef FLAG_AESON
import qualified Data.Aeson as Aeson
# endif
# ifdef FLAG_HASHABLE
import Data.Hashable
# endif
# ifdef FLAG_QUICKCHECK
import Test.QuickCheck
# endif
# ifdef FLAG_CEREAL
import qualified Data.Serialize as Cereal
# endif
# ifdef FLAG_BINARY
import qualified Data.Binary as Binary
# endif
-- $setup
--
-- This module is intended to be used qualified:
--
-- >>> import Vary.VEither (VEither(VLeft, VRight))
-- >>> import qualified Vary.VEither as VEither
--
-- And for many functions, it is useful or outright necessary to enable the following extensions:
--
-- >>> :set -XDataKinds
-- >>> -- As of GHC >= 9.2, you can replace the following three extensions by GHC2021 instead:
-- >>> {-# LANGUAGE FlexibleContexts #-}
-- >>> {-# LANGUAGE TypeApplications #-}
-- >>> {-# LANGUAGE TypeOperators #-}
--
-- Finally, some example snippets in this module make use of 'Data.Function.&', the left-to-right function application operator.
--
-- >>> import Data.Function ((&))
newtype VEither (errs :: [Type]) a = VEither (Vary (a : errs))
-- | Turns the 'VEither' into a normal Vary, no longer considering the @a@ a \'preferred\' value.
--
-- In many cases, you probably want to mattern match on "VEither".'VLeft' instead!
toVary :: VEither errs a -> Vary (a : errs)
{-# INLINE toVary #-}
toVary (VEither vary) = vary
-- | Turns a 'Vary' into a 'VEither'. Now the @a@ is considered the \'preferred\' value.
--
-- In many cases, you probably want to use "VEither".'VLeft' instead!
fromVary :: Vary (a : errs) -> VEither errs a
{-# INLINE fromVary #-}
fromVary vary = VEither vary
-- | Turns a 'VEither' into a normal 'Either'.
toEither :: VEither errs a -> Either (Vary errs) a
{-# INLINE toEither #-}
toEither = toVary >>> Vary.pop
-- | Turns a normal 'Either' into a 'VEither'.
fromEither :: Either (Vary errs) a -> VEither errs a
{-# INLINE fromEither #-}
fromEither = Data.Either.either Vary.morph Vary.from >>> fromVary
-- | Shorthand to construct a 'VEither' from a single error value.
--
-- Instead of:
--
-- >>> (VLeft (Vary.from @Bool True)) :: VEither '[Bool] String
-- VLeft (Vary.from @Bool True)
--
-- You can just write:
--
-- >>> VEither.fromLeft @Bool True :: VEither '[Bool] String
-- VLeft (Vary.from @Bool True)
fromLeft :: forall err errs a. err :| errs => err -> VEither errs a
{-# INLINE fromLeft #-}
fromLeft = Vary.from @err >>> VLeft
-- | Construct a 'VEither' from an @a@.
--
-- Exists for symmetry with 'fromLeft'.
-- Indeed, this is just another name for 'VRight' (and for 'pure').
fromRight :: forall a errs. a -> VEither errs a
{-# INLINE fromRight #-}
fromRight = VRight
-- | Case analysis on a 'VEither'. Similar to 'Data.Either.either'.
--
-- See also "VEither".'mapLeft', "VEither".'mapLeftOn' and "VEither".'mapRight'.
veither :: (Vary errs -> c) -> (a -> c) -> VEither errs a -> c
{-# INLINE veither #-}
veither f _ (VLeft x) = f x
veither _ g (VRight y) = g y
{-# COMPLETE VLeft, VRight #-}
-- Matches when the VEither contains one of the errors, returning @Vary errs@
pattern VLeft :: forall a errs. Vary errs -> VEither errs a
#if __GLASGOW_HASKELL__ >= 902
{-# INLINE VLeft #-}
#endif
pattern VLeft errs <- (toEither -> Left errs)
where
VLeft (Vary tag err) = VEither ((Vary (tag+1) err))
-- | Matches when the VEither contains the preferred value of type @a@.
pattern VRight :: forall a errs. a -> VEither errs a
#if __GLASGOW_HASKELL__ >= 902
{-# INLINE VRight #-}
#endif
pattern VRight a <- (toEither -> Right a)
where
VRight a = VEither (Vary.from @a a)
-- | Handle a particular error possibility.
--
-- Works very similarly to "Vary".'Vary.on'.
onLeft :: forall err b errs a. (err -> b) -> (VEither errs a -> b) -> VEither (err : errs) a -> b
{-# INLINE onLeft #-}
onLeft thiserrFun restfun ve = case ve of
VLeft e -> Vary.on @err thiserrFun (\otherErr -> restfun (VLeft otherErr)) e
VRight a -> restfun (VRight a)
-- | Handle the success posibility.
--
--
-- Works very similarly to "Vary".'Vary.on'.
-- Usually used together with "VError".'onLeft'.
onRight :: (a -> b) -> (VEither errs a -> b) -> VEither errs a -> b
{-# INLINE onRight #-}
onRight valfun restfun ve = case ve of
VRight a -> valfun a
VLeft err -> restfun (VLeft err)
-- | Handle a single error, by mapping it either to the success type @a@ or to one of the other errors in @errs@.
--
-- This is syntactic sugar over using "VEither".'onLeft',
-- but can be nicer to use if one or only a few error variants need to be handled,
-- because it lets you build a simple pipeline:
--
-- >>> :{
-- examplePipe ve = ve
-- & VEither.handle @Int (pure . show)
-- & VEither.handle @Bool (pure . show)
-- :}
--
-- >>> :t examplePipe
-- examplePipe
-- :: VEither (Int : Bool : errs) String -> VEither errs String
-- >>> examplePipe (VEither.fromLeft False :: VEither '[Int, Bool, Float] String)
-- VRight "False"
handle :: (err -> VEither errs a) -> VEither (err : errs) a -> VEither errs a
{-# INLINE handle #-}
handle fun = onLeft fun id
-- | If you have a VEither which does not actually contain any errors,
-- you can be sure it always contains an @a@.
--
-- Similar to "Vary".'Vary.intoOnly'.
intoOnly :: forall a. VEither '[] a -> a
{-# INLINE intoOnly #-}
intoOnly (VRight a) = a
intoOnly (VLeft emptyVary) = Vary.exhaustiveCase emptyVary
-- | Extend a smaller `VEiher` into a bigger one, change the order of its error types, or get rid of duplicate error types.
--
-- Similar to "Vary".'Vary.morph'
morph :: forall ys xs a. Subset (a : xs) (a : ys) => VEither xs a -> VEither ys a
{-# INLINE morph #-}
morph = toVary >>> Vary.morph >>> fromVary
-- | Execute a function expecting a larger (or differently-ordered) variant
-- with a smaller (or differently-ordered) variant,
-- by calling `morph` on it before running the function.
morphed :: forall xs ys a res. Subset (a : xs) (a : ys) => (VEither ys a -> res) -> VEither xs a -> res
{-# INLINE morphed #-}
morphed fun = fun . morph
-- | Map a function over one of the error values inside the 'VEither'.
--
-- Any other 'VLeft' and also 'VRight' are kept untouched.
--
-- Similar to "Vary".'Vary.mapOn'.
mapLeftOn :: forall x y xs ys a. (Mappable x y xs ys) => (x -> y) -> VEither xs a -> VEither ys a
{-# INLINE mapLeftOn #-}
mapLeftOn _ (VRight val) = VRight val
mapLeftOn fun (VLeft err) = VLeft $ Vary.mapOn fun err
-- | Map a function over the 'VEither' if it contains a 'VLeft', otherwise leave it alone.
--
-- See also "VEither".'mapLeftOn', "VEither".'mapRight' and "VEither".'veither'.
--
mapLeft :: (Vary xs -> Vary ys) -> VEither xs a -> VEither ys a
{-# INLINE mapLeft #-}
mapLeft fun ve = case ve of
VRight a -> VRight a
VLeft errs -> VLeft (fun errs)
-- | Map a function over the 'VEither' if it contains a 'VRight', otherwise leave it alone.
--
-- Exists for symmetry with "VEither".'mapLeft' and "VEither".'mapLeftOn'.
--
-- Indeed, it is just another name for 'fmap'.
--
-- See also "VEither".'veither'.
mapRight :: (x -> y) -> VEither errs x -> VEither errs y
{-# INLINE mapRight #-}
mapRight fun ve = case ve of
VRight a -> VRight (fun a)
VLeft errs -> VLeft errs
instance (Show a, Show (Vary errs)) => Show (VEither errs a) where
show (VLeft errs) = "VLeft (" <> show errs <> ")"
show (VRight a) = "VRight " <> show a
instance (Eq a, Eq (Vary errs)) => Eq (VEither errs a) where
a == b = toVary a == toVary b
instance (Ord a, Ord (Vary errs)) => Ord (VEither errs a) where
compare a b = compare (toVary a) (toVary b)
instance (NFData a, NFData (Vary errs)) => NFData (VEither errs a) where
rnf = toVary >>> rnf
instance Functor (VEither errs) where
fmap :: forall a b. (a -> b) -> VEither errs a -> VEither errs b
{-# INLINE fmap #-}
fmap = mapRight
instance Applicative (VEither errs) where
{-# INLINE pure #-}
pure = VRight
{-# INLINE (<*>) #-}
(VRight fun) <*> (VRight val) = VRight (fun val)
(VLeft err) <*> _ = (VLeft err)
_ <*> (VLeft err) = (VLeft err)
instance Monad (VEither errs) where
(>>=) :: forall a b. VEither errs a -> (a -> VEither errs b) -> VEither errs b
(VRight a) >>= fun = fun a
(VLeft err) >>= _ = (VLeft err)
instance Foldable (VEither errs) where
foldMap _ (VLeft _) = mempty
foldMap f (VRight y) = f y
foldr _ z (VLeft _) = z
foldr f z (VRight y) = f y z
length (VLeft _) = 0
length (VRight _) = 1
instance Traversable (VEither errs) where
traverse _ (VLeft x) = pure (VLeft x)
traverse f (VRight y) = VRight <$> f y
instance Semigroup (VEither errs a) where
(VRight a) <> _ = (VRight a)
_ <> b = b
-- Look! A hand-written Generic instance! ;-)
--
-- This closely follows the implementation of the normal Either,
-- and pretends the type truly is built up of VLeft and VRight
instance Generic (VEither errs a) where
type (Rep (VEither errs a)) = D1
(MetaData "VEither" "Vary.VEither" "vary" False)
(C1
(MetaCons "VLeft" PrefixI False)
(S1
(MetaSel
Nothing NoSourceUnpackedness NoSourceStrictness DecidedLazy)
(Rec0 (Vary errs)))
:+: C1
(MetaCons "VRight" PrefixI False)
(S1
(MetaSel
Nothing NoSourceUnpackedness NoSourceStrictness DecidedLazy)
(Rec0 a)))
from :: VEither errs a -> Rep (VEither errs a) x
from ve =
case ve of
(VLeft err) -> M1 $ L1 $ M1 $ M1 $ K1 err
(VRight val) -> M1 $ R1 $ M1 $ M1 $ K1 val
to :: Rep (VEither errs a) x -> VEither errs a
to rep = case rep of
(M1 (L1 (M1 (M1 (K1 err))))) -> VLeft err
(M1 (R1 (M1 (M1 (K1 val))))) -> VRight val
-- Conceptually VEither is a Bifunctor,
-- but the kind does not align :-(
-- p has to be Type -> Type -> Type
-- But in the case of VEither it is [Type] -> Type -> Type
--
-- instance Bifunctor VEither where
-- first = mapLeft
-- second = mapRight
-- bimap = veither
#ifdef FLAG_HASHABLE
instance (Hashable a, Hashable (Vary errs), (Eq (VEither errs a))) => Hashable (VEither errs a)
#endif
#ifdef FLAG_AESON
deriving instance Aeson.ToJSON (Vary (a : errs)) => Aeson.ToJSON (VEither errs a)
deriving instance Aeson.FromJSON (Vary (a : errs)) => Aeson.FromJSON (VEither errs a)
#endif
#ifdef FLAG_QUICKCHECK
deriving instance (Arbitrary (Vary (a : errs))) => Test.QuickCheck.Arbitrary (VEither errs a)
#endif
#ifdef FLAG_CEREAL
deriving instance (Cereal.Serialize (Vary (a : errs))) => Cereal.Serialize (VEither errs a)
#endif
#ifdef FLAG_BINARY
deriving instance (Binary.Binary (Vary (a : errs))) => Binary.Binary (VEither errs a)
#endif