| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
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:
- Define a functor (e.g.
ClipboardF). - (optionally, for convenience, define aliases for the constraint
(e.g.
MonadClipboard) and effect-set (e.g.Clipboard)). - Define overloaded constructors (e.g.
getClipboard,setClipboard). TODO th. - Define a handler (e.g.
(), which involves minimal boilerplate. (if you've used thehandleClipboard::ClipboardF(IOa) ->IOa)freepackage, you know how it's done). Then, wrap that handler (e.g. with the shape) in anClipboardFa -> aInterpreter, for extensibility.
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 -- :: (MonadClipboardm,MonadOpenUrlm) => m () s <-getClipboard-- :: (MonadClipboardm ) => m StringopenUrls -- :: (MonadOpenUrlm) => m ()
Note:
- the type of
openFromClipboardis 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 (withappendInterpreters).
- main :: IO ()
- type MonadClipboard m effects = (MonadLanguage m effects, ClipboardF ∈ effects)
- type Clipboard = `[ClipboardF]`
- data ClipboardF k
- = GetClipboard (String -> k)
- | SetClipboard String k
- getClipboard :: MonadClipboard m effects => m String
- setClipboard :: MonadClipboard m effects => String -> m ()
- reverseClipboard :: MonadClipboard m effects => m ()
- runClipboard :: Language `[ClipboardF]` :~> IO
- interpretClipboard :: Interpreter IO `[ClipboardF]`
- interpretClipboard2 :: Interpreter IO `[ClipboardF]`
- handleClipboard :: AnAlgebra ClipboardF (IO a)
- sh_GetClipboard :: IO String
- sh_SetClipboard :: String -> IO ()
- type MonadOpenUrl m effects = (MonadLanguage m effects, OpenUrlF ∈ effects)
- type OpenUrl = `[OpenUrlF]`
- data OpenUrlF k
- openUrl :: MonadOpenUrl m effects => String -> m ()
- runOpenUrl :: Language `[OpenUrlF]` :~> IO
- interpretOpenUrl :: Interpreter IO `[OpenUrlF]`
- handleOpenUrl :: AnAlgebra OpenUrlF (IO a)
- sh_OpenUrl :: String -> IO ()
- type MonadWorkflow m effects = (MonadClipboard m effects, MonadOpenUrl m effects)
- type Workflow = `[ClipboardF, OpenUrlF]`
- runWorkflow :: Language Workflow :~> IO
- interpretWorkflow1 :: Interpreter IO Workflow
- interpretWorkflow2 :: Interpreter IO Workflow
- interpretOpenUrl2 :: Interpreter IO OpenUrl
- openFromClipboard :: (RElem (* -> *) OpenUrlF effects (RIndex (* -> *) OpenUrlF effects), RElem (* -> *) ClipboardF effects (RIndex (* -> *) ClipboardF effects), MonadLanguage m effects) => m ()
- openFromClipboard_nothingSpecialized :: MonadWorkflow m effects => m ()
- openFromClipboard_monadSpecialized :: (ClipboardF ∈ effects, OpenUrlF ∈ effects) => Language effects ()
- openFromClipboard_effectsSpecialized :: MonadLanguage m `[ClipboardF, OpenUrlF]` => m ()
- openFromClipboard_bothSpecialized :: Language `[ClipboardF, OpenUrlF]` ()
- type MonadReader r m effects = (MonadLanguage m effects, ReaderF r ∈ effects)
- type Reader r = `[ReaderF r]`
- data ReaderF r k
- ask :: MonadReader r m effects => m r
- type MonadWriter w m effects = (MonadLanguage m effects, WriterF w ∈ effects)
- type Writer w = `[WriterF w]`
- data WriterF w k
- tell :: MonadWriter w m effects => w -> m ()
- type MonadState s m effects = (MonadLanguage m effects, StateF s ∈ effects)
- type State s = `[StateF s]`
- data StateF s k
- get :: MonadState s m effects => m s
- put :: MonadState s m effects => s -> m ()
- newtype RWS r w s a = RWS {}
- runRWS :: Monoid w => r -> s -> RWS r w s a -> (a, w, s)
- liftRWS :: Monoid w => Rec (OpNaturalTransformation (RWS r w s)) `[ReaderF r, WriterF w, StateF s]`
- liftReaderF :: Monoid w => ReaderF r a -> RWS r w s a
- liftWriterF :: Monoid w => WriterF w a -> RWS r w s a
- liftStateF :: Monoid w => StateF s a -> RWS r w s a
- exampleRWS :: (MonadReader Bool m effects, MonadWriter [String] m effects, MonadState i m effects, Num i, Show i) => m i
- exampleRWS_specializedLanguage :: (Num i, Show i) => Language `[ReaderF Bool, WriterF [String], StateF i]` i
- exampleRWS_specializedRWS :: (Num i, Show i) => RWS Bool [String] i i
Documentation
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 |
Instances
overloaded constructors
getClipboard :: MonadClipboard m effects => m String Source
getClipboard =liftL$GetClipboardid
setClipboard :: MonadClipboard m effects => String -> m () Source
setClipboard s =liftL$SetClipboards ()
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.
singletonInterpreterhandleClipboard
interpretClipboard2 :: Interpreter IO `[ClipboardF]` Source
definition #2: constructed and interpreted directly from single handler.
=Interpreter$HandlerMhandleClipboard:&RNil
handleClipboard :: AnAlgebra ClipboardF (IO a) Source
glue the functor to its effects.
handleClipboard = \caseGetClipboardf ->sh_GetClipboard>>=fSetClipboards k ->sh_SetClipboards>>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
overloaded constructors
the interpreter
interpretOpenUrl :: Interpreter IO `[OpenUrlF]` Source
interpretOpenUrl =singletonInterpreter$ caseOpenUrls k ->sh_OpenUrls >> k
can extract the "co-algebra" with
handleOpenUrl = fromSingletonInterpreter interpretOpenUrl
handleOpenUrl :: AnAlgebra OpenUrlF (IO a) Source
glue the functor to its effects.
handleOpenUrl = \caseOpenUrls k ->sh_OpenUrls>>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 ainterpretWorkflow1 :: Interpreter IO Workflow Source
definition #1: compose interpreters by appending vinyl records.
interpretWorkflow =appendInterpretersinterpretClipboardinterpretOpenUrl
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$HandlerMhandleClipboard:&HandlerMhandleOpenUrl:& RNil
interpretOpenUrl2 :: Interpreter IO OpenUrl Source
If we can handle an effect, plus some others; then we can handle that effect, alone.
interpretOpenUrl2=downcastInterpreterinterpretWorkflow1
This casts '[ down to ClipboardF,OpenUrlF]'[.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 <-getClipboardopenUrls
Inferred (with NoMonomorphismRestriction):
:: (MonadClipboardm effects ,MonadOpenUrlm effects ) => m ()
(the same, without aliases)
:: (MonadLanguagem effects ,RElemClipboardF effects (RIndexClipboardF effects) ,RElemOpenUrlF effects (RIndexOpenUrlF 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] ()
openFromClipboard_nothingSpecialized :: MonadWorkflow m effects => m () Source
openFromClipboard_monadSpecialized :: (ClipboardF ∈ effects, OpenUrlF ∈ effects) => Language effects () Source
openFromClipboard_effectsSpecialized :: MonadLanguage m `[ClipboardF, OpenUrlF]` => m () Source
Reader, as an effect
type MonadReader r m effects = (MonadLanguage m effects, ReaderF r ∈ effects) Source
the constraint
the functor
ask :: MonadReader r m effects => m r Source
ask =liftL$Askid
Writer, as an effect
type MonadWriter w m effects = (MonadLanguage m effects, WriterF w ∈ effects) Source
the constraint
the functor
tell :: MonadWriter w m effects => w -> m () Source
tell w =liftL$Tellw ()
State, as an effect
type MonadState s m effects = (MonadLanguage m effects, StateF s ∈ effects) Source
the constraint
the functor
get :: MonadState s m effects => m s Source
get =liftL$Getid
put :: MonadState s m effects => s -> m () Source
put s =liftL$Puts ()
instance MonadLanguge RWS
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= OpNaturalTransformationliftReaderF:& OpNaturalTransformationliftWriterF:& OpNaturalTransformationliftStateF:& RNil -- (theOpNaturalTransformationis boilerplate)
and then perform a record lookup for **the** particular effect given at runtime:
instance (Monoidw) =>MonadLanguage(RWS r w s) [ReaderFr,WriterFw,StateFs] whereliftLeffect =getOpNaturalTransformation(rgetProxyliftRWS) 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
liftRWS :: Monoid w => Rec (OpNaturalTransformation (RWS r w s)) `[ReaderF r, WriterF w, StateF 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
exampleRWS_specializedLanguage :: (Num i, Show i) => Language `[ReaderF Bool, WriterF [String], StateF i]` i Source