Core concepts
Effects
The Effect type is used to mutate the model over time in response to actions. It also allows IO to be scheduled for evaluation by the miso scheduler. IO is never evaluated inside Effect, it is only scheduled — there is no MonadIO instance.
Effect is defined as an RWS:
type Effect context props model action
= RWS (ComponentInfo context props) [Schedule context action] model ()- The Reader portion is
ComponentInfo:ask,asksandMiso.Lens.viewread its fields (the currentComponentId, the parent id, theDOMRefthe component is mounted on,props,context). - The Writer portion schedules
IO.tellcreates aSchedulethat runs according to itsSynchronicity; seewithSink. - The State portion is the
model:get,put,modifyand theMonadStatelens operators fromMiso.Lens.
Asynchronous IO
io- introduce asynchronous IO whose result is dispatched as an action;
io_discards the result. withSink- the core function from which most other combinators are defined — gives access to the event
Sink. The scheduler attaches exception handlers to all IO. issue- dispatch an action asynchronously (
batchfor several). tell- for maximum flexibility the
MonadWriterinstance schedules rawSchedules.
update :: Action -> Effect ctx props Model Action
update = \case
FetchUser uid -> io (GotUser <$> lookupUser uid) -- async, result becomes an action
Log msg -> io_ (consoleLog msg) -- async, fire and forget
Tick -> withSink $ \sink -> forkTimer (sink Tock)Synchronous IO
sync forces the scheduler to evaluate IO synchronously (sync_ discards the result). It is recommended to use io by default — sync will block the scheduler. Reserve it for cheap reads such as localStorage or measuring a DOMRef.
The Sink
type Sink action = action -> IO ()A Sink writes any action to the global event queue. Subscriptions receive one; withSink hands you the current component's.
Managing model state
Any MonadState function may be used to manipulate the model — get, put, modify — plus the lens operators (.=, %=, += …) from Miso.Lens. See State & lenses.
Try it
no rolls yet
data DiceModel = DiceModel { _rolls :: [Int], _busy :: Bool }
deriving (Show, Eq)
data DiceAction = Roll | Rolled Int | Clear
dice :: Component ctx () DiceModel DiceAction
dice = component (DiceModel [] False) update view
where
update = \case
-- `io` schedules IO; its result comes back as another action.
Roll -> do
modify (\m -> m { _busy = True })
io $ do
threadDelay 300000 -- pretend this is a network call
r <- mathRandom
pure (Rolled (1 + floor (r * 6)))
Rolled n -> modify (\m -> m { _rolls = take 12 (n : _rolls m), _busy = False })
-- `io_` schedules IO whose result is discarded.
Clear -> do
modify (\m -> m { _rolls = [] })
io_ (consoleLog "cleared")
view _ () m =
H.div_ []
[ H.button_ [ HE.onClick Roll, boolProp "disabled" (_busy m) ]
[ text (if _busy m then "rolling…" else "Roll a die (async)") ]
, H.button_ [ HE.onClick Clear ] [ "clear" ]
, H.p_ [] [ text (if null (_rolls m) then "no rolls yet" else ms (unwords (map show (_rolls m)))) ]
]