Procedures to be processed in a thread.
batch : List (Procedure memory event) -> Procedure memory event
Batch Procedure
s together. The elements are evaluated in order.
An identifier for a thread.
stringifyThreadId : ThreadId -> String
Convert ThreadId
into String
.
Different ThreadId
s will be converted to different strings, and the same ThreadId
s will always be converted to the same string.
ThreadId -> List (Procedure memory event)
An alias for a bunch of Procedure
s.
none : Procedure memory event
Construct a Procedure
instance that do nothing.
modify : (memory -> memory) -> Procedure memory event
Construct a Procedure
instance that modifies shared memory state.
Note that the update operation, passed as the first argument, is performed atomically. It means the state of shared memory read by a particular thread with modify
is not updated by another thread until it is updated by the thread.
push : (memory -> Platform.Cmd.Cmd event) -> Procedure memory event
Construct a Procedure
instance that issues a Cmd
directed to the thread on which this function is evaluated.
await : (event -> memory -> List (Procedure memory event)) -> Procedure memory event
Construct a Procedure
instance that awaits the local events for the thread.
If it returns empty list, it awaits again.
Otherwise, it evaluates the given Procedure
.
Note1: The shared memory state passed to the first argument function may become outdated during running the thread for the Procedure
generated by that function, so it is safe to use this shared memory state only to determine whether to accept or miss events.
Note2: Technically, all the modify
push
async
written before the await
will be executed internally as a single operation. This avoids the situation where a local event triggered by a push
occurs while processing tons of subsequent modify
s and push
s, thus ensuring that the await
always can catch the local event caused by the previous Procedure
s.
Note3: push
s written before an await
will not necessarily cause local events in the order written. For example, if the first push
sends a request to the server and it fires a local event with its result, and the second push
sleeps for 0.1 seconds and then returns a local event, the first local event can fire later if the server is slow to respond. To avoid this situation, after using one push
, catch it with await
and use the next push
, or use sync
.
async : Block memory event -> Procedure memory event
Construct a Procedure
instance that evaluates the given Block
in the asynchronous thread.
The asynchronous thread is provided new ThreadId
and runs independently of the original thread; therefore the subsequent Procedure
s in the original thread are evaluated immediately, and the asynchronous thread is cancelled when the original thread ends.
Infinite recursion by giving itself as the argument to async
is not recommended to prevent threads from overgrowing. Use jump
if you want to create threads that never end.
block : Block memory event -> Procedure memory event
Construct a Procedure
instance that wait for the given Procedure
to be completed.
Given Procedure
is evaluated in the independent threads with new ThreadId
, but the subsequent Procedure
s in the original thread are not evaluated immediately. For example, the following sleep function uses block
to scope the WakeUp
event so that it only affects the inside of the sleep
function.
import Process
import Task
import Thread.Procedure as Procedure exposing (Procedure)
sleep : Float -> Procedure Memory Event
sleep msec =
Procedure.block <|
\_ ->
[ Procedure.push <|
\_ ->
Process.sleep msec
|> Task.perform (\() -> WakeUp)
, Procedure.await <|
\event _ ->
case event of
WakeUp ->
[ Procedure.none
]
_ ->
[]
]
Infinite recursion by giving itself as the argument to async
is not recommended to prevent threads from overgrowing. Use jump
if you want to create threads that never end.
sync : List (Block memory event) -> Procedure memory event
Construct a Procedure
instance that wait for all the given Block
s to be completed.
Each Block
is evaluated in the independent threads with its own ThreadId
, but the subsequent Procedure
s in the original thread are not evaluated immediately, but wait for all the given Block
s to be completed.
race : List (Block memory event) -> Procedure memory event
Construct a Procedure
instance that wait for one of the given Block
s to be completed.
Each Block
is evaluated in the independent thread with its own ThreadId
, but the subsequent Procedure
s in the original thread are not evaluated immediately, but wait for one of the given Block
s to be completed.
Note1: If one of the threads exits, all other threads will be suspended after processing until the next await
.
quit : Procedure memory event
Quit the thread immediately.
Subsequent Procedure
s are not evaluated and are discarded.
jump : Block memory event -> Procedure memory event
Ignore subsequent Procedure
s, and evaluate given Block
in the current thread. It is convenient for following two situations.
Calling itself in the Block
will result in a compile error; The jump
avoids it to makes the recursive Block
.
import Thread.Procedure as Procedure exposing (Block)
import Time exposing (Posix)
clockProcedures : Block Memory Event
clockProcedures tid =
[ Procedure.await <|
\event _ ->
case event of
ReceiveTick time ->
[ Procedure.modify <|
\memory ->
{ memory | time = time }
]
_ ->
[]
, Procedure.jump clockProcedures
]
You can use block
or async
for a similar purpose, but whereas they create new threads for the given Block
; it causes threads overgrowing.
Sometimes you may want to handle errors as follows:
unsafePruning : Block Memory Event
unsafePruning tid =
[ requestPosts
, Procedure.await <|
\event _ ->
case event of
ReceivePosts (Err error) ->
[ handleError error tid
|> Procedure.batch
]
ReceivePosts (Ok posts) ->
[ Procedure.modify <|
\memory ->
{ memory | posts = posts }
]
_ ->
[]
, Procedure.block blockForNewPosts
]
It appears to be nice, but it does not work as intended. Actually, the above Block
can evaluate the blockForNewPosts
even after evaluating handleError
. To avoid this, you can use jump
:
safePruning : Block Memory Event
safePruning tid =
[ requestPosts
, Procedure.await <|
\event _ ->
case event of
ReceivePosts (Err error) ->
[ Procedure.jump <| handleError error
]
ReceivePosts (Ok posts) ->
[ Procedure.modify <|
\memory ->
{ memory | posts = posts }
]
_ ->
[]
, Procedure.block blockForNewPosts
]
doUntil : Block memory event -> (event -> memory -> List (Procedure memory event)) -> Procedure memory event
Evaluate another Block
, provided as a first argument, with new ThreadId
until the second argument returns non-empty list.
For example, you could use it to define a function that executes the Block
for appropreate SPA page until the URL changes:
import Thread.Procedure as Procedure exposing (Block)
import Url exposing (Url)
pageController : Route -> Block Memory Event
pageController route tid =
[ Procedure.doUntil
-- The thread for the `pageProcedures` will be killed
-- when the URL canges.
(pageProcedures route)
<|
\event _ ->
case event of
UrlChanged url ->
[ Procedure.jump <| pageController (routeFromUrl url)
]
_ ->
[]
]
addFinalizer : Block memory event -> Procedure memory event
For a thread running this Procedure
, add a finalizer: Procedure
s to be evaluated when the thread is terminated, such as when the last Procedure
for the thread has finished to be evaluated, or when the thread is interrupted by quit
or race
, or the parent thread ends by such reasons.
Since addFinally
appends the finalizer, it is especially important to note that if you use addFinally
in a thread that self-recurses with turn
, the finalizer will be executed as many times as it self-recurses.
modifyAndThen : (memory -> ( memory, x )) -> (x -> Block memory event) -> Procedure memory event
Modify the shared memory atomically, creating the intermediate value in the process, and pass the value to the another Block
in the original thread.
The intermediate value is supposed to be the information of the certain resource at a particular time.
when : (memory -> Basics.Bool) -> List (Procedure memory event) -> Procedure memory event
Evaluate given Procedure
s only if the first argument returns True
with current memory state, otherwise returns none
.
unless : (memory -> Basics.Bool) -> List (Procedure memory event) -> Procedure memory event
Evaluate given Procedure
s only if the first argument is False
with current memory state, otherwise returns none
.
withMemory : (memory -> Block memory event) -> Procedure memory event
Select a Block
to run by the current memory state.
Do not use the provided memory state in the Block
in order to avoid using outdated memory state.
These items are needed when you try to build a hierarchy of memory and events in an SPA.
These items are used to build memory and event hierarchies, for example in SPAs. Note that the pattern often unnecessarily increases complexity, so you should first consider using monolithic shared memory and events.
For a sample, see sample/src/Advanced.elm
and sample/src/SPA.elm
.
lift : Thread.Lifter.Lifter a b -> Procedure b event -> Procedure a event
Lift the memory type of of the given Procedure
.
Note that this function does not set up a dedicated memory for b
, but simply makes it operate on the part of memory a
; so the memory b
is shared with other threads.
If you want to create a thread that allocates a dedicated memory area of type b
for a given procedure, use functions in the Thread.LocalMemory
module.
wrap : Thread.Wrapper.Wrapper a b -> Procedure memory b -> Procedure memory a
Wrap the event type of the given Procedure
.
liftBlock : Thread.Lifter.Lifter a b -> Block b event -> ThreadId -> List (Procedure a event)
Block
version of liftMemory
.
wrapBlock : Thread.Wrapper.Wrapper a b -> Block memory b -> ThreadId -> List (Procedure memory a)
Wrap the event type of the given Block
.
It is recommended to use Thread.Browser
for normal use.
init : memory -> Block memory event -> ( Model memory event, Platform.Cmd.Cmd (Msg event) )
initThreadId : ThreadId
ThreadId
for the initially loaded procedure.
update : Msg event -> Model memory event -> ( Model memory event, Platform.Cmd.Cmd (Msg event) )
extractMemory : Model memory event -> memory
mapMsg : (a -> b) -> Msg a -> Msg b
setTarget : ThreadId -> event -> Msg event
Set the target thread for an event by its ThreadId
.