| Copyright | (C) 2016-2026 David M. Johnson |
|---|---|
| License | BSD3-style (see the file LICENSE) |
| Maintainer | David M. Johnson <code@dmj.io> |
| Stability | experimental |
| Portability | non-portable |
| Safe Haskell | Safe-Inferred |
| Language | Haskell2010 |
Miso.Effect
Description
Overview
Miso.Effect defines the three core abstractions used in the Model-View-Update loop:
Effect— the monad returned by everyupdatehandler. Combines a state update onmodelwith a list ofIOactions to schedule.Sub— a long-running subscription () that feeds actions into the event queue from threads, timers, WebSockets, etc.Sinkaction -> IO ()Sink— a function (action -> IO ()) that enqueues a single action for processing byupdate.
The Effect monad
typeEffectcontext props model action = RWS (ComponentInfocontext props) [Schedulecontext action] model ()
The RWS decomposition:
- Reader —
ComponentInfo: component metadata (componentInfoId,componentInfoDOMRef,componentInfoProps) accessible viaaskor the convenience lenses. - Writer — accumulated list of
ScheduledIOactions to run after the model update. - State — the
model, updated viaput,modify, or the lens operators from Miso.Lens.
Scheduling IO
By default all IO runs asynchronously in a separate thread after the
VDOM has been patched. Use sync / sync_ to block the render thread:
update = \case Fetch ->io(fetchData >>= pure . GotData) -- async LogIt ->io_(consoleLog "hi") -- async, no action Urgent ->sync(pure SomeSyncAction) -- blocks render Many ->batch[a1, a2, a3] -- multiple async Opt ->for(fetchMaybe >>= pure) -- Maybe/Foldable
Subscriptions
A Sub is a function that receives a Sink and runs forever (typically
on a forked thread). Register subscriptions in subs:
tickSub ::SubAction tickSub sink = forever $ do threadDelay 16667 sink Tick myComponent = (componentmodel update view) {subs= [tickSub] }
Use mapSub to adapt a Sub a into a Sub b with a mapping function.
Component metadata
Within update, access the current component's runtime info through
ask or the provided lenses:
update = \case
Init -> do
domRef <- view componentInfoDOMRef
compId <- view componentInfoId
myProps <- getProps
io_ (initThirdParty domRef)
See also
- Miso.Types —
Component,update,subs - Miso.Lens — lens operators (
,.=,+=) for model updates%= - Miso.Subscription — pre-built subscriptions (mouse, keyboard, history, …)
Synopsis
- type Effect context props model action = RWS (ComponentInfo context props) [Schedule context action] model ()
- type Sub action = Sink action -> IO ()
- type Sink action = action -> IO ()
- type DOMRef = JSVal
- data ComponentInfo context props = ComponentInfo {
- _componentInfoId :: ComponentId
- _componentInfoParentId :: ComponentId
- _componentInfoDOMRef :: DOMRef
- _componentInfoProps :: props
- _componentInfoContext :: context
- type ComponentId = Int
- mkComponentInfo :: ComponentId -> ComponentId -> DOMRef -> props -> context -> ComponentInfo context props
- data Schedule context action
- = Schedule Synchronicity (Sink action -> IO ())
- | ContextModify (context -> context)
- data Synchronicity
- (<#) :: model -> IO action -> Effect context props model action
- (#>) :: IO action -> model -> Effect context props model action
- batch :: [IO action] -> Effect context props model action
- batch_ :: [IO ()] -> Effect context props model action
- io :: IO action -> Effect context props model action
- io_ :: IO () -> Effect context props model action
- sync :: IO action -> Effect context props model action
- sync_ :: IO () -> Effect context props model action
- for :: Foldable f => IO (f action) -> Effect context props model action
- issue :: action -> Effect context props model action
- withSink :: (Sink action -> IO ()) -> Effect context props model action
- modifyContext :: (context -> context) -> Effect context props model action
- modifyContext_ :: State context () -> Effect context props model action
- putContext :: context -> Effect context props model action
- mapSub :: (a -> b) -> Sub a -> Sub b
- noop :: action -> Effect context props model action
- beforeAll :: IO () -> Effect context props model action -> Effect context props model action
- afterAll :: IO () -> Effect context props model action -> Effect context props model action
- modifyAllIO :: (IO () -> IO ()) -> Effect context props model action -> Effect context props model action
- componentInfoDOMRef :: Lens (ComponentInfo context props) DOMRef
- componentInfoParentId :: Lens (ComponentInfo context props) ComponentId
- componentInfoId :: Lens (ComponentInfo context props) ComponentId
- runEffect :: Effect context props model action -> ComponentInfo context props -> model -> (model, [Schedule context action])
- componentInfoProps :: Lens (ComponentInfo context props) props
- props :: Lens (ComponentInfo context props) props
- getProps :: MonadReader (ComponentInfo context props) m => m props
- componentInfoContext :: Lens (ComponentInfo context props) context
- context :: Lens (ComponentInfo context props) context
- getContext :: MonadReader (ComponentInfo context props) m => m context
Effect
Types
type Effect context props model action = RWS (ComponentInfo context props) [Schedule context action] model () Source #
A monad for succinctly expressing model transitions in the update function.
Effect is a RWS, where the State allows modification to model.
It's also a Writer Monad, where the accumulator is a list of scheduled
IO actions. Multiple actions can be scheduled using tell
from the mtl library and a single asynchronous action can be scheduled using io_.
An Effect represents the results of an update action.
It consists of the updated model and a list of subscriptions. Each Sub is
run in a new thread so there is no risk of accidentally blocking the
application.
Tip: use the Effect monad in combination with the stateful Miso.Lens
operators (all operators ending in "="). The following example assumes
the lenses field1, counter and field2 are in scope and that the
LambdaCase language extension is enabled:
myComponent = Component
{ update = \case
MyAction1 -> do
field1 .= value1
counter += 1
MyAction2 -> do
field2 %= f
io_ $ do
consoleLog "Hello"
consoleLog "World!"
, ...
}
type Sub action = Sink action -> IO () Source #
Type synonym for constructing subscriptions.
For example usage see Miso.Subscription
The Sink function is used to write to the global event queue.
type Sink action = action -> IO () Source #
Function to write to the global event queue for processing by the scheduler.
data ComponentInfo context props Source #
This is the 'Reader r' in Effect. Accessible via ask. It holds
a phantom type for context (the app-global React-style context, which is
write-only from within update). It gives access to Component metadata such
as the DOMRef the Component was mounted on and the ComponentId associated
with it.
Constructors
| ComponentInfo | |
Fields
| |
type ComponentId = Int Source #
ComponentId of the current Component
Arguments
| :: ComponentId | |
| -> ComponentId |
|
| -> DOMRef | |
| -> props | props |
| -> context | context |
| -> ComponentInfo context props |
Smart constructor for ComponentInfo
IO
data Schedule context action Source #
Represents a scheduled Effect that is executed either synchronously
or asynchronously.
All IO is by default asynchronous, use the sync function for synchronous
execution. Beware sync can block the render thread for a specific
Component.
N.B. During Component unmounting, all effects are evaluated
synchronously.
The ContextModify constructor carries a pending mutation to the app-global
React-style context. It is emitted by modifyContext / putContext and
applied to the global context during the scheduler's commit phase, triggering
a re-render of every Component with useContext enabled.
Since: 1.9.0.0
Constructors
| Schedule Synchronicity (Sink action -> IO ()) | |
| ContextModify (context -> context) |
data Synchronicity Source #
Type to indicate if effects should be handled asynchronously or synchronously.
Instances
| Show Synchronicity Source # | |
Defined in Miso.Effect Methods showsPrec :: Int -> Synchronicity -> ShowS # show :: Synchronicity -> String # showList :: [Synchronicity] -> ShowS # | |
| Eq Synchronicity Source # | |
Defined in Miso.Effect Methods (==) :: Synchronicity -> Synchronicity -> Bool # (/=) :: Synchronicity -> Synchronicity -> Bool # | |
Combinators
(<#) :: model -> IO action -> Effect context props model action infixl 0 Source #
Smart constructor for an Effect with exactly one action.
(#>) :: IO action -> model -> Effect context props model action infixr 0 Source #
Effect smart constructor, flipped
batch_ :: [IO ()] -> Effect context props model action Source #
Like batch but actions are discarded
Since: 1.9.0.0
Like io but doesn't cause an action to be dispatched to
the update function.
This is handy for scheduling IO computations where you don't care
about their results or when they complete.
Note: The result of IO a is discarded.
Since: 1.9.0.0
Like sync, except discards the result.
Since: 1.9.0.0
Arguments
| :: action |
|
| -> Effect context props model action |
Arguments
| :: (Sink action -> IO ()) | Callback function that provides access to the underlying |
| -> Effect context props model action |
withSink allows users to write to the global event queue. This is useful for introducing IO into the system.
A synonym for tell, specialized to Effect.
A use-case is scheduling an IO computation which creates a 3rd-party JS
widget which has an associated callback. The callback can then call the sink
to turn events into actions.
updateFetchJSON =withSink$ \sink -> getJSON (sink . ReceivedJSON) (sink . HandleError)
Since: 1.9.0.0
Arguments
| :: (context -> context) | Transformation to apply to the global |
| -> Effect context props model action |
Mutate the app-global React-style context from within update.
The supplied function is scheduled as a ContextModify and folded over the
current global context during the scheduler's commit phase. If the context
value changes (per its Eq instance), every Component with useContext
enabled is re-rendered.
Note that context is write-only inside update; to read it, use the
context argument threaded into the view function.
updateToggle =modifyContext(\theme -> if theme == Light then Dark else Light)
Since: 1.13.0.0
Arguments
| :: State context () |
|
| -> Effect context props model action |
Mutate the app-global React-style context using a State computation.
A convenience wrapper around modifyContext that runs the supplied
action over the current global context (via State contextexecState),
scheduling the resulting context -> context transformation as a
ContextModify. This lets you use put / modify and the lens operators
from Miso.Lens to update context, mirroring how model is updated.
updateToggle =modifyContext_$ theme.=Dark
Since: 1.13.0.0
Arguments
| :: context | New global |
| -> Effect context props model action |
Replace the app-global React-style context with a new value.
A convenience wrapper around modifyContext. See modifyContext for details
of when re-renders are triggered.
Since: 1.13.0.0
Arguments
| :: IO () |
|
| -> Effect context props model action | Effect whose IO actions are modified |
| -> Effect context props model action |
Performs the given IO action before all IO actions collected by the given
effect.
-- delays connecting a websocket by 100000 microseconds beforeAll (liftIO $ threadDelay 100000) $ websocketConnectJSON OnConnect OnClose OnOpen OnError
Since: 1.9.0.0
Arguments
| :: IO () |
|
| -> Effect context props model action | Effect whose IO actions are modified |
| -> Effect context props model action |
Performs the given IO action after all IO actions collected by the given
effect.
Example usage:
-- log that running the a websocket Effect completed afterAll (consoleLog "Done running websocket effect") $ websocketConnectJSON OnConnect OnClose OnOpen OnError
Lens
componentInfoDOMRef :: Lens (ComponentInfo context props) DOMRef Source #
componentInfoParentId :: Lens (ComponentInfo context props) ComponentId Source #
Lens for accessing the parents's ComponentId from ComponentInfo.
update = case
SomeAction -> do
compParentId <- view componentParentId
someAction compParentId
Since: 1.9.0.0
componentInfoId :: Lens (ComponentInfo context props) ComponentId Source #
Lens for accessing the ComponentId from ComponentInfo.
update = case
SomeAction -> do
compId <- view componentInfoId
someAction compId
Since: 1.9.0.0
Internal
runEffect :: Effect context props model action -> ComponentInfo context props -> model -> (model, [Schedule context action]) Source #
Internal function used to unwrap an Effect
Props
componentInfoProps :: Lens (ComponentInfo context props) props Source #
Lens for accessing the underlying Component props.
update = case
SomeAction -> do
props <- view componentInfoProps
someAction props
Since: 1.9.0.0
props :: Lens (ComponentInfo context props) props Source #
Lens for accessing the underlying Component props.
This is a shorter convenience lens that is a synonynm for componentInfoProps.
See getProps for usage in the Effect monad.
update = case
SomeAction ->
someAction =<< view props
getProps :: MonadReader (ComponentInfo context props) m => m props Source #
props retrieval from within the Effect monad.
update = case
SomeAction -> do
props <- getProps
someAction props
Context
componentInfoContext :: Lens (ComponentInfo context props) context Source #
Lens for accessing the underlying Component context.
update = case
SomeAction -> do
ctx <- view componentInfoContext
someAction ctx
Since: 1.13.0.0
context :: Lens (ComponentInfo context props) context Source #
Lens for accessing the underlying Component context.
This is a shorter convenience lens that is a synonynm for componentInfoContext.
See getContext for usage in the Effect monad.
update = case
SomeAction ->
someAction =<< view context
Note: this lens is read-only within Effect. It targets the
ComponentInfo reader environment, so setting through it (e.g. with
set / .=) has no observable effect. To change the
global context from update, use modifyContext,
putContext, or modifyContext_ (the State-monad
variant, which supports lens operators like ) instead..=
Since: 1.13.0.0
getContext :: MonadReader (ComponentInfo context props) m => m context Source #
Read-only context retrieval from within the Effect monad.
update = case
SomeAction -> do
ctx <- getContext
someAction ctx
Since: 1.13.0.0