avail: Low-overhead effect management for concrete monads

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]

Please see the README on GitHub at https://github.com/re-xyr/avail#readme


[Skip to Readme]

Properties

Versions 0.1.0.0
Change log CHANGELOG.md
Dependencies base (>=4.14 && <5), capability (>=0.4 && <0.6), exceptions (==0.10.*), monad-control (==1.0.*), mtl (==2.2.*), primitive (==0.7.*), template-haskell (>=2.16 && <3), transformers-base (==0.4.*), unliftio-core (==0.2.*) [details]
License BSD-3-Clause
Copyright 2021 Xy Ren
Author Xy Ren
Maintainer xy.r@outlook.com
Category Control
Home page https://github.com/re-xyr/avail#readme
Bug tracker https://github.com/re-xyr/avail/issues
Source repo head: git clone https://github.com/re-xyr/avail
Uploaded by daylily at 2021-11-12T11:24:11Z

Modules

[Index]

Downloads

Maintainer's Corner

For package maintainers and hackage trustees


Readme for avail-0.1.0.0

[back to package description]

avail

avail is a companion to monad transformers that allows you to impose effect constraints on concrete monads. Specifically, instead of writing

myApp :: (MonadWriter Log m, MonadState Store m, MonadReader Env m) => m ()

it allows you to write

myApp :: Effs '[MonadWriter Log, MonadState Store, MonadReader Env] => App ()

where App is a specific, concrete monad stack.

Introduction

Current effects libraries all have one principle of effect restriction: an effect can be used in a monad if it can be interpreted in terms of the monad. This works well with a polymorphic monad type, but a polymorphic type is unfriendly to compiler optimization. In contrast, a concrete monad can be easily optimized, but if we fix a monad that supplies all the effects we need, we can no longer restrict what effects each function can use.

avail solves this problem with the phantom constraint pattern. We use a newtype wrapper M to screen out the user from directly manipulating the underlying monad, and performing any operation in a typeclass (for example, MonadIO) requires the corresponding phantom Eff constraint (in this case. Eff MonadIO) to be in scope. In other words, one can perform operations of a class only if:

The second requirement decouples the availability of effects from the monad implementation. At last, we use a function runM to clear the constraints and restore the underlying monad. A typical example looks like this:

import Avail
import Control.Monad.Reader
import Control.Monad.State

type App = M (ReaderT Int (State Bool))

testParity :: Effs '[MonadReader Int, MonadState Bool] => App ()
testParity = do
  num <- ask
  put (even num)

example :: IO ()
example = do
  print $ runM @'[MonadReader Int, MonadState Bool] testParity
    & (`runReaderT` 2)
    & (`execState` False)
  print $ runM @'[MonadReader Int, MonadState Bool] testParity
    & (`runReaderT` 3)
    & (`execState` False)

Through microbenchmarks and tests in some applications, the performance of using avail is at least on par with, and often better than, using the polymorphic counterparts.

Making avail work with new typeclasses

avail already comes with support of all mtl, exceptions, unliftio, monad-control and capability typeclasses. To add support for your own typeclass, for example:

class MonadOvO m where
  ...

You can use the Template Haskell utilities in the Avail.Derive module.

import Avail.Derive
avail [t| MonadOvO |]

There may be other more complicated cases, such as dependencies between classes and multi-param classes:

class (MonadOvO m, MonadQwQ m) => MonadUwU r m where
  ...

avail gets you covered:

import Avail.Derive
with1 \r -> avail'
  [ [t| MonadOvO |]
  , [t| MonadQwQ |]
  ] [t| MonadUwU $r |]

Limitations

Where is availability?

The old availability is abandoned due to its attempt at addressing two problems at once (capability and availability management), and may introduce confusion to new users on the choice between capability and availability. The new avail library focuses on availability management and acts not as a standalone effects library, but an alternative effect management layer that is used together with mtl or capability (which also reduces preformance overhead).

The old availability can be found at re-xyr/availability-old.