Switching type classes at runtime
Jump to navigation
Jump to search
Introduction
It is possible to create a type class instance at runtime by coercing a value to another one. This blog post mentions the concept.
Tutorial
Let's say we have a type class called "Throws":
-- | Checked exceptions
class Throws e where
throwChecked :: e -> IO a
We'd like to turn this into a "IOError", so that it can be caught:
-- | Rethrow checked exceptions as unchecked (regular) exceptions
rethrowUnchecked :: forall e a. (Throws e => IO a) -> (Exception e => IO a)
rethrowUnchecked act = aux act throwIO
where
aux :: (Throws e => IO a) -> ((e -> IO a) -> IO a)
aux = unsafeCoerce . Wrap
-- | Wrap an action that may throw a checked exception
--
-- This is used internally in 'rethrowUnchecked' to avoid impredicative
-- instantiation of the type of 'unsafeCoerce'.
newtype Wrap e a = Wrap (Throws e => IO a)
-- | Catch a checked exception
--
-- This is the only way to discharge a 'Throws' type class constraint.
catchChecked :: Exception e => (Throws e => IO a) -> (e -> IO a) -> IO a
catchChecked = catch . rethrowUnchecked
-- | 'catchChecked' with the arguments reversed
handleChecked :: Exception e => (e -> IO a) -> (Throws e => IO a) -> IO a
handleChecked act handler = catchChecked handler act