Safe Haskell | Safe |
---|---|

Language | Haskell2010 |

A capability is a type class over a monad which specifies the effects that a function is allowed to perform. Capabilities differ from traditional monad transformer type classes in that they are completely independent of the way the monad is constructed. A state capability can for instance be implemented as a lens on a field in a larger state monad, or an error capability could provide for throwing only a subset of the errors of an error monad.

This library defines several standard, reusable capabilities that replace the mtl's monad-transformer type classes. Because capabilities are not tied to a particular implementation of the monad, they cannot be discharged by instance resolution. Instead this library provides combinators in the form of newtypes with instances, to be used with deriving-via. To learn about deriving via, watch Baldur Blondal's introductory video https://skillsmatter.com/skillscasts/10934-lightning-talk-stolen-instances-taste-just-fine.

By way of comparison, with the mtl you would write something like

foo :: (MonadReader E, MonadState S) => a -> m ()

You can use `foo`

at type `a -> ReaderT E (State S)`

. But you can't use `foo`

with the `ReaderT`

pattern
https://www.fpcomplete.com/blog/2017/06/readert-design-pattern. With this
library, you would instead have:

foo :: (HasReader "conf" E, HasState "st" S) => a -> m ()

Where `"conf"`

and `"st"`

are the names (also referred to as tags) of the
capabilities demanded by foo. Contrary to the mtl, capabilities are named,
rather than disambiguated by the type of their implied state, or exception.
This makes it easy to have multiple state capabilities.

To *provide* these capabilities, for instance with the ReaderT pattern, do as
follows (for a longer form tutorial, check the
README):

newtype MyM a = MyM (ReaderT (E, IORef s)) deriving (Functor, Applicative, Monad) deriving (HasState "st" Int) via ReaderIORef (Rename 2 (Pos 2 () (MonadReader (ReaderT (E, IORef s) IO)))) deriving (HasReader "conf" Int) via (Rename 1 (Pos 1 () (MonadReader (ReaderT (E, IORef s) IO))))

Then you can use `foo`

at type `MyM`

. Or any other type which can provide
these capabilites.

## Module structure

Each module introduces a capability type class (or several related type classes). Each class comes with a number of instances on newtypes (each newtype should be seen as a combinator to be used with deriving-via to provide the capability). Many newtypes come from the common Capability.Accessors module (re-exported by each of the other modules), which in particular contains a number of ways to address components of a data type using the generic-lens library.

- Capability.Reader reader effects
- Capability.State state effects
- Capability.Writer writer effects
- Capability.Error throw and catch errors
- Capability.Stream streaming effect (aka generators)

Some of the capability modules have a “discouraged” companion (such as Capability.Writer.Discouraged). These modules contain deriving-via combinators which you can use if you absolutely must: they are correct, but inefficient, so we recommend that you do not.

## Further considerations

The tags of capabilities can be of any kind, they are not restricted to symbols. When exporting functions demanding capabilities in libraries, it is recommended to use a type as follows:

data Conf foo :: HasReader Conf C => m ()

This way, `Conf`

can be qualified in case of a name conflict with another
library.