capability: Extensional capabilities and deriving combinators

This is a package candidate release! Here you can preview how this package release will appear once published to the main package index (which can be accomplished via the 'maintain' link below). Please note that once a package has been published to the main package index it cannot be undone! Please consult the package uploading documentation for more information.

[maintain] [Publish]

Standard capability type classes for extensional effects and combinators to derive capability instances with little boilerplate.


[Skip to Readme]

Properties

Versions 0.1.0.0, 0.1.0.0, 0.2.0.0, 0.3.0.0, 0.4.0.0, 0.5.0.0, 0.5.0.1
Change log ChangeLog.md
Dependencies base (>=4.12 && <5.0), dlist (>=0.8 && <0.9), exceptions (>=0.6 && <0.11), generic-lens (>=1.0 && <1.1), lens (>=4.16 && <5.0), monad-control (>=1.0 && <1.1), monad-unlift (>=0.2 && <0.3), mtl (>=2.0 && <3.0), mutable-containers (>=0.3 && <0.4), primitive (>=0.6 && <0.7), safe-exceptions (>=0.1 && <0.2), streaming (>=0.2 && <0.3), transformers (>=0.5.5 && <0.6), unliftio (>=0.2 && <0.3), unliftio-core (>=0.1 && <0.2) [details]
License BSD-3-Clause
Copyright 2018 EURL Tweag
Author
Maintainer andreas.herrmann@tweag.io
Category Control
Home page https://github.com/tweag/capability
Source repo head: git clone https://github.com/tweag/capability
Uploaded by aherrmann at 2018-10-09T07:23:03Z

Modules

[Index] [Quick Jump]

Flags

Automatic Flags
NameDescriptionDefault
hspec-jenkins

You can enable the use of the `hspec-jenkins` package using `-fhspec-jenkins`.

This package allows JUnit formatted test reporting for CI.

Disabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees


Readme for capability-0.1.0.0

[back to package description]

capability: effects, extensionally

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:

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,

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:

An example usage looks like this:

testParity :: (HasReader "foo" Int m, HasState "bar" Bool m) => m ()
testParity = do
  num <- ask @"foo"
  put @"bar" (even num)

data Ctx = Ctx { foo :: Int, bar :: IORef Bool }
  deriving Generic

newtype M a = M { runM :: Ctx -> IO a }
  deriving (Functor, Applicative, Monad) via ReaderT Ctx IO
  -- Use DerivingVia to derive a HasReader instance.
  deriving (HasReader "foo" Int) via
    -- Pick the field foo from the Ctx record in the ReaderT environment.
    Field "foo" "ctx" (MonadReader (ReaderT Ctx IO))
  -- Use DerivingVia to derive a HasState instance.
  deriving (HasState "bar" Bool) via
    -- Convert a reader of IORef to a state capability.
    ReaderIORef (Field "bar" "ctx" (MonadReader (ReaderT Ctx IO)))

example :: IO ()
example = do
    rEven <- newIORef False
    runM testParity (Ctx 2 rEven)
    readIORef rEven >>= print
    runM testParity (Ctx 3 rEven)
    readIORef rEven >>= print

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

This package is not available on Hackage yet, as some of its dependencies have not been updated to GHC 8.6, yet.

API documentation can be found in the artifacts tab of any successful build in the CircleCI project.

Examples

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

$ nix-shell --pure --run "cabal configure --enable-tests"
$ nix-shell --pure --run "cabal repl examples"

ghci> :set -XOverloadedStrings
ghci> wordAndLetterCount "ab ba"
Letters
'a': 2
'b': 2
Words
"ab": 1
"ba": 1

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

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

Build instructions

Nix Shell

Some of this package's dependencies require patches to build with GHC 8.6. These patches are defined in nix/haskell/default.nix. A development environment with all patched dependencies in scope is defined in shell.nix.

Cachix Nix Cache

A Nix cache for this package's dependencies is provided via cachix. If you have cachix installed, then you can activate it by executing

$ cachix use tweag

Build

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

$ nix-shell --pure --run "cabal configure"
$ nix-shell --pure --run "cabal build"