Batch Procedures together. The elements are evaluated in order.
An identifier for a thread.
Convert ThreadId into String.
Different ThreadIds will be converted to different strings, and the same ThreadIds will always be converted to the same string.
An alias for a bunch of Procedures.
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.
Construct a Procedure instance that issues a Cmd directed to the thread on which this function is evaluated.
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 modifys and pushs, thus ensuring that the await always can catch the local event caused by the previous Procedures.
Note3: pushs 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.
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 Procedures 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.
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 Procedures 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.
Construct a Procedure instance that wait for all the given Blocks to be completed.
Each Block is evaluated in the independent threads with its own ThreadId, but the subsequent Procedures in the original thread are not evaluated immediately, but wait for all the given Blocks to be completed.
Construct a Procedure instance that wait for one of the given Blocks to be completed.
Each Block is evaluated in the independent thread with its own ThreadId, but the subsequent Procedures in the original thread are not evaluated immediately, but wait for one of the given Blocks to be completed.
Note1: If one of the threads exits, all other threads will be suspended after processing until the next await.
Quit the thread immediately.
Subsequent Procedures are not evaluated and are discarded.
Ignore subsequent Procedures, 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
]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)
]
_ ->
[]
]For a thread running this Procedure, add a finalizer: Procedures 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.
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.
Evaluate given Procedures only if the first argument returns True with current memory state, otherwise returns none.
Evaluate given Procedures only if the first argument is False with current memory state, otherwise returns none.
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 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 the event type of the given Procedure.
Block version of liftMemory.
Wrap the event type of the given Block.
It is recommended to use Thread.Browser for normal use.
ThreadId for the initially loaded procedure.
Procedures to be processed in a thread.