vinyl-effects-0.0.0: TODO

Safe HaskellNone
LanguageHaskell2010

Vinyl.Effects.Example

Contents

Description

This module defines two effects (Clipboard and OpenUrl), and then composes them (Workflow) "on-the-fly".

For each effect, we:

You can use these effects extensibly, "mtl-style". e.g. Since getClipboard and openUrl are overloaded, they can both be used in openFromClipboard.

openFromClipboard = do   -- :: (MonadClipboard m, MonadOpenUrl m) => m ()
  s <- getClipboard      -- :: (MonadClipboard m                ) => m String
  openUrl s              -- :: (                  MonadOpenUrl m) => m ()

Note:

  • the type of openFromClipboard is inferred.
  • the constraints are aliases; you don't need to write a new class for each new effect type.
  • for compositions of effects (like MonadWorkflow), you don't even need to write a new type. Just append the interpreters you want (with appendInterpreters).

Synopsis

Documentation

main :: IO () Source

run with:

stack build && stack exec example-vinyl-effects

(read the source too).

Effect #1: Clipboard

type MonadClipboard m effects = (MonadLanguage m effects, ClipboardF effects) Source

the constraint

type Clipboard = `[ClipboardF]` Source

the set of effects (one)

data ClipboardF k Source

the functor

Constructors

GetClipboard (String -> k) 
SetClipboard String k 

overloaded constructors

getClipboard :: MonadClipboard m effects => m String Source

getClipboard = liftL $ GetClipboard id

setClipboard :: MonadClipboard m effects => String -> m () Source

setClipboard s = liftL $ SetClipboard s ()

e.g. reverseClipboard

reverseClipboard :: MonadClipboard m effects => m () Source

derived from the two primitves.

the interpreter

runClipboard :: Language `[ClipboardF]` :~> IO Source

calls interpretLanguage.

when using free monads directly, you would:

runClipboard = iterTM handleClipboard

interpretClipboard :: Interpreter IO `[ClipboardF]` Source

definition #1: "inject" a handler into an interpreter with singletonInterpreter.

singletonInterpreter handleClipboard

interpretClipboard2 :: Interpreter IO `[ClipboardF]` Source

definition #2: constructed and interpreted directly from single handler.

= Interpreter
  $ HandlerM handleClipboard
  :& RNil

handleClipboard :: AnAlgebra ClipboardF (IO a) Source

glue the functor to its effects.

handleClipboard = \case
  GetClipboard f   -> sh_GetClipboard >>= f
  SetClipboard s k -> sh_SetClipboard s >> k

the implementation

sh_GetClipboard :: IO String Source

shells out ($ pbpaste), works only on OSX.

sh_SetClipboard :: String -> IO () Source

shells out ($ ... | pbcopy), works only on OSX. blocking.

Effect #2: Clipboard

type MonadOpenUrl m effects = (MonadLanguage m effects, OpenUrlF effects) Source

the constraint

type OpenUrl = `[OpenUrlF]` Source

the set of effects (one)

data OpenUrlF k Source

the functor

overloaded constructors

openUrl :: MonadOpenUrl m effects => String -> m () Source

openUrl s = liftL $ OpenUrl s ()

the interpreter

runOpenUrl :: Language `[OpenUrlF]` :~> IO Source

runOpenUrl = interpretLanguage interpretOpenUrl

interpretOpenUrl :: Interpreter IO `[OpenUrlF]` Source

interpretOpenUrl = singletonInterpreter $ case
  OpenUrl s k -> sh_OpenUrl s >> k

can extract the "co-algebra" with

handleOpenUrl = fromSingletonInterpreter interpretOpenUrl

handleOpenUrl :: AnAlgebra OpenUrlF (IO a) Source

glue the functor to its effects.

handleOpenUrl = \case
  OpenUrl s k -> sh_OpenUrl s >> k

the implementation

sh_OpenUrl :: String -> IO () Source

shells out ($ open ...), should work cross-platform. blocking.

Workflow: #1 + #2

type MonadWorkflow m effects = (MonadClipboard m effects, MonadOpenUrl m effects) Source

a constraint (with -XConstraintKinds).

type Workflow = `[ClipboardF, OpenUrlF]` Source

a set of two effects.

runWorkflow :: Language Workflow :~> IO Source

run an ad-hoc grouping of two effects.

runWorkflow = interpretLanguage interpretWorkflow1

can run any action of type:

(MonadWorkflow m effects) => m a

interpretWorkflow1 :: Interpreter IO Workflow Source

definition #1: compose interpreters by appending vinyl records.

interpretWorkflow = appendInterpreters interpretClipboard interpretOpenUrl

no new Either-like datatypes needed, the type-aliases are only for clarity.

interpretWorkflow2 :: Interpreter IO Workflow Source

definition #2: Construct an interpreter directly, via handlers.

Interpreter
  $ HandlerM handleClipboard
 :& HandlerM handleOpenUrl
 :& RNil

interpretOpenUrl2 :: Interpreter IO OpenUrl Source

If we can handle an effect, plus some others; then we can handle that effect, alone.

interpretOpenUrl2 = downcastInterpreter interpretWorkflow1

This casts '[ClipboardF,OpenUrlF] down to '[OpenUrlF].

(For example, some library exports only a single interpreter that handles five effects. We can reconstruct an interpreter that handles only three of those effects with a one-liner).

e.g. openFromClipboard

openFromClipboard :: (RElem (* -> *) OpenUrlF effects (RIndex (* -> *) OpenUrlF effects), RElem (* -> *) ClipboardF effects (RIndex (* -> *) ClipboardF effects), MonadLanguage m effects) => m () Source

an effect to visit the url that's currently in the clipboard.

