miso

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, asks and Miso.Lens.view read its fields (the current ComponentId, the parent id, the DOMRef the component is mounted on, props, context).
  • The Writer portion schedules IO. tell creates a Schedule that runs according to its Synchronicity; see withSink.
  • The State portion is the model: get, put, modify and the MonadState lens operators from Miso.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 (batch for several).
tell
for maximum flexibility the MonadWriter instance schedules raw Schedules.
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

io and io_: scheduling asynchronous worklive

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)))) ]
        ]