polysemy: Higher-order, low-boilerplate, zero-cost free monads.

[ bsd3, language, library ] [ Propose Tags ]
This version is deprecated.
Versions [RSS] 0.1.0.0, 0.1.1.0, 0.1.2.0, 0.1.2.1, 0.2.0.0, 0.2.1.0, 0.2.2.0, 0.3.0.0, 0.3.0.1, 0.4.0.0, 0.5.0.0, 0.5.0.1, 0.5.1.0, 0.6.0.0, 0.7.0.0, 1.0.0.0, 1.1.0.0, 1.2.0.0, 1.2.1.0, 1.2.2.0, 1.2.3.0, 1.3.0.0, 1.4.0.0, 1.5.0.0, 1.6.0.0, 1.7.0.0, 1.7.1.0, 1.8.0.0, 1.9.0.0, 1.9.1.0, 1.9.1.1, 1.9.1.2, 1.9.1.3 (info)
Change log ChangeLog.md
Dependencies base (>=4.7 && <5), mtl (>=2.2.2 && <2.3), random (>=1.1 && <1.2), syb (>=0.7 && <0.8), template-haskell (>=2.14.0.0 && <2.15), transformers (>=0.5.5.0 && <0.6) [details]
License BSD-3-Clause
Copyright 2019 Sandy Maguire
Author Sandy Maguire
Maintainer sandy@sandymaguire.me
Category Language
Home page https://github.com/isovector/polysemy#readme
Bug tracker https://github.com/isovector/polysemy/issues
Source repo head: git clone https://github.com/isovector/polysemy
Uploaded by isovector at 2019-04-26T16:24:43Z
Distributions Arch:1.9.1.3, LTSHaskell:1.9.1.3, NixOS:1.9.1.3, Stackage:1.9.1.3
Reverse Dependencies 74 direct, 4 indirect [details]
Downloads 18128 total (148 in the last 30 days)
Rating 2.75 (votes: 9) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for polysemy-0.1.2.0

[back to package description]

polysemy

Build Status Hackage

Dedication

The word 'good' has many meanings. For example, if a man were to shoot his grandmother at a range of five hundred yards, I should call him a good shot, but not necessarily a good man.

Gilbert K. Chesterton

Overview

polysemy is a library for writing high-power, low-boilerplate, zero-cost, domain specific languages. It allows you to separate your business logic from your implementation details. And in doing so, polysemy lets you turn your implementation code into reusable library code.

It's like mtl but composes better, requires less boilerplate, and avoids the O(n^2) instances problem.

It's like freer-simple but more powerful and 35x faster.

It's like fused-effects but with an order of magnitude less boilerplate.

Features

  • Effects are higher-order, meaning it's trivial to write bracket and local as first-class effects.
  • Effects are low-boilerplate, meaning you can create new effects in a single-digit number of lines. New interpreters are nothing but functions and pattern matching.
  • Effects are zero-cost, meaning that GHC1 can optimize away the entire abstraction at compile time.

1: Unfortunately this is not true in GHC 8.6.3, but will be true as soon as my patch lands.

Examples

Make sure you read the Necessary Language Extensions before trying these yourself!

Console effect:

{-# LANGUAGE TemplateHaskell #-}

import Polysemy

data Console m a where
  ReadTTY  :: Console m String
  WriteTTY :: String -> Console m ()

makeSem ''Console

runConsoleIO :: Member (Lift IO) r => Sem (Console ': r) a -> Sem r a
runConsoleIO = interpret $ \case
  ReadTTY      -> sendM getLine
  WriteTTY msg -> sendM $ putStrLn msg

Resource effect:

{-# LANGUAGE TemplateHaskell #-}

import qualified Control.Exception as X
import           Polysemy

data Resource m a where
  Bracket :: m a -> (a -> m ()) -> (a -> m b) -> Resource m b

makeSem ''Resource

runResource
    :: forall r a
     . Member (Lift IO) r
    => (∀ x. Sem r x -> IO x)
    -> Sem (Resource ': r) a
    -> Sem r a
runResource finish = interpretH $ \case
  Bracket alloc dealloc use -> do
    a <- runT  alloc
    d <- bindT dealloc
    u <- bindT use

    let runIt :: Sem (Resource ': r) x -> IO x
        runIt = finish .@ runResource

    sendM $ X.bracket (runIt a) (runIt . d) (runIt . u)

Easy.

Friendly Error Messages

Free monad libraries aren't well known for their ease-of-use. But following in the shoes of freer-simple, polysemy takes a serious stance on providing helpful error messages.

For example, the library exposes both the interpret and interpretH combinators. If you use the wrong one, the library's got your back:

runResource
    :: forall r a
     . Member (Lift IO) r
    => (∀ x. Sem r x -> IO x)
    -> Sem (Resource ': r) a
    -> Sem r a
runResource finish = interpret $ \case
  ...

makes the helpful suggestion:

    • 'Resource' is higher-order, but 'interpret' can help only
      with first-order effects.
      Fix:
        use 'interpretH' instead.
    • In the expression:
        interpret
          $ \case

Likewise it will give you tips on what to do if you forget a TypeApplication or forget to handle an effect.

Don't like helpful errors? That's OK too --- just flip the error-messages flag and enjoy the raw, unadulterated fury of the typesystem.

Necessary Language Extensions

You're going to want to stick all of this into your package.yaml file.

  ghc-options: -O2 -flate-specialise -fspecialise-aggressively
  default-extensions:
    - DataKinds
    - FlexibleContexts
    - GADTs
    - LambdaCase
    - PolyKinds
    - RankNTypes
    - ScopedTypeVariables
    - TypeApplications
    - TypeOperators
    - TypeFamilies