Using Persistent Variables

One of the features that sets KRL apart from most programming languages is its use of persistent variables. Persistent variables ensure that a rule can easily establish an execution context without resorting to a database. 

Persistent Variable Basics

Persistent variables come in two forms: entity and application variables. Entity variables store values for a specific entity whereas application variables are not entity specific. 

Application variables are most useful for storing information about the ruleset or application itself. For example, in the Fuse connected-car platform, an application variable is used to create a record of all the driver when their accounts are created for administrative purposes. 

Entity variables are much more common. Entity variables allow KRL rulesets to be multi-tenanted without programmer intervention. 

You can retrieve and use the value of a persistent variable by merely including it in an expression. You can generally treat a persistent variable just like you do a normal variable in these cases. For example:

ent:var_a + 1            // add one to the value stored in variable
ent:var_b{"x"}           // retrieve value stored in variable with key "x"
ent:var_c.put(["y"], 10) // make a new map from var_c with 10 stared at key "y"
ent:var_d[4]             // retrieve value in array at index 4

Persistent variables are updated in the postlude of a rule. There are two mutating operations: clear and assignment:

  • clear <pvar>. This deletes the persistent variable <pvar>
  • <pvar> := <expr>. This sets the value of <pvar> to the value of the expression.
Persistent variables are scoped within a ruleset. One ruleset cannot see another ruleset's persistent variables. That might seem like a significant limitation, but it's easily overcome using modules. 

Persistent Variables in Modules

KRL Modules are one of the most important and powerful techniques for organizing KRL code. Any ruleset can be a module. 

Entity variables in KRL are statically scoped within a module. This has powerful implications: any persistent variable modified in a rule can be seen by any function in the global section of the same ruleset, even when that function is used in another ruleset as part of a module

Wow!

The PDM ruleset shown here is automatically multi-tenanted because of persistent variables. We didn't do anything to identify the user, retrieve that user's row from a database, or any of the other activities that a developer would normally have to undertake in order to manage data for multiple users. This ruleset can be installed in any number of picos and will store the data uniquely for each.

To see how this works and why it's important, consider a module defined to act as a persistent data module (PDM). The PDM ruleset has a rule that is listening for the pds:new_data event.

rule add_data_item {
  select when pds new_data
  pre {
    k = event:attr("key");
    val = event:attr("value")
  }
  always {
    ent:data := ent:data.put(k, val);
    raise pds event "data_added"
      attributes {"key":k};
  }
}

This rule does two things when it sees a salient event (note this rule has no action, just a postlude):

  1. Stores the value of the event attribute named value in an entity variable named ent:data. The ent:data variable is a map and the key is taken from the event attribute named key.
  2. Raises a pds:data_added event.

The first is the primary result we want from this rule: the data gets stored in an entity variable. Because this is an entity variable it will be persistently stored for the entity that owns the pico where this rule is running. 

The second is interesting because it ensures that this rule is not a dead-end leaf on the event tree. Other rules, not written or even contemplated yet, can be written that look for the pds:data_added event. See Tweeting from KBlog: An Experiment in Loose Coupling for a discussion of why this is important. 

In addition to having a rule that responds to events and stores data, the PDM ruleset is also a KRL module:

meta {
 name "Persistent Data Manager"
 provides get_item
}
 
global {
 get_item = function(k) {
   ent:data{k};
 };
}

The module provides a single function: get_item() that simply accesses and returns the value stored with a given key, k, in the entity variable called ent:data.

Because modules form a closure over persistent variables used in the ruleset, when a rule stores something in a persistent variable in a module and a provided function retrieves it, they're talking about the same piece of data. This is the magic that allows a ruleset to act as a personal data manager. Even if get_item() is called from another ruleset that happens to have an entity variable with the exact same name, the values will be retrieved from the entity variable in our PDM ruleset. 






Copyright Picolabs | Licensed under Creative Commons.