-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Monadic capabilities with late binding -- -- Monadic capabilities with late binding @package caps @version 0.1 -- | Monadic capabilities are additional methods for a base monad. For -- instance, when our base monad is IO, our capabilities might -- include logging, networking, database access, and so on. -- -- This framework allows mutually recursive late-bound capabilities with -- runtime dispatch and a type-safe interface. -- -- A capability is defined as a record type with methods parametrized -- over a base monad: -- --
-- data Logging m =
-- Logging
-- { _logError :: String -> m (),
-- _logDebug :: String -> m ()
-- }
--
--
-- We can define implementations as values of this record type:
--
--
-- loggingDummy :: Monad m => CapImpl Logging '[] m
-- loggingDummy = CapImpl $ Logging (\_ -> return ()) (\_ -> return ())
--
-- loggingIO :: MonadIO m => CapImpl Logging '[] m
-- loggingIO = CapImpl $
-- Logging
-- { _logError = \msg -> liftIO . putStrLn $ "[Error] " ++ msg
-- _logDebug = \msg -> liftIO . putStrLn $ "[Debug] " ++ msg
-- }
--
--
-- The dictionary is wrapped in CapImpl to guarantee that it is
-- sufficiently polymorphic (this is required to support simultaneous use
-- of monadic actions in negative position and capability extension).
--
-- Then we want to use this capability in the CapsT monad (which
-- is nothing more but a synonym for ReaderT of
-- Capabilities), and for this we define a helper per method:
--
-- -- logError :: HasCap Logging caps => String -> CapsT caps m () -- logError message = withCap $ \cap -> _logError cap message -- -- logDebug :: HasCap Logging caps => String -> CapsT caps m () -- logDebug message = withCap $ \cap -> _logDebug cap message ---- -- We can define other capabilities in a similar manner: -- --
-- data Networking m =
-- Networking
-- { _sendRequest :: ByteString -> m ByteString }
--
-- data FileStorage m =
-- FileStorage
-- { _readFile :: FilePath -> m ByteString,
-- _writeFile :: FilePath -> ByteString -> m ()
-- }
--
--
-- Implementations of capabilities may depend on other capabilities,
-- which are listed in their signature. For instance, this is how we can
-- define the FileStorage capability using the Logging
-- capability:
--
--
-- fileStorageIO :: MonadIO m => CapImpl FileStorage '[Logging] m
-- fileStorageIO = CapImpl $
-- FileStorage
-- { _readFile = \path -> do
-- logDebug $ "readFile " ++ path
-- lift $ ByteString.readFile path
-- _writeFile = \path content -> do
-- logDebug $
-- "writeFile " ++ path ++
-- " (" ++ show (ByteString.length content) ++
-- " bytes)"
-- lift $ ByteString.writeFile path content
-- }
--
--
-- Here the fileStorageIO implementation requires a logging
-- capability, but it's not specified which one.
--
-- When we decided what set of capabilities our application needs, we can
-- put them together in a Capabilities map and run the application
-- with this map in a ReaderT context:
--
-- -- caps = buildCaps $ -- AddCap loggingIO $ -- AddCap fileStorageIO $ -- BaseCaps emptyCaps -- -- flip runReaderT caps $ do -- config <- readFile "config.yaml" -- ... ---- -- Capabilities passed to buildCaps can depend on each other. The -- order does not matter (although it is reflected in the types), and -- duplicate capabilities are disallowed. -- -- We can override a capability locally: -- --
-- do -- config <- readFile "config.yaml" -- withReaderT (overrideCap loggingDummy) $ do -- -- logging is disabled here -- writeFile "config-backup.yaml" config -- ... ---- -- or we can add more capabilities: -- --
-- do -- config <- readFile "config.yaml" -- networkingImpl <- parseNetworkingConfig config -- withReaderT (addCap networkingImpl) $ do -- -- networking capability added -- resp <- sendRequest req -- ... --module Monad.Capabilities -- | Capabilities caps m is a map of capabilities -- caps over a base monad m. Consider the following -- capabilities: -- --
-- data X m = X (String -> m String) -- data Y m = Y (Int -> m Bool) ---- -- We can construct a map of capabilities with the following type: -- --
-- capsXY :: Capabilities '[X, Y] IO ---- -- In this case, capsXY would be a map with two elements, one at -- key X and one at key Y. The types of capabilities -- themselves serve as keys. -- -- Capabilities is a heterogeneous collection, meaning that its -- values have different types. The type of a value is determined by the -- key: -- --
-- X: X (\_ -> return "hi") :: X (CapsT '[X, Y] IO) -- Y: Y (\_ -> return True) :: Y (CapsT '[X, Y] IO) -- ---- --------------------- -------------------- -- keys values types of values ---- -- Notice that stored dictionaries are parametrized not just by the base -- monad IO, but with the CapsT transformer on top. This -- means that each capability has access to all other capabilities and -- itself. data Capabilities (caps :: [CapK]) (m :: MonadK) -- | The CapsT transformer adds access to capabilities. This is a -- convenience synonym for ReaderT of Capabilities, and all -- ReaderT functions (runReaderT, withReaderT) can -- be used with it. type CapsT caps m = ReaderT (Capabilities caps m) m emptyCaps :: Capabilities '[] m -- | Build a map of capabilities from individual implementations: -- --
-- capsXY :: Capabilities '[X, Y] IO -- capsXY = buildCaps $ -- AddCap xImpl $ -- AddCap yImpl $ -- BaseCaps emptyCaps --buildCaps :: forall caps m. CapabilitiesBuilder caps caps m -> Capabilities caps m -- | CapabilitiesBuilder is a type to extend capabilities. -- -- The allCaps parameter is a list of capabilities that will be -- provided to buildCaps eventually, when the building process is -- done. The caps parameter is the part of capabilities that was -- constructed so far. The builder is considered complete when -- allCaps ~ caps, only then it can be passed to -- buildCaps. data CapabilitiesBuilder (allCaps :: [CapK]) (caps :: [CapK]) (m :: MonadK) [AddCap] :: (Typeable cap, HasCaps icaps allCaps, HasNoCap cap caps) => CapImpl cap icaps m -> CapabilitiesBuilder allCaps caps m -> CapabilitiesBuilder allCaps (cap : caps) m [BaseCaps] :: Capabilities caps m -> CapabilitiesBuilder allCaps caps m -- | The CapImpl newtype guarantees that the wrapped capability -- implementation is sufficiently polymorphic so that required subtyping -- properties hold in methods that take monadic actions as input -- (negative position). -- -- This rules out using addCap, insertCap, and -- buildCaps inside capability implementations in an unsafe -- manner. data CapImpl cap icaps m [CapImpl] :: WithSpine icaps => {getCapImpl :: forall caps. HasCaps icaps caps => cap (CapsT caps m)} -> CapImpl cap icaps m -- | Lookup a capability in a Capabilities map. The HasCap -- constraint guarantees that the lookup does not fail. getCap :: forall cap m caps. (Typeable cap, HasCap cap caps) => Capabilities caps m -> cap (CapsT caps m) -- | Override the implementation of an existing capability. overrideCap :: (Typeable cap, HasCap cap caps, HasCaps icaps caps) => CapImpl cap icaps m -> Capabilities caps m -> Capabilities caps m -- | Extend the set of capabilities. In case the capability is already -- present, a type error occurs. addCap :: (Typeable cap, HasNoCap cap caps, HasCaps icaps (cap : caps)) => CapImpl cap icaps m -> Capabilities caps m -> Capabilities (cap : caps) m -- | Extend the set of capabilities. In case the capability is already -- present, it will be overriden (as with overrideCap), but occur -- twice in the type. insertCap :: (Typeable cap, HasCaps icaps (cap : caps)) => CapImpl cap icaps m -> Capabilities caps m -> Capabilities (cap : caps) m -- | Extract a capability from CapsT and provide it to a -- continuation. withCap :: (Typeable cap, HasCap cap caps) => (cap (CapsT caps m) -> CapsT caps m a) -> CapsT caps m a -- | Determine at runtime whether 'HasCap cap caps' or 'HasNoCap cap caps' -- holds. checkCap :: forall cap caps m. Typeable cap => Capabilities caps m -> HasCapDecision cap caps -- | Override the implementation of an existing capability using the -- previous implementation. This is a more efficient equivalent to -- extracting a capability with getCap, adjusting it with a -- function, and putting it back with overrideCap. adjustCap :: forall cap caps m. (Typeable cap, HasCap cap caps) => (forall caps'. cap (CapsT caps' m) -> cap (CapsT caps' m)) -> Capabilities caps m -> Capabilities caps m -- | The Context capability is used to model the Reader -- effect within the capabilities framework. newtype Context x (m :: MonadK) Context :: x -> Context x (m :: MonadK) -- | The HasContext constraint is a shorthand for HasCap of -- Context. class (Typeable x, HasCap (Context x) caps) => HasContext x caps -- | Initialize a Context capability. newContext :: forall x m. x -> CapImpl (Context x) '[] m -- | Retrieve the context value. Moral equivalent of ask. askContext :: (HasContext x caps, Applicative m) => CapsT caps m x -- | Execute a computation with a modified context value. Moral equivalent -- of local. localContext :: forall x caps m a. HasContext x caps => (x -> x) -> CapsT caps m a -> CapsT caps m a -- | Ensure that the caps list has an element cap. type family HasCap cap caps :: Constraint -- | Ensure that the caps list subsumes icaps. It is -- equivalent to a HasCap icap caps constraint for each -- icap in icaps. type family HasCaps icaps caps :: Constraint -- | Ensure that the caps list does not have an element -- cap. type family HasNoCap cap caps :: Constraint -- | Evidence that cap is present or absent in caps. data HasCapDecision cap caps [HasNoCap] :: HasNoCap cap caps => HasCapDecision cap caps [HasCap] :: HasCap cap caps => HasCapDecision cap caps makeCap :: Name -> DecsQ instance GHC.Show.Show (Monad.Capabilities.Capabilities caps m) instance (Data.Typeable.Internal.Typeable x, Monad.Capabilities.HasCap (Monad.Capabilities.Context x) caps) => Monad.Capabilities.HasContext x caps instance forall k (cap :: k) (caps :: [k]). GHC.Show.Show (Monad.Capabilities.HasCapDecision cap caps) instance Monad.Capabilities.WithSpine '[] instance forall k (xs :: [k]) (x :: k). Monad.Capabilities.WithSpine xs => Monad.Capabilities.WithSpine (x : xs)