witch-0.2.0.0: src/lib/Witch/Utility.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Witch.Utility where
import qualified Control.Exception as Exception
import qualified Data.Typeable as Typeable
import qualified GHC.Stack as Stack
import qualified Witch.Cast as Cast
import qualified Witch.Identity as Identity
import qualified Witch.TryCast as TryCast
import qualified Witch.TryCastException as TryCastException
-- | This is the same as 'id' except that it requires a type application. This
-- can be an ergonomic way to pin down a polymorphic type in a function
-- pipeline. For example:
--
-- > -- Avoid this:
-- > f . (\ x -> x :: Int) . g
-- >
-- > -- Prefer this:
-- > f . as @Int . g
as :: forall s source . Identity.Identity s ~ source => source -> source
as = id
-- | This is the same as 'Cast.cast' except that it requires a type
-- application for the @source@ type.
--
-- > -- Avoid this:
-- > cast (x :: s)
-- >
-- > -- Prefer this:
-- > from @s x
from
:: forall s target source
. (Identity.Identity s ~ source, Cast.Cast source target)
=> source
-> target
from = Cast.cast
-- | This is the same as 'Cast.cast' except that it requires a type
-- application for the @target@ type.
--
-- > -- Avoid this:
-- > cast x :: t
-- >
-- > -- Prefer this:
-- > into @t x
into
:: forall t source target
. (Identity.Identity t ~ target, Cast.Cast source target)
=> source
-> target
into = Cast.cast
-- | This function converts from some @source@ type into some @target@ type,
-- applies the given function, then converts back into the @source@ type. This
-- is useful when you have two types that are isomorphic but some function
-- that only works with one of them.
--
-- > -- Avoid this:
-- > from @t . f . from @s
-- >
-- > -- Prefer this:
-- > over @t f
over
:: forall t source target
. ( Identity.Identity t ~ target
, Cast.Cast source target
, Cast.Cast target source
)
=> (target -> target)
-> source
-> source
over f = Cast.cast . f . Cast.cast
-- | This function first converts from some @source@ type into some @through@
-- type, and then converts that into some @target@ type. Usually this is used
-- when writing 'Cast.Cast' instances. Sometimes this can be used to work
-- around the lack of an instance that should probably exist.
--
-- > -- Avoid this:
-- > from @u . into @u
-- >
-- > -- Prefer this:
-- > via @u
via
:: forall u source target through
. ( Identity.Identity u ~ through
, Cast.Cast source through
, Cast.Cast through target
)
=> source
-> target
via = Cast.cast . (\x -> x :: through) . Cast.cast
-- | This is the same as 'TryCast.tryCast' except that it requires a type
-- application for the @source@ type.
--
-- > -- Avoid this:
-- > tryCast (x :: s)
-- >
-- > -- Prefer this:
-- > tryFrom @s x
tryFrom
:: forall s target source
. (Identity.Identity s ~ source, TryCast.TryCast source target)
=> source
-> Either (TryCastException.TryCastException source target) target
tryFrom = TryCast.tryCast
-- | This is the same as 'TryCast.tryCast' except that it requires a type
-- application for the @target@ type.
--
-- > -- Avoid this:
-- > tryCast x :: Either (TryCastException s t) t
-- >
-- > -- Prefer this:
-- > tryInto @t x
tryInto
:: forall t source target
. (Identity.Identity t ~ target, TryCast.TryCast source target)
=> source
-> Either (TryCastException.TryCastException source target) target
tryInto = TryCast.tryCast
-- | This function can be used to implement 'TryCast.tryCast' with a function
-- that returns 'Maybe'. For example:
--
-- > -- Avoid this:
-- > tryCast s = case f s of
-- > Nothing -> Left $ TryCastException s Nothing
-- > Just t -> Right t
-- >
-- > -- Prefer this:
-- > tryCast = maybeTryCast f
maybeTryCast
:: (source -> Maybe target)
-> source
-> Either (TryCastException.TryCastException source target) target
maybeTryCast f s = case f s of
Nothing -> Left $ TryCastException.TryCastException s Nothing
Just t -> Right t
-- | This function can be used to implement 'TryCast.tryCast' with a function
-- that returns 'Either'. For example:
--
-- > -- Avoid this:
-- > tryCast s = case f s of
-- > Left e -> Left . TryCastException s . Just $ toException e
-- > Right t -> Right t
-- >
-- > -- Prefer this:
-- > tryCast = eitherTryCast f
eitherTryCast
:: Exception.Exception exception
=> (source -> Either exception target)
-> source
-> Either (TryCastException.TryCastException source target) target
eitherTryCast f s = case f s of
Left e ->
Left . TryCastException.TryCastException s . Just $ Exception.toException e
Right t -> Right t
-- | This is similar to 'via' except that it works with 'TryCast.TryCast'
-- instances instead. This function is especially convenient because juggling
-- the types in the 'TryCastException.TryCastException' can be tedious.
--
-- > -- Avoid this:
-- > fmap (tryFrom @u) . tryInto @u
-- >
-- > -- Prefer this:
-- > tryVia @u
tryVia
:: forall u source target through
. ( Identity.Identity u ~ through
, TryCast.TryCast source through
, TryCast.TryCast through target
)
=> source
-> Either (TryCastException.TryCastException source target) target
tryVia s = case TryCast.tryCast s of
Left (TryCastException.TryCastException _ e) ->
Left $ TryCastException.TryCastException s e
Right u -> case TryCast.tryCast (u :: through) of
Left (TryCastException.TryCastException _ e) ->
Left $ TryCastException.TryCastException s e
Right t -> Right t
-- | This function is like 'TryCast.tryCast' except that it will throw an
-- impure exception if the conversion fails.
--
-- > -- Avoid this:
-- > either throw id . cast
-- >
-- > -- Prefer this:
-- > unsafeCast
unsafeCast
:: forall source target
. ( Stack.HasCallStack
, TryCast.TryCast source target
, Show source
, Typeable.Typeable source
, Typeable.Typeable target
)
=> source
-> target
unsafeCast = either Exception.throw id . TryCast.tryCast
-- | This function is like 'from' except that it will throw an impure
-- exception if the conversion fails.
--
-- > -- Avoid this:
-- > either throw id . from @s
-- >
-- > -- Prefer this:
-- > unsafeFrom @s
unsafeFrom
:: forall s target source
. ( Identity.Identity s ~ source
, Stack.HasCallStack
, TryCast.TryCast source target
, Show source
, Typeable.Typeable source
, Typeable.Typeable target
)
=> source
-> target
unsafeFrom = unsafeCast
-- | This function is like 'into' except that it will throw an impure
-- exception if the conversion fails.
--
-- > -- Avoid this:
-- > either throw id . into @t
-- >
-- > -- Prefer this:
-- > unsafeInto @t
unsafeInto
:: forall t source target
. ( Identity.Identity t ~ target
, Stack.HasCallStack
, TryCast.TryCast source target
, Show source
, Typeable.Typeable source
, Typeable.Typeable target
)
=> source
-> target
unsafeInto = unsafeCast