vary-0.1.0.0: src/Vary/Core.hs
{-# LANGUAGE GHC2021 #-}
{-# OPTIONS_HADDOCK not-home #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE MonoLocalBinds #-}
module Vary.Core (Vary (..), pop) where
import Data.Kind (Type)
import GHC.Exts (Any)
import qualified Unsafe.Coerce as Data.Coerce
import Control.DeepSeq (NFData (..))
import Control.Exception (Exception(..))
import Data.Typeable (Typeable, typeOf)
-- $setup
-- >>> :set -XGHC2021
-- >>> :set -XDataKinds
-- >>> import Vary (Vary, (:|))
-- >>> import qualified Vary
-- | Vary, contains one value out of a set of possibilities
--
-- Vary is what is known as a /Variant/ type.
-- This is also known as an /open union/ or /coproduct/, among other names.
--
-- You can see it as the generalization of `Either`.
-- Conceptually, these are the same:
--
-- > Vary [a, b, c, d, e]
-- > Either a (Either b (Either c (Either d e)))
--
-- However, compared to a deeply nested `Either`, `Vary` is:
--
-- - Much easier to work with;
-- - Much more efficient, as a single (strict) word is used for the tag.
--
-- `Vary`'s can be constructed with "Vary".`Vary.from` and values can be extracted using "Vary".`Vary.into` and "Vary".'Vary.on' .
data Vary (possibilities :: [Type]) = Vary {-# UNPACK #-} !Word Any
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.
--
-- This function can also be seen as turning one layer of `Vary` into its isomorphic `Either` representation.
--
-- This function is not often useful in 'normal' code, but /super/ useful in generic code where you want to recurse on the variant's types.
--
-- For instance when implementing a typeclass for any `Vary` whose elements implement the typeclass:
--
--
-- > instance Show (Vary '[]) where
-- > show = Vary.exhaustiveCase
-- >
-- > instance (Show a, Show (Vary as)) => Show (Vary (a : as)) where
-- > show vary = case Vary.pop vary of
-- > Right val -> "Vary.from " <> show val
-- > Left other -> show other
--
-- 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))
instance Eq (Vary '[]) where
(==) = emptyVaryError "Eq.(==)"
instance (Eq a, Eq (Vary as)) => Eq (Vary (a : as)) where
{-# INLINE (==) #-}
a == b = pop a == pop b
instance Ord (Vary '[]) where
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
instance Show (Vary '[]) where
show = emptyVaryError "Show.show"
-- | `Vary`'s 'Show' instance only works for types which are 'Typeable'
--
-- This allows us to print the name of the type which
-- the current value is of.
--
-- >>> Vary.from @Bool True :: Vary '[Int, Bool, String]
-- Vary.from @Bool True
--
-- >>> 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
instance NFData (Vary '[]) where
rnf = emptyVaryError "NFData.rnf"
instance (NFData a, NFData (Vary as)) => NFData (Vary (a : as)) where
{-# INLINE rnf #-}
rnf vary = rnf (pop vary)
instance (Typeable (Vary '[]), Show (Vary '[])) => Exception (Vary '[]) where
-- | 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
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