packages feed

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 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