Dynamic Linking

The main idea of dynamic linking is to have higher-order state machines, i.e. state machines that take one or more state machines as arguments, and to use the passed state machines in links, i.e. dynamically link to them.

Consider a simple example, where we want to say hello to someone. Instead of hardcoding the name, we want a state machine to provide the name. This way we could have different strategies for getting the name like returning a hardcoded value, or retrieving the name from a service.

Defining a state interface

First we need to define the type of the state machine that provides the name. The type of a state machine is also called state interface. It could look like this:

{
  port done
  resultType string
}

We need a port to know when the state machine is finished. The result is simply a string and will contain the name of the person we want to say hello to.

Creating a state reference

As a next step we need some state machines that fulfill our state interface above. To keep it simple we return some hardcoded values, e.g.:

get_name_jane.lf
get_name_jane {
  port done result ~= nil
  resultType string
  entry @{
    setResult("Jane Doe")
  }@
}

and

get_name_bob.lf
get_name_bob {
  port done result ~= nil
  resultType string
  entry @{
    setResult("Bob Smith")
  }@
}

Using say_hello_to, i.e. linking to it, could be done like this:

say_hello_to_everyone.lf
say_hello_to_everyone {
  port done child("say_hello_to_bob").port("done")

  --> say_hello_to_jane <- say_hello_to {
    port done -> say_hello_to_bob
  } where {
    get_name_state: {
      state: "get_name_jane";
      parameter: {};
    };
  }

  say_hello_to_bob <- say_hello_to {
  } where {
    get_name_state: {
      state: "get_name_bob";
      parameter: {};
    };
  }
}

Executing say_hello_to_everyone with ride log running in parallel prints

$ ride log
[ INFO] 10:48:49.755 UTC: Hello Jane Doe!
[ INFO] 10:48:49.759 UTC: Hello Bob Smith!

Using an anonymous state

Since creating root states for every state reference can be quite cumbersome, Lingua Franka also supports anonymous states. We can rewrite say_hello_to_everyone, without separate root states:

say_hello_to_everyone.lf
say_hello_to_everyone {
  port done child("say_hello_to_bob").port("done")

  --> say_hello_to_jane <- say_hello_to {
    port done -> say_hello_to_bob
  } where {
    get_name_state: {
      port done result ~= nil
      resultType string
      entry @{
        setResult("Jane Doe")
      }@
    };
  }

  say_hello_to_bob <- say_hello_to {
  } where {
    get_name_state: {
      port done result ~= nil
      resultType string
      entry @{
        setResult("Bob Smith")
      }@
    };
  }
}

Setting state references via context menu

It is also possible to set state references via the context menu. We modify the state machine say_hello_to a bit to become an app and add a context menu:

say_hello_to_app.lf
say_hello_to_app {
  port Success child("say_hello").port("done")
  port Error false

  clientData {
    type: "app";
    name: "Say Hello";
    contextMenu: @{
      <step id="name" name="Say hello to" class="flex-column">
        <drop-down-menu params="
          parameter: parameter('get_name_state'),
          default: {state: 'get_name_jane', parameter: {}},
          items: [ {value: {state: 'get_name_jane', parameter: {}}, text: 'Jane Doe'},
                   {value: {state: 'get_name_bob', parameter: {}}, text: 'Bob Smith'},
                 ],
          step: step
        "></drop-down-menu>
      </step>
    }@;
  }

  parameterType {
    {
      port done
      resultType string
    } get_name_state;
  }

  resultType {
    string error_cause;
  }

  --> get_name <- parameter.get_name_state {
    port done -> say_hello
  }

  say_hello {
    port done true

    parameterType string

    entry @{
      printf("Hello %1!", parameter)
    }@
  } where child("get_name").result
} where {
  get_name_state: nil;
}

Our Say Hello app now has a simple drop down menu, where each element corresponds to one of our get_name_* state machines defined above.

Setting state references via Lua scripts

It is also possible to set state references via Lua scripts. We modify the context menu of say_hello_to_app, so that it provides only a name. This name is then used in the entry script to create a corresponding state machine reference:

say_hello_to_app_2.lf
say_hello_to_app_2 {
  port Success child("say_hello").port("done")
  port Error false

  clientData {
    type: "app";
    name: "Say Hello 2";
    contextMenu: @{
      <step id="name" name="Say hello to" class="flex-column">
        <drop-down-menu params="
          parameter: parameter('name'),
          default: 'jane',
          items: [ {value: 'jane', text: 'Jane Doe'},
                   {value: 'bob', text: 'Bob Smith'},
                 ],
          step: step
        "></drop-down-menu>
      </step>
    }@;
  }

  parameterType {
    string name;
  }

  variableType {
    {
      port done
      resultType string
    } get_name_state;
  }

  resultType {
    string error_cause;
  }

  entry @{
    setVariable({
      get_name_state = {
        state = "get_name_" .. parameter.name,
        parameter = {}
      }
    })
  }@

  --> get_name <- variable.get_name_state {
    port done -> say_hello
  }

  say_hello {
    port done true

    parameterType string

    entry @{
      printf("Hello %1!", parameter)
    }@
  } where child("get_name").result
} where {
  get_name_state: nil;
}