Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add haddocks for capabilities #33

Merged
merged 6 commits into from
Sep 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions LOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# 2018.09.21

## Capability type-class laws

The capabilities `HasReader`, `HasState`, and `HasWriter` provide the same
interface as the corresponding Mtl type classes, apart from the tags.
Unfortunately, neither the Mtl documentation, nor the transformers documentation
specify any laws for these type classes. A discussion about this can be
found on the [mtl issue tracker](https://github.com/haskell/mtl/issues/5).

This library does define laws for the capability classes based on the above
mentioned mtl issue. At this point these are not definitive.
A detailed discussion of the state laws, and formal proofs written in Agda
can be found can be found in
[this](http://gallium.inria.fr/blog/lawvere-theories-and-monads/)
blog post by Pierre Dagand.
26 changes: 20 additions & 6 deletions src/Capability/Error.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,22 @@
{-# OPTIONS_GHC -Wno-simplifiable-class-constraints #-}

module Capability.Error
( HasThrow(..)
( -- * Interface
HasThrow(..)
, throw
, HasCatch(..)
, catch
, catchJust
, wrapError
-- * Strategies
, MonadError(..)
, MonadThrow(..)
, MonadCatch(..)
, SafeExceptions(..)
, MonadUnliftIO(..)
-- ** Modifiers
, module Capability.Accessors
-- * Re-exported
, Exception(..)
, Typeable
) where
Expand Down Expand Up @@ -58,10 +62,13 @@ import qualified UnliftIO.Exception as UnliftIO
class Monad m
=> HasThrow (tag :: k) (e :: *) (m :: * -> *) | tag m -> e
where
-- | Use 'throw' instead.
-- | For technical reasons, this method needs an extra proxy argument.
-- You only need it if you are defining new instances of 'HasReader'.
-- Otherwise, you will want to use 'throw'.
-- See 'throw' for more documentation.
throw_ :: Proxy# tag -> e -> m a

-- | Throw an exception.
-- | Throw an exception in the specified exception capability.
throw :: forall tag e m a. HasThrow tag e m => e -> m a
throw = throw_ (proxy# @_ @tag)
{-# INLINE throw #-}
Expand All @@ -83,12 +90,19 @@ throw = throw_ (proxy# @_ @tag)
class HasThrow tag e m
=> HasCatch (tag :: k) (e :: *) (m :: * -> *) | tag m -> e
where
-- | Use 'catch' instead.
-- | For technical reasons, this method needs an extra proxy argument.
-- You only need it if you are defining new instances of 'HasReader'.
-- Otherwise, you will want to use 'catch'.
-- See 'catch' for more documentation.
catch_ :: Proxy# tag -> m a -> (e -> m a) -> m a
-- | Use 'catchJust' instead.
-- | For technical reasons, this method needs an extra proxy argument.
-- You only need it if you are defining new instances of 'HasReader'.
-- Otherwise, you will want to use 'catchJust'.
-- See 'catchJust' for more documentation.
catchJust_ :: Proxy# tag -> (e -> Maybe b) -> m a -> (b -> m a) -> m a

-- | Provide a handler for exceptions thrown in the given action.
-- | Provide a handler for exceptions thrown in the given action
-- in the given exception capability.
catch :: forall tag e m a. HasCatch tag e m => m a -> (e -> m a) -> m a
catch = catch_ (proxy# @_ @tag)
{-# INLINE catch #-}
Expand Down
5 changes: 4 additions & 1 deletion src/Capability/Reader.hs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
module Capability.Reader
( HasReader(..)
( -- * Interface
HasReader(..)
, ask
, asks
, local
, reader
-- * Strategies
, MonadReader(..)
, ReadStatePure(..)
, ReadState(..)
-- ** Modifiers
, module Capability.Accessors
) where

Expand Down
41 changes: 41 additions & 0 deletions src/Capability/Reader/Internal/Class.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,66 @@ module Capability.Reader.Internal.Class

import GHC.Exts (Proxy#, proxy#)

-- | Reader capability
--
-- An instance should fulfill the following laws.
-- At this point these laws are not definitive,
-- see <https://github.com/haskell/mtl/issues/5>.
--
-- prop> k <*> ask @t = ask @t <**> k
-- prop> ask @t *> m = m = m <* ask @t
-- prop> local @t f (ask @t) = fmap f (ask @t)
-- prop> local @t f . local @t g = local @t (g . f)
-- prop> local @t f (pure x) = pure x
-- prop> local @t f (m >>= \x -> k x) = local @t f m >>= \x -> local @t f (k x)
-- prop> reader @t f = f <$> ask @t
class Monad m
=> HasReader (tag :: k) (r :: *) (m :: * -> *) | tag m -> r
where
-- | For technical reasons, this method needs an extra proxy argument.
-- You only need it if you are defining new instances of 'HasReader'.
-- Otherwise, you will want to use 'ask'.
-- See 'ask' for more documentation.
ask_ :: Proxy# tag -> m r
-- | For technical reasons, this method needs an extra proxy argument.
-- You only need it if you are defining new instances of 'HasReader'.
-- Otherwise, you will want to use 'local'.
-- See 'local' for more documentation.
local_ :: Proxy# tag -> (r -> r) -> m a -> m a
-- | For technical reasons, this method needs an extra proxy argument.
-- You only need it if you are defining new instances of 'HasReader'.
-- Otherwise, you will want to use 'reader'.
-- See 'reader' for more documentation.
reader_ :: Proxy# tag -> (r -> a) -> m a

-- | @ask \@tag@
-- retrieves the environment of the reader capability @tag@.
ask :: forall tag r m. HasReader tag r m => m r
ask = ask_ (proxy# @_ @tag)
{-# INLINE ask #-}

-- | @asks \@tag@
-- retrieves the image by @f@ of the environment
-- of the reader capability @tag@.
--
-- prop> asks @tag f = f <$> ask @tag
asks :: forall tag r m a. HasReader tag r m => (r -> a) -> m a
asks f = f <$> ask @tag
{-# INLINE asks #-}

-- | @local \@tag f m@
-- runs the monadic action @m@ in a modified environment @e' = f e@,
-- where @e@ is the environment of the reader capability @tag@.
-- Symbolically: @return e = ask \@tag@.
local :: forall tag r m a. HasReader tag r m => (r -> r) -> m a -> m a
local = local_ (proxy# @_ @tag)
{-# INLINE local #-}

-- | @reader \@tag act@
-- lifts a purely environment-dependent action @act@ to a monadic action
-- in an arbitrary monad @m@ with capability @HasReader@.
--
-- It happens to coincide with @asks@: @reader = asks@.
reader :: forall tag r m a. HasReader tag r m => (r -> a) -> m a
reader = reader_ (proxy# @_ @tag)
{-# INLINE reader #-}
5 changes: 4 additions & 1 deletion src/Capability/State.hs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
module Capability.State
( HasState(..)
( -- * Interface
HasState(..)
, get
, put
, state
, modify
, modify'
, gets
-- * Strategies
, MonadState(..)
, ReaderIORef(..)
, ReaderRef(..)
-- ** Modifiers
, module Capability.Accessors
) where

Expand Down
42 changes: 42 additions & 0 deletions src/Capability/State/Internal/Class.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,77 @@ module Capability.State.Internal.Class

import GHC.Exts (Proxy#, proxy#)

-- | State capability
--
-- An instance should fulfill the following laws.
-- At this point these laws are not definitive,
-- see <https://github.com/haskell/mtl/issues/5>.
--
-- prop> get @t >>= \s1 -> get @t >>= \s2 -> pure (s1, s2) = get @t >>= \s -> pure (s, s)
-- prop> get @t >>= \_ -> put @t s = put @t s
-- prop> put @t s1 >> put @t s2 = put @t s2
-- prop> put @t s >> get @t = put @t s >> pure s
-- prop> state @t f = get @t >>= \s -> let (a, s') = f s in put @t s' >> pure a
class Monad m
=> HasState (tag :: k) (s :: *) (m :: * -> *) | tag m -> s
where
-- | For technical reasons, this method needs an extra proxy argument.
-- You only need it if you are defining new instances of 'HasState.
-- Otherwise, you will want to use 'get'.
-- See 'get' for more documentation.
get_ :: Proxy# tag -> m s
-- | For technical reasons, this method needs an extra proxy argument.
-- You only need it if you are defining new instances of 'HasState.
-- Otherwise, you will want to use 'put'.
-- See 'put' for more documentation.
put_ :: Proxy# tag -> s -> m ()
-- | For technical reasons, this method needs an extra proxy argument.
-- You only need it if you are defining new instances of 'HasState.
-- Otherwise, you will want to use 'state'.
-- See 'state' for more documentation.
state_ :: Proxy# tag -> (s -> (a, s)) -> m a

-- | @get \@tag@
-- retrieve the current state of the state capability @tag@.
get :: forall tag s m. HasState tag s m => m s
get = get_ (proxy# @_ @tag)
{-# INLINE get #-}

-- | @put \@tag s@
-- replace the current state of the state capability @tag@ with @s@.
put :: forall tag s m. HasState tag s m => s -> m ()
put = put_ (proxy# @_ @tag)
{-# INLINE put #-}

-- | @state \@tag f@
-- lifts a pure state computation @f@ to a monadic action in an arbitrary
-- monad @m@ with capability @HasState@.
--
-- Given the current state @s@ of the state capability @tag@
-- and @(a, s') = f s@, update the state to @s'@ and return @a@.
state :: forall tag s m a. HasState tag s m => (s -> (a, s)) -> m a
state = state_ (proxy# @_ @tag)
{-# INLINE state #-}

-- | @modify \@tag f@
-- given the current state @s@ of the state capability @tag@
-- and @s' = f s@, updates the state of the capability @tag@ to @s'@.
modify :: forall tag s m. HasState tag s m => (s -> s) -> m ()
modify f = state @tag $ \s -> ((), f s)
{-# INLINE modify #-}

-- | Same as 'modify' but strict in the new state.
modify' :: forall tag s m. HasState tag s m => (s -> s) -> m ()
modify' f = do
s' <- get @tag
put @tag $! f s'
{-# INLINE modify' #-}

-- | @gets \@tag f@
-- retrieves the image, by @f@ of the current state
-- of the state capability @tag@.
--
-- prop> gets @tag f = f <$> get @tag
gets :: forall tag s m a. HasState tag s m => (s -> a) -> m a
gets f = do
s <- get @tag
Expand Down
18 changes: 15 additions & 3 deletions src/Capability/Stream.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,17 @@
{-# LANGUAGE UndecidableInstances #-}

module Capability.Stream
( HasStream(..)
( -- * Interface
HasStream(..)
, yield
-- * Strategies
, StreamStack(..)
, StreamDList(..)
-- ** Modifiers
, module Capability.Accessors
) where

import Capability.Accessors
import Capability.State
import Capability.Writer
import Control.Monad.IO.Class (MonadIO)
Expand All @@ -32,13 +37,20 @@ import Streaming
import qualified Streaming.Prelude as S

-- | Streaming capability.
--
-- An instance does not need to fulfill any additional laws
-- besides the monad laws.
class Monad m
=> HasStream (tag :: k) (a :: *) (m :: * -> *) | tag m -> a
where
-- | Use 'yield' instead.
-- | For technical reasons, this method needs an extra proxy argument.
-- You only need it if you are defining new instances of 'HasReader'.
-- Otherwise, you will want to use 'yield'.
-- See 'yield' for more documentation.
yield_ :: Proxy# tag -> a -> m ()

-- | Emit the given value.
-- | @yield \@tag a@
-- emits @a@ in the stream capability @tag@.
yield :: forall tag a m. HasStream tag a m => a -> m ()
yield = yield_ (proxy# @_ @tag)
{-# INLINE yield #-}
Expand Down
50 changes: 49 additions & 1 deletion src/Capability/Writer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,40 +33,88 @@
{-# OPTIONS_GHC -Wno-simplifiable-class-constraints #-}

module Capability.Writer
( HasWriter(..)
( -- * Interface
HasWriter(..)
, writer
, tell
, listen
, pass
-- * Strategies
, WriterLog(..)
-- ** Modifiers
, module Capability.Accessors
) where

import Capability.Accessors
import Capability.State
import Control.Monad.IO.Class (MonadIO)
import Control.Monad.Primitive (PrimMonad)
import Data.Coerce (coerce)
import GHC.Exts (Proxy#, proxy#)

-- | Writer capability
--
-- An instance should fulfill the following laws.
-- At this point these laws are not definitive,
-- see <https://github.com/haskell/mtl/issues/5>.
--
-- prop> listen @t (pure a) = pure (a, mempty)
-- prop> listen @t (tell @t w) = tell @t w >> pure (w, w)
-- prop> listen @t (m >>= k) = listen @t m >>= \(a, w1) -> listen @t (k a) >>= \(b, w2) -> pure (b, w1 `mappend` w2)
-- prop> pass @t (tell @t w >> pure (a, f)) = tell @t (f w) >> pure a
-- prop> writer @t (a, w) = tell @t w >> pure a
class (Monoid w, Monad m)
=> HasWriter (tag :: k) (w :: *) (m :: * -> *) | tag m -> w
where
-- | For technical reasons, this method needs an extra proxy argument.
-- You only need it if you are defining new instances of 'HasReader'.
-- Otherwise, you will want to use 'writer'.
-- See 'writer' for more documentation.
writer_ :: Proxy# tag -> (a, w) -> m a
-- | For technical reasons, this method needs an extra proxy argument.
-- You only need it if you are defining new instances of 'HasReader'.
-- Otherwise, you will want to use 'tell'.
-- See 'tell' for more documentation.
tell_ :: Proxy# tag -> w -> m ()
-- | For technical reasons, this method needs an extra proxy argument.
-- You only need it if you are defining new instances of 'HasReader'.
-- Otherwise, you will want to use 'listen'.
-- See 'listen' for more documentation.
listen_ :: Proxy# tag -> m a -> m (a, w)
-- | For technical reasons, this method needs an extra proxy argument.
-- You only need it if you are defining new instances of 'HasReader'.
-- Otherwise, you will want to use 'pass'.
-- See 'pass' for more documentation.
pass_ :: Proxy# tag -> m (a, w -> w) -> m a

-- | @writer \@tag (a, w)@
-- lifts a pure writer action @(a, w)@ to a monadic action in an arbitrary
-- monad @m@ with capability @HasWriter@.
--
-- Appends @w@ to the output of the writer capability @tag@
-- and returns the value @a@.
writer :: forall tag w m a. HasWriter tag w m => (a, w) -> m a
writer = writer_ (proxy# @_ @tag)
{-# INLINE writer #-}

-- | @tell \@tag w@
-- appends @w@ to the output of the writer capability @tag@.
tell :: forall tag w m. HasWriter tag w m => w -> m ()
tell = tell_ (proxy# @_ @tag)
{-# INLINE tell #-}

-- | @listen \@tag m@
-- executes the action @m@ and returns the output of @m@
-- in the writer capability @tag@ along with result of @m@.
-- Appends the output of @m@ to the output of the writer capability @tag@.
listen :: forall tag w m a. HasWriter tag w m => m a -> m (a, w)
listen = listen_ (proxy# @_ @tag)
{-# INLINE listen #-}

-- | @pass \@tag m@
-- executes the action @m@. Assuming @m@ returns @(a, f)@ and appends
-- @w@ to the output of the writer capability @tag@.
-- @pass \@tag m@ instead appends @w' = f w@ to the output and returns @a@.
pass :: forall tag w m a. HasWriter tag w m => m (a, w -> w) -> m a
pass = pass_ (proxy# @_ @tag)
{-# INLINE pass #-}
Expand Down