vary 0.1.0.3 → 0.1.0.5
raw patch · 7 files changed
+422/−61 lines, 7 filesdep +QuickCheckdep +aesondep +hashabledep ~deepseqPVP: minor bump suggested
API additions: PVP suggests at least a minor version bump
Dependencies added: QuickCheck, aeson, hashable
Dependency ranges changed: deepseq
API changes (from Hackage documentation)
+ Vary.VEither: handle :: (err -> VEither errs a) -> VEither (err : errs) a -> VEither errs a
+ Vary.VEither: instance (Data.Hashable.Class.Hashable a, Data.Hashable.Class.Hashable (Vary.Core.Vary errs), GHC.Classes.Eq (Vary.VEither.VEither errs a)) => Data.Hashable.Class.Hashable (Vary.VEither.VEither errs a)
+ Vary.VEither: instance Data.Aeson.Types.FromJSON.FromJSON (Vary.Core.Vary (a : errs)) => Data.Aeson.Types.FromJSON.FromJSON (Vary.VEither.VEither errs a)
+ Vary.VEither: instance Data.Aeson.Types.ToJSON.ToJSON (Vary.Core.Vary (a : errs)) => Data.Aeson.Types.ToJSON.ToJSON (Vary.VEither.VEither errs a)
+ Vary.VEither: instance Test.QuickCheck.Arbitrary.Arbitrary (Vary.Core.Vary (a : errs)) => Test.QuickCheck.Arbitrary.Arbitrary (Vary.VEither.VEither errs a)
Files
- CHANGELOG.md +19/−1
- README.md +20/−3
- src/Vary.hs +3/−1
- src/Vary/Core.hs +201/−43
- src/Vary/VEither.hs +88/−4
- test/README.lhs +20/−3
- vary.cabal +71/−6
CHANGELOG.md view
@@ -8,4 +8,22 @@ ## Unreleased -## 0.1.0.0 - YYYY-MM-DD+## 0.1.0.5 - 2025-02-05++- Relax max allowed versions of DeepSeq (<1.6), Hashable (<1.6), QuickCheck (<2.16)++## 0.1.0.4 - 2024-01-15++- Improved test coverage for exception cases; README is now fully tested using Literate Haskell.++## 0.1.0.3 - 2024-01-14++- Fix a typographic error in the documentation++## 0.1.0.2 - 2024-01-14++- Improve documentation++## 0.1.0.0 - 2024-01-13++- Initial version
README.md view
@@ -9,10 +9,23 @@ Just like tuples are a version of a user-defined product type (only without the field names), a Variant is a version of a user-defined sum type (but without the field names). -Variant types are the generalization of `Either`. Especially in the situation where you want to handle multiple errors, Variant types are a great abstraction to use.+In other words: Variant types are the generalization of `Either` to more (or less) than two alternatives. +| Product: | Sum: |+|--------------|--------------------------|+| () | Vary [] / Void |+| Solo a | Vary [a] |+| (a, b) | Vary [a, b] / Either a b |+| (a, b, c) | Vary [a, b, c] |+| (a, b, c, d) | Vary [a, b, c, d] |+| (...) | Vary [...] |++Especially when doing error handling (both in pure code and in exception-heavy code), Variant types are a great abstraction to use.+ Variant types are sometimes called '_polymorphic_ variants' for disambiguation. They are also commonly known as (open) unions, coproducts or extensible sums. +Vary is lightweight on dependencies. With all library flags turned off, it only depends on `base` and `deepseq`.+ ## General Usage ### Setup@@ -103,6 +116,7 @@ * This can fail, because the downloaded file might turn out actually not to be a valid image file (PNG or JPG); * Or even if the downloaded file /is/ an image, it might have a much too high resolution to attempt to read; +_(NOTE: For simplicity, we pretend everything is a pure function rather than using IO or some more fancy effect stack in the examples below.)_ The first instinct might be to write dedicated sum types for these errors like so: @@ -235,10 +249,11 @@ ```haskell thumbnailServiceRetry :: String -> VEither [IncorrectUrl2, NotAnImage2, TooBigImage2] Image thumbnailServiceRetry url = do- image <- download url - & VEither.onLeft (\ServerUnreachable2 -> waitAndRetry 10 (\_ -> thumbnailServiceRetry url)) id+ image <- VEither.handle @ServerUnreachable2 retry $ download url thumb <- thumbnail image pure thumb+ where+ retry _err = waitAndRetry 10 (\_ -> thumbnailServiceRetry url) ``` - No more wrapper type definitions!@@ -276,6 +291,8 @@ - Only the most widely-useful functions are provided in `Vary` itself. There are some extra functions in `Vary.Utils` which are intentionally left out of the main module to make it more digestible for new users. - Libraries are already many years old (with no newer updates), and so they are not using any of the newer GHC extensions or inference improvements. - `Vary` makes great use of the `GHC2021` group of extensions, TypeFamilies and the `TypeError` construct to make most type errors disappear and for the few that remain it should be easy to understand how to fix them.+- None of the libraries make an attempt to work well with Haskell's exception mechanisms.+ - `Vary` [has excellent support to be thrown and caught as exceptions](https://hackage.haskell.org/package/vary/docs/Vary.html#vary_and_exceptions). ## Acknowledgements
src/Vary.hs view
@@ -20,6 +20,7 @@ {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE NoStarIsType #-}+{-# LANGUAGE EmptyCase #-} module Vary ( -- * General Usage@@ -364,7 +365,7 @@ -- & Vary.intoOnly -- String -- :} -- "True"---+ -- Note that if you end up handling all cases of a variant, you might prefer using `Vary.on` and `Vary.exhaustiveCase` instead. -- -- == Generic code@@ -391,3 +392,4 @@ case into @a vary of Just a -> from @b (fun a) Nothing -> (Vary tag val)+
src/Vary/Core.hs view
@@ -1,17 +1,35 @@ {-# LANGUAGE GHC2021 #-}-{-# OPTIONS_HADDOCK not-home #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE MonoLocalBinds #-}+{-# OPTIONS_HADDOCK not-home #-}+ module Vary.Core (Vary (..), pop) where -import Data.Kind (Type)-import GHC.Exts (Any)-import qualified Unsafe.Coerce as Data.Coerce+import Control.Applicative ((<|>)) import Control.DeepSeq (NFData (..))-import Control.Exception (Exception(..))+import Control.Exception (Exception (..))+import Data.Kind (Type) import Data.Typeable (Typeable, typeOf)+import GHC.Exts (Any)+import GHC.Generics+import Unsafe.Coerce qualified as Data.Coerce +# ifdef FLAG_AESON+import Data.Aeson qualified as Aeson+# endif++# ifdef FLAG_HASHABLE+import Data.Hashable+# endif++# ifdef FLAG_QUICKCHECK+import Test.QuickCheck+import Test.QuickCheck.Arbitrary (GSubterms, RecursivelyShrink)+# endif+ -- $setup -- >>> :set -XGHC2021 -- >>> :set -XDataKinds@@ -40,7 +58,6 @@ emptyVaryError :: forall anything. String -> Vary '[] -> anything emptyVaryError name = error (name <> " was called on empty Vary '[]") - -- | Attempts to extract a value of the first type from the `Vary`. -- -- If this failed, we know it has to be one of the other possibilities.@@ -60,31 +77,39 @@ -- > Right val -> "Vary.from " <> show val -- > Left other -> show other ----- To go the other way: +-- To go the other way: -- -- - Use "Vary".`Vary.morph` to turn @Vary as@ back into @Vary (a : as)@ -- - Use "Vary".`Vary.from` to turn @a@ back into @Vary (a : as)@ pop :: Vary (a : as) -> Either (Vary as) a {-# INLINE pop #-} pop (Vary 0 val) = Right (Data.Coerce.unsafeCoerce val)-pop (Vary tag val) = Left (Data.Coerce.unsafeCoerce (Vary (tag - 1) val))+pop (Vary tag inner) = Left (Vary (tag - 1) inner) +pushHead :: a -> Vary (a : as)+{-# INLINE pushHead #-}+pushHead val = Vary 0 (Data.Coerce.unsafeCoerce val)++{-# INLINE pushTail #-}+pushTail :: Vary as -> Vary (a : as)+pushTail (Vary tag inner) = Vary (tag + 1) inner+ instance Eq (Vary '[]) where (==) = emptyVaryError "Eq.(==)" instance (Eq a, Eq (Vary as)) => Eq (Vary (a : as)) where- {-# INLINE (==) #-}- a == b = pop a == pop b+ {-# INLINE (==) #-}+ a == b = pop a == pop b instance Ord (Vary '[]) where- compare = emptyVaryError "Ord.compare"+ compare = emptyVaryError "Ord.compare" instance (Ord a, Ord (Vary as)) => Ord (Vary (a : as)) where- {-# INLINE compare #-}- l `compare` r = pop l `compare` pop r+ {-# INLINE compare #-}+ l `compare` r = pop l `compare` pop r instance Show (Vary '[]) where- show = emptyVaryError "Show.show"+ show = emptyVaryError "Show.show" -- | `Vary`'s 'Show' instance only works for types which are 'Typeable' --@@ -97,40 +122,173 @@ -- >>> Vary.from @(Maybe Int) (Just 1234) :: Vary '[Maybe Int, Bool] -- Vary.from @(Maybe Int) (Just 1234) instance (Typeable a, Show a, Show (Vary as)) => Show (Vary (a : as)) where- showsPrec d vary = case pop vary of- Right val ->- showString "Vary.from " . - showString "@" . - showsPrec (d+10) (typeOf val) . - showString " " . - showsPrec (d+11) val- Left other -> showsPrec d other+ showsPrec d vary = case pop vary of+ Right val ->+ showString "Vary.from "+ . showString "@"+ . showsPrec (d + 10) (typeOf val)+ . showString " "+ . showsPrec (d + 11) val+ Left other -> showsPrec d other instance NFData (Vary '[]) where- rnf = emptyVaryError "NFData.rnf"+ rnf = emptyVaryError "NFData.rnf" instance (NFData a, NFData (Vary as)) => NFData (Vary (a : as)) where- {-# INLINE rnf #-}- rnf vary = rnf (pop vary)-+ {-# INLINE rnf #-}+ rnf vary = rnf (pop vary) -instance (Typeable (Vary '[]), Show (Vary '[])) => Exception (Vary '[]) where+instance (Typeable (Vary '[]), Show (Vary '[])) => Exception (Vary '[]) -- | See [Vary and Exceptions](#vary_and_exceptions) for more info. instance (Exception e, Exception (Vary errs), Typeable errs) => Exception (Vary (e : errs)) where- displayException vary = - case pop vary of- Right val -> displayException val- Left rest -> displayException rest+ displayException vary =+ either displayException displayException (pop vary) - toException vary = - case pop vary of- Right val -> toException val- Left rest -> toException rest- - fromException some_exception = - case fromException @e some_exception of- Just e -> Just (Vary 0 (Data.Coerce.unsafeCoerce e))- Nothing -> case fromException @(Vary errs) some_exception of- Just (Vary tag err) -> Just (Data.Coerce.unsafeCoerce (Vary (tag+1) err))- Nothing -> Nothing+ toException vary =+ either toException toException (pop vary)++ fromException ex =+ (pushHead <$> fromException @e ex) <|> (pushTail <$> fromException @(Vary errs) ex)++-- case fromException @e some_exception of+-- Just e -> Just (pushHead e)+-- Nothing ->+-- case fromException @(Vary errs) some_exception of+-- Just vary -> Just (pushTail vary)+-- Nothing -> Nothing++-- Behold! A manually-written Generic instance!+--+-- This instance is very similar to the one for tuples (), (,), (,,), ...+-- but with each occurrence of :*: replaced by :+:+-- (and using `V1` instead of `U1` for the empty Vary)+type family RepHelper (list :: [Type]) :: Type -> Type where+ RepHelper '[] = V1+ RepHelper '[a] =+ S1+ ( MetaSel+ Nothing+ NoSourceUnpackedness+ NoSourceStrictness+ DecidedLazy+ )+ (K1 R a)+ RepHelper (a : b : bs) =+ S1+ (MetaSel Nothing NoSourceUnpackedness NoSourceStrictness DecidedLazy)+ (Rec0 a)+ :+: RepHelper (b : bs)++class GenericHelper (list :: [Type]) where+ fromHelper :: Vary list -> (RepHelper list) x+ toHelper :: (RepHelper list) x -> Vary list++instance GenericHelper '[] where+ fromHelper = emptyVaryError "Generic.from"+ toHelper void = case void of {}++instance GenericHelper '[a] where+ fromHelper vary = case pop vary of+ Right val -> M1 $ K1 $ val+ Left empty -> emptyVaryError "Generic.from" empty++ toHelper (M1 (K1 val)) = pushHead val++instance (GenericHelper (b : bs)) => GenericHelper (a : b : bs) where+ fromHelper vary = case pop vary of+ Right val -> L1 $ M1 $ K1 $ val+ Left rest -> R1 $ fromHelper rest++ toHelper (L1 (M1 (K1 val))) = pushHead val+ toHelper (R1 rest) = pushTail (toHelper rest)++-- | Vary '[] 's generic representation is `V1`.+instance Generic (Vary '[]) where+ type+ Rep (Vary '[]) =+ D1+ (MetaData "Vary" "Vary" "vary" False)+ (RepHelper '[])+ from = emptyVaryError "Generic.from"+ to void = case void of {}++-- | Any non-empty Vary's generic representation is encoded similar to a tuple but with `:+:` instead of `:*:`.+instance (GenericHelper (a : as)) => Generic (Vary (a : as)) where+ type+ Rep (Vary (a : as)) =+ D1+ (MetaData "Vary" "Vary" "vary" False)+ ( C1+ (MetaCons "from" PrefixI False)+ (RepHelper (a : as))+ )+ from vary = M1 $ M1 $ fromHelper vary+ to (M1 (M1 gval)) = toHelper gval++# ifdef FLAG_AESON+deriving instance Aeson.FromJSON (Vary '[])++deriving instance (Aeson.FromJSON a) => Aeson.FromJSON (Vary '[a])++instance (Aeson.FromJSON a, Aeson.FromJSON (Vary (b : bs))) => Aeson.FromJSON (Vary (a : b : bs)) where+ {-# INLINE parseJSON #-}+ parseJSON val = (pushHead <$> Aeson.parseJSON val) <|> (pushTail <$> Aeson.parseJSON val)++deriving instance Aeson.ToJSON (Vary '[])++deriving instance (Aeson.ToJSON a) => Aeson.ToJSON (Vary '[a])++instance (Aeson.ToJSON a, Aeson.ToJSON (Vary (b : bs))) => Aeson.ToJSON (Vary (a : b : bs)) where+ {-# INLINE toJSON #-}+ toJSON vary =+ either Aeson.toJSON Aeson.toJSON (pop vary)++ {-# INLINE toEncoding #-}+ toEncoding vary =+ either Aeson.toEncoding Aeson.toEncoding (pop vary)+# endif++# ifdef FLAG_QUICKCHECK+instance (Test.QuickCheck.Arbitrary a) => Test.QuickCheck.Arbitrary (Vary '[a]) where+ arbitrary = pushHead <$> arbitrary+ shrink = genericShrink++instance+ ( Arbitrary a,+ Arbitrary (Vary (b : bs)),+ Generic (Vary (a : b : bs)),+ RecursivelyShrink (Rep (Vary (a : b : bs))),+ GSubterms (Rep (Vary (a : b : bs))) (Vary (a : b : bs))+ ) =>+ Test.QuickCheck.Arbitrary (Vary (a : b : bs))+ where+ arbitrary = oneof [pushHead <$> arbitrary, pushTail <$> arbitrary]+ shrink = genericShrink+# endif++#ifdef FLAG_HASHABLE+class FastHashable a where+ badHashWithSalt :: Int -> a -> Int++instance (Hashable a) => FastHashable (Vary '[a]) where+ {-# INLINE badHashWithSalt #-}+ badHashWithSalt salt vary = case pop vary of+ Right val -> hashWithSalt salt val+ Left empty -> emptyVaryError "hashWithSalt" empty++instance (Hashable a, FastHashable (Vary (b : bs))) => FastHashable (Vary (a : b : bs)) where+ {-# INLINE badHashWithSalt #-}+ badHashWithSalt salt vary = case pop vary of+ Right val -> hashWithSalt salt val+ Left rest -> badHashWithSalt salt rest++instance+ ( Eq (Vary (a : as)),+ FastHashable (Vary (a : as))+ ) =>+ Hashable (Vary (a : as))+ where+ hashWithSalt salt vary@(Vary tag _inner) = fromIntegral tag `hashWithSalt` badHashWithSalt salt vary+ hash vary@(Vary tag _inner) = badHashWithSalt (fromIntegral tag) vary+#endif
src/Vary/VEither.hs view
@@ -3,6 +3,8 @@ {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE UndecidableInstances #-} module Vary.VEither ( -- * General Usage -- $setup@@ -28,6 +30,7 @@ -- similar to "Vary".'Vary.on' and co. onLeft, onRight,+ handle, -- * Transforming mapLeftOn,@@ -47,6 +50,18 @@ import qualified Vary import GHC.Generics +# ifdef FLAG_AESON+import Data.Aeson qualified as Aeson+# endif++# ifdef FLAG_HASHABLE+import Data.Hashable+# endif++# ifdef FLAG_QUICKCHECK+import Test.QuickCheck+# endif+ -- $setup -- -- This module is intended to be used qualified:@@ -58,15 +73,24 @@ -- -- >>> :set -XGHC2021 -- >>> :set -XDataKinds+--+-- 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@@ -93,56 +117,97 @@ -- >>> 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'.+-- 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 {-# INLINE VLeft #-} 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 {-# INLINE VRight #-} 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@@ -158,6 +223,7 @@ -- -- 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 @@ -166,6 +232,7 @@ -- 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)@@ -178,6 +245,7 @@ -- -- 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@@ -232,7 +300,10 @@ (VRight a) <> _ = (VRight a) _ <> b = b --- Look ma! A Hand-written Generic instance!+-- 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)@@ -257,8 +328,8 @@ 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)+ (M1 (L1 (M1 (M1 (K1 err))))) -> VLeft err+ (M1 (R1 (M1 (M1 (K1 val))))) -> VRight val -- Conceptually VEither is a Bifunctor,@@ -270,3 +341,16 @@ -- 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
test/README.lhs view
@@ -9,10 +9,23 @@ Just like tuples are a version of a user-defined product type (only without the field names), a Variant is a version of a user-defined sum type (but without the field names). -Variant types are the generalization of `Either`. Especially in the situation where you want to handle multiple errors, Variant types are a great abstraction to use.+In other words: Variant types are the generalization of `Either` to more (or less) than two alternatives. +| Product: | Sum: |+|--------------|--------------------------|+| () | Vary [] / Void |+| Solo a | Vary [a] |+| (a, b) | Vary [a, b] / Either a b |+| (a, b, c) | Vary [a, b, c] |+| (a, b, c, d) | Vary [a, b, c, d] |+| (...) | Vary [...] |++Especially when doing error handling (both in pure code and in exception-heavy code), Variant types are a great abstraction to use.+ Variant types are sometimes called '_polymorphic_ variants' for disambiguation. They are also commonly known as (open) unions, coproducts or extensible sums. +Vary is lightweight on dependencies. With all library flags turned off, it only depends on `base` and `deepseq`.+ ## General Usage ### Setup@@ -103,6 +116,7 @@ * This can fail, because the downloaded file might turn out actually not to be a valid image file (PNG or JPG); * Or even if the downloaded file /is/ an image, it might have a much too high resolution to attempt to read; +_(NOTE: For simplicity, we pretend everything is a pure function rather than using IO or some more fancy effect stack in the examples below.)_ The first instinct might be to write dedicated sum types for these errors like so: @@ -235,10 +249,11 @@ ```haskell thumbnailServiceRetry :: String -> VEither [IncorrectUrl2, NotAnImage2, TooBigImage2] Image thumbnailServiceRetry url = do- image <- download url - & VEither.onLeft (\ServerUnreachable2 -> waitAndRetry 10 (\_ -> thumbnailServiceRetry url)) id+ image <- VEither.handle @ServerUnreachable2 retry $ download url thumb <- thumbnail image pure thumb+ where+ retry _err = waitAndRetry 10 (\_ -> thumbnailServiceRetry url) ``` - No more wrapper type definitions!@@ -276,6 +291,8 @@ - Only the most widely-useful functions are provided in `Vary` itself. There are some extra functions in `Vary.Utils` which are intentionally left out of the main module to make it more digestible for new users. - Libraries are already many years old (with no newer updates), and so they are not using any of the newer GHC extensions or inference improvements. - `Vary` makes great use of the `GHC2021` group of extensions, TypeFamilies and the `TypeError` construct to make most type errors disappear and for the few that remain it should be easy to understand how to fix them.+- None of the libraries make an attempt to work well with Haskell's exception mechanisms.+ - `Vary` [has excellent support to be thrown and caught as exceptions](https://hackage.haskell.org/package/vary/docs/Vary.html#vary_and_exceptions). ## Acknowledgements
vary.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.36.0.+-- This file has been generated from package.yaml by hpack version 0.35.2. -- -- see: https://github.com/sol/hpack name: vary-version: 0.1.0.3+version: 0.1.0.5 synopsis: Vary: Friendly and fast polymorphic variants (open unions/coproducts/extensible sums) description: Vary: Friendly and fast Variant types for Haskell .@@ -15,6 +15,8 @@ . Variant types are sometimes called '_polymorphic_ variants' for disambiguation. They are also commonly known as (open) unions, coproducts or extensible sums. .+ Vary is lightweight on dependencies. With all library flags turned off, it only depends on `base` and `deepseq`.+ . Please see the full README below or on GitHub at <https://github.com/qqwy/haskell-vary#readme> category: Data, Data Structures, Error Handling homepage: https://github.com/qqwy/haskell-vary#readme@@ -33,6 +35,21 @@ type: git location: https://github.com/qqwy/haskell-vary +flag aeson+ description: When enabled implements the @FromJSON@/@ToJSON@ typeclasses from the aeson library. When disabled, does not depend on the aeson library.+ manual: True+ default: True++flag hashable+ description: When enabled implements the @Hashable@ typeclass from the hashable library. When disabled, does not depend on the @hashable@ library.+ manual: True+ default: True++flag quickcheck+ description: When enabled implements the @Arbitrary@ typeclasses from the QuickCheck library. When disabled, does not depend on the QuickCheck library.+ manual: True+ default: True+ library exposed-modules: Vary@@ -45,8 +62,20 @@ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints build-depends: base >=4.7 && <5- , deepseq >=1.4.0 && <1.5+ , deepseq >=1.4.0 && <1.6 default-language: Haskell2010+ if flag(hashable)+ cpp-options: -DFLAG_HASHABLE+ build-depends:+ hashable >=1.3.0 && <1.6+ if flag(aeson)+ cpp-options: -DFLAG_AESON+ build-depends:+ aeson >=2.0.0 && <2.3+ if flag(quickcheck)+ cpp-options: -DFLAG_QUICKCHECK+ build-depends:+ QuickCheck >=2.12 && <2.16 test-suite doctests type: exitcode-stdio-1.0@@ -58,10 +87,22 @@ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N build-depends: base >=4.7 && <5- , deepseq >=1.4.0 && <1.5+ , deepseq >=1.4.0 && <1.6 , doctest-parallel , vary default-language: Haskell2010+ if flag(hashable)+ cpp-options: -DFLAG_HASHABLE+ build-depends:+ hashable >=1.3.0 && <1.6+ if flag(aeson)+ cpp-options: -DFLAG_AESON+ build-depends:+ aeson >=2.0.0 && <2.3+ if flag(quickcheck)+ cpp-options: -DFLAG_QUICKCHECK+ build-depends:+ QuickCheck >=2.12 && <2.16 test-suite readme type: exitcode-stdio-1.0@@ -73,12 +114,24 @@ markdown-unlit:markdown-unlit build-depends: base >=4.7 && <5- , deepseq >=1.4.0 && <1.5+ , deepseq >=1.4.0 && <1.6 , hspec , markdown-unlit , should-not-typecheck , vary default-language: Haskell2010+ if flag(hashable)+ cpp-options: -DFLAG_HASHABLE+ build-depends:+ hashable >=1.3.0 && <1.6+ if flag(aeson)+ cpp-options: -DFLAG_AESON+ build-depends:+ aeson >=2.0.0 && <2.3+ if flag(quickcheck)+ cpp-options: -DFLAG_QUICKCHECK+ build-depends:+ QuickCheck >=2.12 && <2.16 test-suite vary-test type: exitcode-stdio-1.0@@ -90,6 +143,18 @@ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N build-depends: base >=4.7 && <5- , deepseq >=1.4.0 && <1.5+ , deepseq >=1.4.0 && <1.6 , vary default-language: Haskell2010+ if flag(hashable)+ cpp-options: -DFLAG_HASHABLE+ build-depends:+ hashable >=1.3.0 && <1.6+ if flag(aeson)+ cpp-options: -DFLAG_AESON+ build-depends:+ aeson >=2.0.0 && <2.3+ if flag(quickcheck)+ cpp-options: -DFLAG_QUICKCHECK+ build-depends:+ QuickCheck >=2.12 && <2.16