-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Combinators for handling errors of many types in a composable way -- -- Combinators for handling errors of many types in a composable way. @package oops @version 0.1.2.0 -- | Traditionally in Haskell, we use Either a b to represent a -- choice of two types. If we want to represent three types, we -- use Either a (Either b c), and this nesting can continue as -- far as it needs to. However, this approach comes with some -- difficulties: it's quite difficult to manipulate, and makes for some -- rather unwieldy type signatures. -- -- Thankfully, though, GHC provides us with GADTs, and they allow us to -- construct a type that encompasses a coproduct of any number of -- arguments: the Variant. Just as Left 3 and Right -- True are of type Either Int Bool, we can write Here -- 3 and There (Here True) to do the same thing (ignoring -- Identity wrappers). We can think of the Here and -- There constructors as an "index": the index of the type we're -- storing is the number of occurrences of There. -- -- $setup >>> :set -XTypeOperators -XDataKinds -- -XTypeApplications -- --
-- > > :t [ Here (Identity 'a'), There (There (Here (Identity True))) ] ---- --
-- > > :t [ Here (pure "Hello"), There (Here (pure True)) ] ---- --
-- >>> :set -XDataKinds -- -- >>> variantF Left Right (Here (Identity True) :: Variant '[Bool]) -- Left (Identity True) ---- --
-- >>> variantF Left Right (There (Here (Identity 3)) :: Variant '[Bool, Int]) -- Right (Here (Identity 3)) --variantF :: (f x -> r) -> (VariantF f xs -> r) -> VariantF f (x : xs) -> r -- | Same as VariantF, but the value will be unwrapped (not in -- Identity) if found. -- --
-- >>> variant Left Right (Here (Identity True) :: Variant '[Bool]) -- Left True ---- --
-- >>> variant Left Right (There (Here (Identity 3)) :: Variant '[Bool, Int]) -- Right (Here (Identity 3)) --variant :: (x -> r) -> (Variant xs -> r) -> Variant (x : xs) -> r -- | Same as caseF, but without the functor wrappers. Again, this -- function will specialise according to the provided variant: -- --
-- > > :t case_ (throw True :: Variant '[Bool, Int]) ---- -- case_ (throw True :: Variant '[Bool, Int]) :: (Bool -> o) -> -- (Int -> o) -> o -- -- You can also use TypeApplications to check the specialisation -- for a particular variant: -- --
-- > > :t case_ @'[Int, Bool, String] ---- -- case_ @'[Int, Bool, String] :: Variant '[Int, Bool, String] -> (Int -- -> o) -> (Bool -> o) -> ([Char] -> o) -> o case_ :: Case xs r fold => Variant xs -> fold -- | The either function provides us with a way of folding an -- Either by providing a function for each possible constructor: -- Left and Right. In our case, we could have any number of -- functions to supply, depending on how many types are in our type-level -- index. -- -- This function specialises depending on the variant provided: -- --
-- > > :t caseF (throw True :: Variant '[Bool]) ---- -- caseF (throw True :: Variant '[Bool]) :: (Identity Bool -> r) -> -- r -- --
-- > > :t caseF (throwF (pure True) :: VariantF IO '[Int, Bool]) ---- -- caseF (throwF (pure True) :: VariantF IO '[Int, Bool]) :: (IO Int -- -> o) -> (IO Bool -> o) -> o caseF :: CaseF xs f r fold => VariantF f xs -> fold -- | When dealing with larger (or polymorphic) variants, it becomes -- difficult (or impossible) to construct VariantF values -- explicitly. In that case, the throwF function gives us a -- polymorphic way to lift values into variants. -- --
-- >>> throwF (pure "Hello") :: VariantF Maybe '[Bool, Int, Double, String] -- There (There (There (Here (Just "Hello")))) ---- --
-- >>> throwF (pure True) :: VariantF Maybe '[Bool, Int, Double, String] -- Here (Just True) ---- --
-- >>> throwF (pure True) :: VariantF IO '[Int, Double, String] -- ... -- ... • Uh oh! I couldn't find Bool inside the variant! -- ... If you're pretty sure I'm wrong, perhaps the variant type is ambiguous; -- ... could you add some annotations? -- ... --class CouldBeF (xs :: [k]) (x :: k) throwF :: CouldBeF xs x => f x -> VariantF f xs snatchF :: CouldBeF xs x => VariantF f xs -> Either (VariantF f xs) (f x) -- | Just as with CouldBeF, we can "throw" values not in a -- functor context into a regular Variant. -- --
-- >>> throw (3 :: Int) :: Variant '[Bool, Int, Double, String] -- There (Here (Identity 3)) ---- --
-- >>> throw "Woo!" :: Variant '[Bool, Int, Double, String] -- There (There (There (Here (Identity "Woo!")))) --class CouldBeF xs x => CouldBe (xs :: [Type]) (x :: Type) throw :: CouldBe xs x => x -> Variant xs snatch :: CouldBe xs x => Variant xs -> Either (Variant xs) x -- | As with CouldBeAnyOf, we can also constrain a variant to -- represent several possible types, as we might with several -- CouldBeF constraints, using one type-level list. type e `CouldBeAnyOfF` xs = All (Map (CouldBeF e) xs) -- | Listing larger variants' constraints might amplify the noise of -- functions' signatures. The CouldBeAnyOfF constraint lets us -- specify several types a variant may contain in a single type-level -- list, as opposed to several independent constraints. So, we could -- replace, -- -- f :: (e CouldBe Int, e CouldBe Bool, e CouldBe -- Char) => VariantF IO e -- -- with the equivalent constraint, -- -- f :: e CouldBeAnyOf '[Int, Bool, Char] => VariantF IO e -- -- As CouldBeAnyOf is just short-hand, we can use throw -- just like when we have CouldBe constraints: -- --
-- >>> :set -XTypeOperators
--
-- >>> :{
-- f :: e `CouldBeAnyOf` '[Int, Bool, Char] => Variant e
-- f = throw 'c'
-- :}
--
--
-- ... and eliminate constraints in just the same way:
--
--
-- >>> :{
-- g :: e `CouldBeAnyOf` '[Int, Bool] => Either (Variant e) Char
-- g = catch @Char f
-- :}
--
type e `CouldBeAnyOf` xs = All (Map (CouldBe e) xs)
-- | This is an odd constraint, as you should rarely need to see it.
-- GHC's partial instantiation tricks should mean that mentions of this
-- class "cancel out" mentions of CouldBeF. As an example, let's
-- imagine a function that represents some business logic that
-- potentially "throws" either an Int or Bool while it
-- runs:
--
--
-- >>> :set -XFlexibleContexts -XMonoLocalBinds -XTypeOperators
--
-- >>> :{
-- f :: (e `CouldBe` Int, e `CouldBe` Bool) => VariantF IO e
-- f = throwF (pure True)
-- :}
--
--
-- As we can see, there are two constraints here. However, if we "catch"
-- one of these possible errors, we don't just add the CatchF
-- constraint: we /cancel out/ the constraint corresponding to the type
-- we caught:
--
--
-- >>> :{
-- g :: e `CouldBe` Int => Either (VariantF IO e) (IO Bool)
-- g = catchF @Bool f
-- :}
--
--
-- This means that constraints only propagate for uncaught
-- exceptions, just as Java functions only need declare exceptions they
-- haven't caught. Once we've caught all the errors, the
-- constraint disappears! This can be a nice way to work if you combine
-- it with something like ExceptT.
class CatchF x xs ys | xs x -> ys, xs ys -> x, x ys -> xs
catchF :: CatchF x xs ys => VariantF f xs -> Either (VariantF f ys) (f x)
-- | throwF is to catchF as throw is to
-- catch. This function allows us to discharge constraints for
-- Variant types. We can revisit the catchF example without
-- the functor wrapper:
--
--
-- >>> :{
-- f :: (e `CouldBe` Int, e `CouldBe` Bool) => Variant e
-- f = throw True
-- :}
--
--
-- ... and be similarly excited when we make one of the constraints
-- disappear:
--
--
-- >>> :{
-- g :: e `CouldBe` Int => Either (Variant e) Bool
-- g = catch @Bool f
-- :}
--
class CatchF x xs ys => Catch (x :: Type) (xs :: [Type]) (ys :: [Type])
catch :: Catch x xs ys => Variant xs -> Either (Variant ys) x
-- | Occasionally, we might want to use our "nested Either" analogue
-- for whatever reason. For that situation the functions here allow you
-- to swap between the two representations.
--
-- -- > > :t toEithersF @IO @'[String, Int, Bool] ---- -- toEithersF IO '[String, Int, Bool] :: VariantF IO '[String, -- Int, Bool] -> Either (IO [Char]) (Either (IO Int) (IO Bool)) -- -- In order to maintain the round-tripping property (see below), the -- functional dependency only goes from the variant to the nested either. -- This is because the opposite doesn't always necessarily make sense. -- -- If Variant '[a, b] is converted to Either a b, it -- would seem sensible to say the opposite is equally as mechanical. -- However, consider a nesting like Either a (Either b c): -- should this translate to Variant '[a, b, c] or Variant -- '[a, Either b c]? There's not a unique mapping in this direction, -- so we can't add the functional dependency. class EithersF (f :: Type -> Type) (xs :: [Type]) (o :: Type) | f xs -> o, o f -> xs toEithersF :: EithersF f xs o => VariantF f xs -> o fromEithersF :: EithersF f xs o => o -> VariantF f xs -- | The f-less analogue of EithersF. The same properties -- as described above will hold, with the same issues around -- fromEithers result inference. -- --
-- > > :t toEithers @'[String, Int, Bool] ---- -- toEithers @'[String, Int, Bool] :: Variant '[String, Int, Bool] -> -- Either [Char] (Either Int Bool) -- -- The round-tripping property is also conserved: class Eithers (xs :: [Type]) (o :: Type) | xs -> o toEithers :: Eithers xs o => Variant xs -> o fromEithers :: Eithers xs o => o -> Variant xs -- | A constraint-based fold requires a polymorphic function relying on a -- shared constraint between all members of the variant. If that's a lot -- of words, let's see a little example: -- --
-- >>> foldF @Show (throwF ["hello"] :: VariantF [] '[(), String, Bool]) show -- "[\"hello\"]" ---- -- If everything in our variant is Show-friendly, we can fold it -- with the show function, and we just show whatever is in there! class FoldF (c :: Type -> Constraint) (xs :: [Type]) foldF :: FoldF c xs => VariantF f xs -> (forall x. c x => f x -> m) -> m -- | Similarly, we can fold the wrapper-less version in the same way. As an -- example, if all the types are the same, we can pull out whatever value -- is in there using the fold interface. -- --
-- >>> :set -XRankNTypes -XScopedTypeVariables
--
-- >>> :{
-- fold' :: forall x xs. Fold ((~) x) xs => Variant xs -> x
-- fold' xs = fold @((~) x) xs id
-- :}
--
--
-- If all the types in the list are the same, and we can turn values of
-- that type into some result and return it.
class FoldF c xs => Fold (c :: Type -> Constraint) (xs :: [Type])
fold :: Fold c xs => Variant xs -> (forall x. c x => x -> m) -> m
-- | A choice of zero types is an uninhabited type! This means we can
-- convert it to Void...
preposterous :: VariantF f '[] -> Void
-- | ... and it also means we can convert back!
postposterous :: Void -> VariantF f '[]
instance forall k (f :: k -> *) (xs :: [k]). Data.Variant.AllF GHC.Classes.Eq f xs => GHC.Classes.Eq (Data.Variant.VariantF f xs)
instance forall k (f :: k -> *) (xs :: [k]). Data.Variant.AllF GHC.Show.Show f xs => GHC.Show.Show (Data.Variant.VariantF f xs)
instance forall k (f :: k -> *) (xs :: [k]). (Data.Variant.AllF GHC.Classes.Eq f xs, Data.Variant.AllF GHC.Classes.Ord f xs) => GHC.Classes.Ord (Data.Variant.VariantF f xs)
instance Data.Variant.FoldF c xs => Data.Variant.Fold c xs
instance Data.Variant.FoldF c '[]
instance (c x, Data.Variant.FoldF c xs) => Data.Variant.FoldF c (x : xs)
instance Data.Variant.Eithers '[x] x
instance Data.Variant.Eithers (y : xs) zs => Data.Variant.Eithers (x : y : xs) (Data.Either.Either x zs)
instance Data.Variant.EithersF f '[x] (f x)
instance (GHC.Base.Functor f, Data.Variant.EithersF f (y : xs) zs) => Data.Variant.EithersF f (x : y : xs) (Data.Either.Either (f x) zs)
instance (Data.Variant.EithersF f xs nested, Test.QuickCheck.Arbitrary.Arbitrary nested) => Test.QuickCheck.Arbitrary.Arbitrary (Data.Variant.VariantF f xs)
instance Data.Variant.CatchF x xs ys => Data.Variant.Catch x xs ys
instance forall a (x :: a) (xs :: [a]). Data.Variant.CatchF x (x : xs) xs
instance forall a (y :: a) (z :: a) (x :: a) (xs :: [a]) (ys :: [a]). (y GHC.Types.~ z, Data.Variant.CatchF x xs ys) => Data.Variant.CatchF x (y : xs) (z : ys)
instance Data.Variant.CouldBeF xs x => Data.Variant.CouldBe xs x
instance forall k (x :: k) (xs :: [k]). Data.Variant.CouldBeF (x : xs) x
instance forall k (xs :: [k]) (x :: k) (y :: k). Data.Variant.CouldBeF xs x => Data.Variant.CouldBeF (y : xs) x
instance forall k (x :: k). Data.Variant.TypeNotFound x => Data.Variant.CouldBeF '[] x
instance Data.Variant.Case '[x] r ((x -> r) -> r)
instance Data.Variant.Case (y : zs) r ((y -> r) -> o) => Data.Variant.Case (x : y : zs) r ((x -> r) -> (y -> r) -> o)
instance Data.Variant.CaseF '[x] f r ((f x -> r) -> r)
instance Data.Variant.CaseF (y : zs) f r ((f y -> r) -> o) => Data.Variant.CaseF (x : y : zs) f r ((f x -> r) -> (f y -> r) -> o)
instance forall k (f :: k -> *) (xs :: [k]). Data.Variant.AllF GHC.Base.Semigroup f xs => GHC.Base.Semigroup (Data.Variant.VariantF f xs)
instance forall a (f :: a -> *) (x :: a) (xs :: [a]). (GHC.Base.Monoid (f x), GHC.Base.Semigroup (Data.Variant.VariantF f (x : xs))) => GHC.Base.Monoid (Data.Variant.VariantF f (x : xs))
module Control.Monad.Oops
-- | When working in some monadic context, using catch becomes
-- trickier. The intuitive behaviour is that each catch shrinks
-- the variant in the left side of my MonadError, but this is
-- therefore type-changing: as we can only throwError and
-- catchError with a MonadError type, this is impossible!
--
-- To get round this problem, we have to specialise to ExceptT,
-- which allows us to map over the error type and change it as we go. If
-- the error we catch is the one in the variant that we want to handle,
-- we pluck it out and deal with it. Otherwise, we "re-throw" the variant
-- minus the one we've handled.
catchFM :: forall x e e' f m a. () => Monad m => CatchF x e e' => (f x -> ExceptT (VariantF f e') m a) -> ExceptT (VariantF f e) m a -> ExceptT (VariantF f e') m a
-- | Just the same as catchFM, but specialised for our plain
-- Variant and sounding much less like a radio station.
catchM :: forall x e e' m a. () => Monad m => Catch x e e' => (x -> ExceptT (Variant e') m a) -> ExceptT (Variant e) m a -> ExceptT (Variant e') m a
-- | Throw an error into a variant MonadError context. Note that
-- this isn't type-changing, so this can work for any
-- MonadError, rather than just ExceptT.
throwFM :: forall x e f m a. () => MonadError (VariantF f e) m => e `CouldBe` x => f x -> m a
-- | Same as throwFM, but without the f context. Given a
-- value of some type within a Variant within a MonadError
-- context, "throw" the error.
throwM :: forall x e m a. () => MonadError (Variant e) m => e `CouldBe` x => x -> m a
-- | Same as catchFM except the error is not removed from the type.
-- This is useful for writing recursive computations or computations that
-- rethrow the same error type.
snatchFM :: forall x e f m a. () => Monad m => e `CouldBe` x => (f x -> ExceptT (VariantF f e) m a) -> ExceptT (VariantF f e) m a -> ExceptT (VariantF f e) m a
-- | Same as catchM except the error is not removed from the type.
-- This is useful for writing recursive computations or computations that
-- rethrow the same error type.
snatchM :: forall x e m a. () => Monad m => e `CouldBe` x => (x -> ExceptT (Variant e) m a) -> ExceptT (Variant e) m a -> ExceptT (Variant e) m a
-- | Add 'ExceptT (Variant '[])' to the monad transformer stack.
runOops :: () => Monad m => ExceptT (Variant '[]) m a -> m a
-- | Suspend the ExceptT monad transformer from the top of the stack
-- so that the stack can be manipulated without the ExceptT layer.
suspendM :: forall x m a n b. () => (m (Either x a) -> n (Either x b)) -> ExceptT x m a -> ExceptT x n b
-- | Catch the specified exception and return the caught value as
-- Left. If no value was caught, then return the returned value in
-- Right.
catchAsLeftM :: forall x e m a. () => Monad m => ExceptT (Variant (x : e)) m a -> ExceptT (Variant e) m (Either x a)
-- | Catch the specified exception. If that exception is caught, exit the
-- program.
catchAndExitFailureM :: forall x e m a. () => MonadIO m => ExceptT (Variant (x : e)) m a -> ExceptT (Variant e) m a
-- | When the expression of type 'Either x a' evaluates to 'Left x', throw
-- the x, otherwise return a.
throwLeftM :: forall x e m a. () => MonadError (Variant e) m => CouldBeF e x => Monad m => Either x a -> m a
-- | When the expression of type 'Maybe a' evaluates to Nothing,
-- throw (), otherwise return a.
throwNothingM :: () => MonadError (Variant e) m => CouldBeF e () => Monad m => Maybe a -> m a
-- | When the expression of type 'Maybe a' evaluates to Nothing,
-- throw the specified value, otherwise return a.
throwNothingAsM :: forall e es m a. () => MonadError (Variant es) m => CouldBe es e => e -> Maybe a -> m a
-- | Catch the specified exception and return it instead. The evaluated
-- computation must return the same type that is being caught.
recoverM :: forall x e m a. () => Monad m => (x -> a) -> ExceptT (Variant (x : e)) m a -> ExceptT (Variant e) m a
-- | Catch the specified exception and return it instead. The evaluated
-- computation must return Void (ie. it never returns)
recoverOrVoidM :: forall x e m. () => Monad m => ExceptT (Variant (x : e)) m Void -> ExceptT (Variant e) m x
-- | When dealing with larger (or polymorphic) variants, it becomes
-- difficult (or impossible) to construct VariantF values
-- explicitly. In that case, the throwF function gives us a
-- polymorphic way to lift values into variants.
--
-- -- >>> throwF (pure "Hello") :: VariantF Maybe '[Bool, Int, Double, String] -- There (There (There (Here (Just "Hello")))) ---- --
-- >>> throwF (pure True) :: VariantF Maybe '[Bool, Int, Double, String] -- Here (Just True) ---- --
-- >>> throwF (pure True) :: VariantF IO '[Int, Double, String] -- ... -- ... • Uh oh! I couldn't find Bool inside the variant! -- ... If you're pretty sure I'm wrong, perhaps the variant type is ambiguous; -- ... could you add some annotations? -- ... --class CouldBeF (xs :: [k]) (x :: k) throwF :: CouldBeF xs x => f x -> VariantF f xs snatchF :: CouldBeF xs x => VariantF f xs -> Either (VariantF f xs) (f x) -- | Just as with CouldBeF, we can "throw" values not in a -- functor context into a regular Variant. -- --
-- >>> throw (3 :: Int) :: Variant '[Bool, Int, Double, String] -- There (Here (Identity 3)) ---- --
-- >>> throw "Woo!" :: Variant '[Bool, Int, Double, String] -- There (There (There (Here (Identity "Woo!")))) --class CouldBeF xs x => CouldBe (xs :: [Type]) (x :: Type) throw :: CouldBe xs x => x -> Variant xs snatch :: CouldBe xs x => Variant xs -> Either (Variant xs) x -- | As with CouldBeAnyOf, we can also constrain a variant to -- represent several possible types, as we might with several -- CouldBeF constraints, using one type-level list. type e `CouldBeAnyOfF` xs = All (Map (CouldBeF e) xs) -- | Listing larger variants' constraints might amplify the noise of -- functions' signatures. The CouldBeAnyOfF constraint lets us -- specify several types a variant may contain in a single type-level -- list, as opposed to several independent constraints. So, we could -- replace, -- -- f :: (e CouldBe Int, e CouldBe Bool, e CouldBe -- Char) => VariantF IO e -- -- with the equivalent constraint, -- -- f :: e CouldBeAnyOf '[Int, Bool, Char] => VariantF IO e -- -- As CouldBeAnyOf is just short-hand, we can use throw -- just like when we have CouldBe constraints: -- --
-- >>> :set -XTypeOperators
--
-- >>> :{
-- f :: e `CouldBeAnyOf` '[Int, Bool, Char] => Variant e
-- f = throw 'c'
-- :}
--
--
-- ... and eliminate constraints in just the same way:
--
--
-- >>> :{
-- g :: e `CouldBeAnyOf` '[Int, Bool] => Either (Variant e) Char
-- g = catch @Char f
-- :}
--
type e `CouldBeAnyOf` xs = All (Map (CouldBe e) xs)
-- | Often, you'll want to have a choice of types that aren't all
-- wrapped in a functor. For this, we provide the Variant type
-- synonym, as well as equivalents of all the functions below. These
-- functions take care of wrapping and unwrapping the Identity
-- wrapper, too, so it should be invisible to users.
type Variant (xs :: [Type]) = VariantF Identity xs
-- | The type VariantF f '[x, y, z] is either f x,
-- f y, or f z. The We construct these with
-- Here, There . Here, and There . There .
-- Here respectively, and we can think o fthe number of
-- There-nestings as being the index of our chosen type in the
-- type-level list of options.
--
-- Often, however, we'll want to avoid being too explicit about our list
-- of types, preferring instead to describe it with constraints. See the
-- methods below for more information!
--
-- -- > > :t [ Here (pure "Hello"), There (Here (pure True)) ] ---- --
-- >>> throwF (pure "Hello") :: VariantF Maybe '[Bool, Int, Double, String] -- There (There (There (Here (Just "Hello")))) ---- --
-- >>> throwF (pure True) :: VariantF Maybe '[Bool, Int, Double, String] -- Here (Just True) ---- --
-- >>> throwF (pure True) :: VariantF IO '[Int, Double, String] -- ... -- ... • Uh oh! I couldn't find Bool inside the variant! -- ... If you're pretty sure I'm wrong, perhaps the variant type is ambiguous; -- ... could you add some annotations? -- ... --class CouldBeF (xs :: [k]) (x :: k) throwF :: CouldBeF xs x => f x -> VariantF f xs snatchF :: CouldBeF xs x => VariantF f xs -> Either (VariantF f xs) (f x) -- | Just as with CouldBeF, we can "throw" values not in a -- functor context into a regular Variant. -- --
-- >>> throw (3 :: Int) :: Variant '[Bool, Int, Double, String] -- There (Here (Identity 3)) ---- --
-- >>> throw "Woo!" :: Variant '[Bool, Int, Double, String] -- There (There (There (Here (Identity "Woo!")))) --class CouldBeF xs x => CouldBe (xs :: [Type]) (x :: Type) throw :: CouldBe xs x => x -> Variant xs snatch :: CouldBe xs x => Variant xs -> Either (Variant xs) x -- | As with CouldBeAnyOf, we can also constrain a variant to -- represent several possible types, as we might with several -- CouldBeF constraints, using one type-level list. type e `CouldBeAnyOfF` xs = All (Map (CouldBeF e) xs) -- | Listing larger variants' constraints might amplify the noise of -- functions' signatures. The CouldBeAnyOfF constraint lets us -- specify several types a variant may contain in a single type-level -- list, as opposed to several independent constraints. So, we could -- replace, -- -- f :: (e CouldBe Int, e CouldBe Bool, e CouldBe -- Char) => VariantF IO e -- -- with the equivalent constraint, -- -- f :: e CouldBeAnyOf '[Int, Bool, Char] => VariantF IO e -- -- As CouldBeAnyOf is just short-hand, we can use throw -- just like when we have CouldBe constraints: -- --
-- >>> :set -XTypeOperators
--
-- >>> :{
-- f :: e `CouldBeAnyOf` '[Int, Bool, Char] => Variant e
-- f = throw 'c'
-- :}
--
--
-- ... and eliminate constraints in just the same way:
--
--
-- >>> :{
-- g :: e `CouldBeAnyOf` '[Int, Bool] => Either (Variant e) Char
-- g = catch @Char f
-- :}
--
type e `CouldBeAnyOf` xs = All (Map (CouldBe e) xs)
-- | Often, you'll want to have a choice of types that aren't all
-- wrapped in a functor. For this, we provide the Variant type
-- synonym, as well as equivalents of all the functions below. These
-- functions take care of wrapping and unwrapping the Identity
-- wrapper, too, so it should be invisible to users.
type Variant (xs :: [Type]) = VariantF Identity xs
-- | The type VariantF f '[x, y, z] is either f x,
-- f y, or f z. The We construct these with
-- Here, There . Here, and There . There .
-- Here respectively, and we can think o fthe number of
-- There-nestings as being the index of our chosen type in the
-- type-level list of options.
--
-- Often, however, we'll want to avoid being too explicit about our list
-- of types, preferring instead to describe it with constraints. See the
-- methods below for more information!
--
-- -- > > :t [ Here (pure "Hello"), There (Here (pure True)) ] ---- --