The variable scoping rules are important. The scopes cover not just variables, but also error handling, parallel processing etc.
Values can be declared outside of a message with $val
, in either specification or a story. If declared inside a specification, it will exist in all flows using that specification. Note that these objects are not immutable, they can be changed, so they really are variables.
$val name="aValue"
Inside a rule, variables and assignments look like a message without a name. Note that someValue
is local and will not be seen outside this rule's scope, see scopes below:
$mock rule.a
=> (someValue=123)
Changes to complex objects look the same - this assumes an object student
already exists in context:
=> (student.name = "Joe")
Just like Javascript, if you have complex attribute names, you can use squarey brackets - this is very useful when the actual attribute name comes from another expression or variable
or when itself contains dots and other special characters:
=> (student["some.weird.attribute.name"] = 12)
=> (student[variable] = 12)
Some special prefixes manage the scope of variables (see scopes below):
dieselScope
, ctx
- will place the variable in the closest enclosing scope (ctx
is deprecated, use dieselScope
instead)dieselRoot
- place the variable in the root context for this flowdieselRealm
- place the variable in the static context shared by all flowsAny flow has a root
and many levels of scopes
. A scope may be created automatically by certain constructs or explicitely with the diesel.scope.push
and diesel.scope.pop
.
Also, each rule has is its own little scope for local variables, referenced as the rule scope:
$when sample.rule1
=> (v1 = 1) // not available in rule 2, but is available in other rule1's
=> (dieselScope.v2 = 2) // is available in rule 2
$when sample.rule2
=> (v1 = v1 + 1) // v1 is Undefined
=> (v2 = v2 + 1) // v2 should be 3
$when sample.rule1 // another overloaded rule1
=> (v1 = v1 + 1) // v1 is Undefined
$send sample.rule1
$send sample.rule2
$expect (v1 is 2, v2 is 3)
Note that you can also access the values in scopes with the accessor notation - especially useful when the name is available in a variable:
=> (a = dieselScope["v2"])
=> (variable = "v2")
=> (a = dieselScope[variable])
The scope hierarchy follows these rules:
student
in root and then re-assigning it inside another rule, the second will overwrite the top).The realm is a static context shared by all flows running in your project. This is a good place for such things like tokens etc. Note that this context is small and slow.
The root is the root context for each "engine" or "flow" and contains globals.
You can set global values with a special scope: dieselRoot.x = expr1
.
Note: be careful with using this scope: if you code lists, loops, asynchronous streams or parallel execution, explicitely putting values in this shared context can create a lot of hassles, just like static variables do in structure programming languages. This is why we use the scope context instead.
A scope contains all variables for that context. You can assign values in scope with dieselScope.x = expr2
so x
then becomes a new variable in the closest enclosing scope. You should not set variables outside the closest enclosing scope, as this would defeat the purpose of automatic scopes for instance in parallel processing.
When you read a value x
, the scopes are searched from the current scope and up and the first occurence of x
is returned, if found.
Remember that scoped values do not cross outside the scope, by default.
Scopes are created automatically following these rules:
root
contextIn all normal flows, you should use only local variables and at most scope variables. You should try to avoid the root
and realm
contexts as much as possible.
These are the ways to propagate values into the scope context:
$when test.rule1 => (v1 = 1). // v1 is local only to other test.rule1 derivations, not seen outside these
$when test.rule2 => (dieselScope.v2 = 2) // dieselScope prefix will put v2 in the scope, so it's shared within the scope
$msg test.queryStudent (id:String) : (student:Student, alternatives:Student*) // declaring value outputs will propagate them to scope
$when test.queryStudent(id="1")
=> (student = x)
=> (alternatives = [])
The student and alternatives above are not local - since they were declared as the output of that rule, they are propagated automaticaly to the closest enclosing scope.
$when test.rule3
=> (payload = 3) // payload is always propagated to the closest scope
Each rule also has it's own scope for local variables. The only things that escape the rule scope are payload
, explicitely declared return values from messages and when using the special prefixes like `dieselScope'.
When setting values, the scopes are as follows.
=> (a=b)
will set the value in the context of the closest parent
=> (dieselScope.a = b)
will set the value in the closes enclosing scope (above the parent, likely)
.todo this is not available yet: => realmVars.a = b)
will set the value at the realm level
By default, the variables are local to the rule inside which they are created. For instance the variable student
below is not visible to the second rule:
$mock test.getStudent
=> (student = 'a student')
$mock test.printStudent
=> ctx.echo(student)
$mock test.students
=> test.getStudent
=> test.printStudent
To have this simple example work, you should use dieselScope.student = 'a student'
OR use payload
instead of student.
The only variables that can cross outside a rule are:
payload
Otherwise, you can use the dieselScope
prefix.
This example would work, because student
is declared as the output of the rule, so it is propagated to the closest scope, outside the parent rule:
$msg test.getStudent : (student)
$mock test.getStudent
=> (student = 'a student')
$mock test.printStudent
=> ctx.echo(student)
$mock test.students
=> test.getStudent
=> test.printStudent
{{alert red}} Note that you cannot change the value of an incoming argument: {{/alert}}
$when this.is.static(aValue = 3)
=> (aValue = 3)
=> ctx.log(aValue)
Be careful when declaring and naming output variables.
Very bad idea:
$msg my.smart.message() : (output, result)
This message will put generic names in the static scope and these can easily overwrite others later and cause havoc. Same can be done by this:
=> (dieselScope.result = "something")
The idea of output domain values is to simply using domain elements in a scope. For instance, if your flow works on an Account
, it is easy to declare the account
and accountId
as outputs of some query messages and then they'll be available anywhere in scope, wihtout having to declare the in each and every message like weak structured langauges (Java).
If there is a chance that a domain variable can cause clashes within the same flow, don't put it in scope or declare it as output of messages.
So here are some concrete examples:
BAD:
$msg warehouse.check (productId, quantity) => (result)
$msg warehouse.reserve (productId, quantity, cartId) => (reservationId)
$msg warehouse.provision (reservationId) => (result)
DECENT:
$msg warehouse.check (productId, quantity) => (warehouseCeckResult)
$msg warehouse.reserve (productId, quantity, cartId) => (reservationId)
$msg warehouse.provision (reservationId) => (provisioningStatus)
Or, better yet, if the check and provision have only local application, don't even give names to their output, use payload
which is expected to be overloaded.
Here are the design notes on contexts, for contributors.
This is a virtual context, backstop of all engine contexts. Contains statics shared across all engines in that realm. It is slow.
It is the root context for each engine, contains statics, pre-defined variables etc.
A scope context. It stops parameter propagation up.
A local context, does not propagate values up, similar to the ScopeCtx but different use.
This is a static context - it does not allow updates, used for evaluating expressions where setting values are not permitted.
Same as the local, but special use for rule scope (each $when
has its own small local variable scope, so they behave intuitively)
You need to log in to post a comment!