Overview
The continuations library (/mufl/mufl_stdlib/continuation.mm) is a part of the MUFL Standard Library, providing users with advanced functionality to control the order of execution of specific transactions across different ADAPT nodes. This library plays a crucial role when sequential execution of transactions is needed between different network participants.
To provide a more tangible understanding of the continuations library, let’s consider a scenario involving a user and a data storage system. This process involves three primary actions:
- Action 1 - The user sends a request to retrieve specific data.
- Action 2 - The data storage sends the requested data to the user.
- Action 3 - The user saves the received data locally.
You could implement the process without using the continuations library by defining a transaction for each action. For example:
library user uses transactions
{
fn save_data (_) {} // placeholder for a function saving the data
trn request_data data_storage_packet_id: global_id
{
// request data from the storage (Action 1)
return ::transaction::success [
::transaction::action::send data_storage_packet_id ($name -> "::data_storage::on_data_request", $targ -> _get_container_id())
].
}
trn on_data_received data
{
save_data data. // suppose the 'save_data' function is implemented and it saves received data locally (Action 3)
return ::transaction::success [].
}
}
application data_storage uses transactions
{
fn get_data (_) {} // placeholder for a function loading the data from the local storage
trn on_data_request user_packet_id: global_id
{
data = get_data (). // suppose the 'get_data' function is implemented and it returns data to be sent
// send data to the user (Action 2)
return ::transaction::success [
::transaction::action::send user_packet_id ($name -> "::user::on_data_received", $targ -> data)
].
}
}
While this implementation of the given scenario is valid, as the number of actions grows, so does the number of transactions, making the code less readable and harder to maintain.
A better approach is to use the continuations library to implement the process using only two transactions regardless of the number of actions. For example::
library user loads library continuation uses transactions
{
fn save_data (_) {} // placeholder for a function saving the data
trn request_data data_storage_packet_id: global_id
{
// define a continuation callback
fn on_data_received (data)
{
// save the data (Action 3)
// this code executes only when a continuation with a given key is invoked
save_data data.
}
// create a unique continuation ID
continuation_id = continuation::add_continuation on_data_received.
// send the request to the storage along with a continuation ID (Action 1)
return ::transaction::success [
::transaction::action::send data_storage_packet_id ($name -> "::data_storage::on_data_request", $targ -> ($continuation_id -> continuation_id, $user_packet_id -> _get_container_id()))
].
}
}
application data_storage loads libraries continuation uses transactions
{
fn get_data (_) {} // placeholder for a function loading the data from the local storage
trn on_data_request _:($continuation_id -> continuation_id: global_id, $user_packet_id -> user_packet_id: global_id)
{
data = get_data ().
// send the data to the user (Action 2)
return ::transaction::success [
::transaction::action::send user_packet_id (
$name -> "::continuation::continue_transaction",
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note that the data storage sends another transaction to the user
$targ -> ($id -> continuation_id, $args -> data))
].
}
}
This example provides a clear and detailed overview of how the continuations library can be used to streamline the sequencing of actions in a multi-action process.