Safe Haskell | None |
---|---|
Language | Haskell2010 |
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
(IO
a) ->IO
a)free
package, you know how it's done). Then, wrap that handler (e.g. with the shape
) in anClipboardF
a -> 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 -- :: (MonadClipboard
m,MonadOpenUrl
m) => m () s <-getClipboard
-- :: (MonadClipboard
m ) => m StringopenUrl
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 (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)
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 = \caseGetClipboard
f ->sh_GetClipboard
>>=
fSetClipboard
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
overloaded constructors
the interpreter
interpretOpenUrl :: Interpreter IO `[OpenUrlF]` Source
interpretOpenUrl =singletonInterpreter
$ caseOpenUrl
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 = \caseOpenUrl
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 data
types 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 '[
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 <-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] ()
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
$Ask
id
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
$Tell
w ()
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
$Get
id
put :: MonadState s m effects => s -> m () Source
put s =liftL
$Put
s ()
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 -- (theOpNaturalTransformation
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] whereliftL
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
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