项目作者: tweag

项目描述 :
Extensional capabilities and deriving combinators
高级语言: Haskell
项目地址: git://github.com/tweag/capability.git
创建时间: 2018-08-09T07:23:27Z
项目社区:https://github.com/tweag/capability

开源协议:BSD 3-Clause "New" or "Revised" License

下载


capability: effects, extensionally

Build status

A capability is a type class that says explicitly which effects
a function is allowed to use. The mtl works like this too.
But unlike the mtl, this library decouples effects from their
implementation. What this means in practice:

  • You can implement large sets of capabilities using the
    efficient ReaderT pattern, rather than a slow monad
    transformer stack.
  • Capabilities compose well: e.g. it’s easy to have multiple reader
    effects.
  • You can use a writer effect without implementing it as a writer
    monad (which is known to leak space).
  • You can reason about effects. For instance, if a monad provides a
    reader effect at type IORef A, it also provides a state effect at type A

For more on these, you may want to read the announcement blog
post
.

This library is an alternative to the mtl. It defines a set
of standard, reusable capability type classes, such as the HasReader
and HasState type classes, which provide the standard reader and
state effects, respectively.

Where mtl instances only need to be defined once and for all,
capability-style programming has traditionally suffered from verbose
boilerplate: rote instance definitions for every new implementation of
the capability. Fortunately GHC 8.6 introduced
the DerivingVia language extension. We use it to
remove the boilerplate, turning capability-style programming into an
appealing alternative to mtl-style programming. The
generic-lens library is used to access fields of
structure in the style of the ReaderT pattern.

An additional benefit of separating capabilities from their
implementation is that they avoid a pitfall of the mtl. In the
mtl, two different MonadState are disambiguated by their types,
which means that it is difficult to have two MonadState Int in the
same monad stack. Capability type classes are parameterized by a name
(also known as a tag). This makes it possible to combine multiple
versions of the same capability. For example,

  1. twoStates :: (HasState "a" Int m, HasState "b" Int m) => m ()

Here, the tags "a" and "b" refer to different state spaces.

In summary, compared to the mtl:

  • capabilities represent what effects a function can use, rather than
    how the monad is constructed;
  • capabilities are named, rather than disambiguated by type;
  • capabilites are discharged with deriving-via combinators
    and generic-lens, rather than with instance
    resolution.

An example usage looks like this:

  1. testParity :: (HasReader "foo" Int m, HasState "bar" Bool m) => m ()
  2. testParity = do
  3. num <- ask @"foo"
  4. put @"bar" (even num)
  5. data Ctx = Ctx { foo :: Int, bar :: IORef Bool }
  6. deriving Generic
  7. newtype M a = M { runM :: Ctx -> IO a }
  8. deriving (Functor, Applicative, Monad) via ReaderT Ctx IO
  9. -- Use DerivingVia to derive a HasReader instance.
  10. deriving (HasReader "foo" Int, HasSource "foo" Int) via
  11. -- Pick the field foo from the Ctx record in the ReaderT environment.
  12. Field "foo" "ctx" (MonadReader (ReaderT Ctx IO))
  13. -- Use DerivingVia to derive a HasState instance.
  14. deriving (HasState "bar" Bool, HasSource "bar" Bool, HasSink "bar" Bool) via
  15. -- Convert a reader of IORef to a state capability.
  16. ReaderIORef (Field "bar" "ctx" (MonadReader (ReaderT Ctx IO)))
  17. example :: IO ()
  18. example = do
  19. rEven <- newIORef False
  20. runM testParity (Ctx 2 rEven)
  21. readIORef rEven >>= print
  22. runM testParity (Ctx 3 rEven)
  23. readIORef rEven >>= print

For more complex examples, see the Examples section and
the examples subtree.

API documentation can be found on
Hackage.

Examples

An example is provided in WordCount.
Execute the following commands to try it out:

  1. $ nix-shell --pure --run "cabal configure --enable-tests"
  2. $ nix-shell --pure --run "cabal repl examples"
  3. ghci> :set -XOverloadedStrings
  4. ghci> wordAndLetterCount "ab ba"
  5. Letters
  6. 'a': 2
  7. 'b': 2
  8. Words
  9. "ab": 1
  10. "ba": 1

To execute all examples and see if they produce the expected results run

  1. $ nix-shell --pure --run "cabal test examples --show-details=streaming --test-option=--color"

Build instructions

Nix Shell

A development environment with all dependencies in scope is defined in
shell.nix.

Build

The build instructions assume that you have Nix installed.
Execute the following command to build the library.

  1. $ nix-shell --pure --run "cabal configure"
  2. $ nix-shell --pure --run "cabal build"