uses two distinct effects, i.e. it's a Workflow action.

openFromClipboard = do
  s <- getClipboard
  openUrl s

Inferred (with NoMonomorphismRestriction):

 :: ( MonadClipboard m effects
    , MonadOpenUrl m effects
    )
 => m ()

(the same, without aliases)

 :: ( MonadLanguage m effects
    , RElem ClipboardF effects (RIndex ClipboardF effects)
    , RElem OpenUrlF   effects (RIndex OpenUrlF   effects)
    )
 => m ()

(which is what haddock displays, unformatted).

i.e. "any monad, that supports any set of effects that have at least ClipboardF and OpenUrlF".

you can specialize the effects:

openFromClipboard
 :: (MonadLanguage m [ClipboardF, OpenUrlF])
 => m ()

i.e. "any monad, that supports exactly two effects, ClipboardF and OpenUrlF".

or the monad:

openFromClipboard
  :: (ClipboardF ∈ effects, OpenUrlF ∈ effects)
  => Language effects ()

or both:

openFromClipboard
 () =>
 :: Language [ClipboardF, OpenUrlF] ()

Reader, as an effect

type MonadReader r m effects = (MonadLanguage m effects, ReaderF r effects) Source

the constraint

type Reader r = `[ReaderF r]` Source

the set of effects (one)

data ReaderF r k Source

the functor

Instances

Functor (ReaderF r) Source 
Monoid w => MonadLanguage (RWS r w s) ((:) (* -> *) (ReaderF r) ((:) (* -> *) (WriterF w) ((:) (* -> *) (StateF s) ([] (* -> *))))) Source 

ask :: MonadReader r m effects => m r Source

ask = liftL $ Ask id

Writer, as an effect

type MonadWriter w m effects = (MonadLanguage m effects, WriterF w effects) Source

the constraint

type Writer w = `[WriterF w]` Source

the set of effects (one)

data WriterF w k Source

the functor

Instances

Functor (WriterF w) Source 
Monoid w => MonadLanguage (RWS r w s) ((:) (* -> *) (ReaderF r) ((:) (* -> *) (WriterF w) ((:) (* -> *) (StateF s) ([] (* -> *))))) Source 

tell :: MonadWriter w m effects => w -> m () Source

tell w = liftL $ Tell w ()

State, as an effect

type MonadState s m effects = (MonadLanguage m effects, StateF s effects) Source

the constraint

type State s = `[StateF s]` Source

the set of effects (one)

data StateF s k Source

the functor

Instances

Functor (StateF s) Source 
Monoid w => MonadLanguage (RWS r w s) ((:) (* -> *) (ReaderF r) ((:) (* -> *) (WriterF w) ((:) (* -> *) (StateF s) ([] (* -> *))))) Source 

get :: MonadState s m effects => m s Source

get = liftL $ Get id

put :: MonadState s m effects => s -> m () Source

put s = liftL $ Put s ()

instance MonadLanguge RWS

newtype RWS r w s a Source

since 'MonadLanguage is a class, even though RWS is a custom monad (not a Language), you can still provide an instance.

The instance declares that RWS has three effects: reading an environment, logging, and state access. Which means that the functors (ReaderF, WriterF, StateF) can be injected into our concrete/custom monad stack.

Thus exampleRWS, which is built with overloaded procedures (ask,tell,get,put are from this module, not the mtl package), can be specialized to both a Language:

-- exampleRWS_specializedLanguage
exampleRWS
 :: (Num i, Show i)
 => Language [ReaderF Bool, WriterF [String], StateF i] i

and the more familiar RWS:

-- exampleRWS_specializedRWS
exampleRWS
 :: (Num i, Show i)
 => RWS Bool [String] i i

To "lift the effect", we define a record of liftings for **each** possible effect:

liftRWS
   = OpNaturalTransformation liftReaderF
  :& OpNaturalTransformation liftWriterF
  :& OpNaturalTransformation liftStateF
  :& RNil

  -- (the OpNaturalTransformation is boilerplate)

and then perform a record lookup for **the** particular effect given at runtime:

instance (Monoid w) => MonadLanguage (RWS r w s) [ReaderF r, WriterF w, StateF s] where
  liftL effect = getOpNaturalTransformation (rget Proxy liftRWS) effect

For example, when the effect is ask:

liftL ask
=
liftL ask
=
getOpNaturalTransformation (rget Proxy liftRWS) ask
=
getOpNaturalTransformation (OpNaturalTransformation liftReaderF) ask
=
liftReaderF ask
=
liftReaderF (Ask id)
=
RWS $ ReaderT $ r -> do
  return (id r)
=
RWS $ ReaderT $ r -> do
  return r
=
RWS $ ReaderT return

Constructors

RWS 

Fields

getRWS :: ReaderT r (WriterT w (StateT s Identity)) a
 

Instances

Monoid w => Monad (RWS r w s) Source 
Functor (RWS r w s) Source 
Monoid w => Applicative (RWS r w s) Source 
Monoid w => MonadLanguage (RWS r w s) ((:) (* -> *) (ReaderF r) ((:) (* -> *) (WriterF w) ((:) (* -> *) (StateF s) ([] (* -> *))))) Source 

runRWS :: Monoid w => r -> s -> RWS r w s a -> (a, w, s) Source

liftReaderF :: Monoid w => ReaderF r a -> RWS r w s a Source

liftWriterF :: Monoid w => WriterF w a -> RWS r w s a Source

liftStateF :: Monoid w => StateF s a -> RWS r w s a Source

exampleRWS :: (MonadReader Bool m effects, MonadWriter [String] m effects, MonadState i m effects, Num i, Show i) => m i Source

uses all four effectful operations (ask,tell,get,put).