KRL misconceptions and common errors
- 1 Preliminaries: KRL as a programming notation
- 2 Common misconceptions
- 2.1 Request/Response vs. Event/Action and Query/Response
- 2.1.1 Queries to a pico
- 2.1.2 Events to a pico
- 2.2 Name/Value binding vs. Variable assignment
- 2.2.1 Name/Value binding in KRL
- 2.2.2 Entity variable assignment
- 2.2.3 Pico persistent state
- 2.3 Operators produce new values
- 2.4 Order of rule evaluation
- 2.5 Interference between rulesets
- 2.6 Rule name vs event salience
- 2.7 String constants vs computed strings
- 2.7.1 Double-quoted strings
- 2.7.2 Chevron-quoted strings
- 2.8 Thinking KRL is like Java
- 2.1 Request/Response vs. Event/Action and Query/Response
- 3 Common errors
- 3.1 Regular expression capture groups
- 3.2 Regular expression capture vs. explicit binding
- 3.3 Functions and function calling
- 3.4 Dots vs. colons vs. map references
- 3.5 Map references and strings
- 3.6 The empty string is not null
- 3.7 Regular expressions and case sensitivity
- 3.8 Operator precedence
- 3.9 Providing attributes when you want to generate an event
- 3.9.1 event:send
- 3.9.2 raise ... event ... attributes ...
- 3.9.3 A common typo
- 3.10 An empty array is truthy
- 3.11 domain ≠ rid
- 3.12 Thought an event attribute was sent
- 3.13 Initializing entity variables on ruleset installation
- 3.14 Using regular expressions with multi-line strings
- 3.15 Ruleset identifier (RID) vs. KRL filename
Preliminaries: KRL as a programming notation
KRL is the programming language for picos. It provides a notation for expressing how a pico will react to incoming events, and how it will respond to queries.
Superficially, although it is a very different language, its syntax resembles that of JavaScript. There are blocks enclosed in curly braces, arithmetic and Boolean expressions, what look like assignment statements (but are not), function definitions and invocations, an if
keyword, and so on. Notably missing are for
and while
keywords. And there are also constructs very different from what one would expect to see in a JavaScript program, such as keywords defaction
, select when
, foreach
, and so forth.
So, it would be a mistake to think of KRL as being like JavaScript, because it has a very different execution model, and a very different memory model, besides the syntax differences. Like JavaScript, it has a run-time support system, the node pico engine. To compound the comparison, KRL is not interpreted, but rather compiles to JavaScript.
The unit of compilation is a ruleset, which is a set of rules in the sense that each rule name has to be unique, but actually more like a list of rules in the sense that the order in which they are written matters. Besides rules, the ruleset also has a place in which global functions can be defined, and at run-time, the functions when invoked evaluate within the context of the ruleset and the pico in which the ruleset is installed.
Common misconceptions
Depending on the languages you are most familiar with, you may naturally assume that KRL will behave in certain ways only to be unpleasantly surprised. Such surprises are documented in this section.
Request/Response vs. Event/Action and Query/Response
The node pico engine provides an interface to the picos it hosts which uses HTTP. Other transport mechanisms could (and will in the near future) be able to direct an event or a query to a pico.
If you are familiar with programming applications for HTTP, you may be tempted to equate a pico to a resource. In the RESTful programming model, an HTTP GET request should not modify the resource, while HTTP PUT and POST requests often do. With picos, a query can never modify the pico, and won't take any action on things outside the pico. Only an event sent to the pico can modify the pico's state. You may usefully think of a pico as being a state machine, with queries telling you about the state it is in, and events causing it to (possibly) change its state.
Queries to a pico
A query is sent to a pico by issuing a GET request to the pico engine which currently hosts it. The engine routes that request in such a way as to invoke a global shared function from one of the rulesets installed in the pico. Whatever value that function computes, given the current state of that pico, is packaged up and becomes the content of the HTTP response sent back to the event generator which issued the request. An event generator might be a program, a link in a web page, a link in the Testing tab of the UI, or even a developer using curl
or the location bar of a web browser.
The URL used for a query has the following format (called the Sky Cloud API):
http://<domain>:<port>/sky/cloud/<ECI>/<RID>/<function name>?name0=value0&...&namen=valuen
The key to distinguishing a query from an event is to notice the first two components of the URL path, which will be exactly /sky/cloud/
and these are followed by the event channel identifier (which the node pico engine uses to identify which pico is to receive the query), and the ruleset identifier and function name (which the node pico engine uses to identify the ruleset (which must be installed in that pico) and the function (which must be defined and shared by that ruleset)). Finally, there are zero or more named arguments. The argument names must precisely match the argument names of the function, although order doesn't matter.
Version 1.x of the pico engine provides an alternate query syntax:
http://<domain>:<port>/c/<ECI>/query/<RID>/<function name>?name0=value0&...&namen=valuen
KRL is implemented in such a way that a function cannot change the state of the pico. Nor may it have any side effects on anything else*. In particular, an HTTP POST is not allowed from within a KRL function.
*A query should not be able to have actions on the outside world. However, KRL allows a function to perform an HTTP GET request, synchronously, so as to use information from a resource on the Internet as a part of its calculation. Ill-designed (from the RESTful perspective) resources may be changed by such a request.
Events to a pico
An event is sent to a pico by issuing a POST (or GET**) request to the pico engine which currently hosts the pico. The engine converts the HTTP request into an event to the pico. Many of the rules in any of the rulesets installed in the pico may be selected for evaluation. Rulesets are considered in a non-deterministic order, but within a ruleset the selected rules will be evaluated in the order written. Each rule may conditionally take action on other picos or the outside world in general (including HTTP POSTs), and may also have effects on the state of the pico. Furthermore, a rule may raise additional events (to the same pico) which will be processed as part of the original incoming event (See Raising Explicit Events in the Postlude). You might think of the original event as ricocheting within the boundary of the pico until things settle down, and all rules have had a chance to run. Only then will results specified by the rules be gathered together and packaged into an HTTP response to the event generator which issued the request. Any actions taken on the outside world happen as the rules are evaluated, but the results are not returned until event evaluation is complete. (See Event Loop Introduction for a more complete discussion).
The URL used for an event has the following format (called the Sky Event API):
http://<domain>:<port>/sky/event/<ECI>/<EID>/<event domain>/<event type>?name0=value0&...&namen=valuen
The key to distinguishing an event from a query is to notice the first two components of the URL path, which will be exactly /sky/event/
and these are followed by the event channel identifier (which the node pico engine uses to identify which pico is to receive the event), an event identifier (used to track event logging within the engine), and the event domain and event type (which are simple identifiers to distinguish one kind of event from another). Finally, there are zero or more named arguments. These become event attributes and are made available to the rules which select on the event, together with an attribute named _headers
whose value is a map containing the HTTP request headers.
Version 1.x of the pico engine provides an alternate event syntax:
KRL is implemented in such a way that events can both take action (conditionally) on the world outside the pico, and change the state of the pico. These are specified, respectively, in the action section of the rule and in the postlude.
**For events, from the RESTful perspective, event generators should use an HTTP POST because the pico (viewed as a resource) may change as a result of the request. Some event generators may not be able to issue HTTP POST requests (for example, the location bar of a browser), so for convenience the node pico engine does accept a GET request for an event. Event generators embedded in programs, web pages, and even curl
should use POST requests.
Name/Value binding vs. Variable assignment
In most programming languages, there is an assignment operation, which changes the value of a variable. It is often denoted by an equal sign. The left hand side of the equal sign evaluates to an address (sometimes called an l-value) in which resides the actual value. The right hand side of the equal sign evaluates to a value (sometimes called an r-value). The assignment statement causes whatever value is in the l-value location to be replaced by this r-value.
Name/Value binding in KRL
Unlike most programming languages, KRL does not use the equal sign for variable assignment. The left hand side is always a simple identifier – a name, which is bound to the value produced by the right hand side. From the point in the ruleset at which a binding occurs, that name refers to that value. If the binding occurs in a ruleset's global block, the binding is in force from that point on in the global block and throughout any of its rules. If the binding occurs within a rule, it is in force from that point on throughout the rule's evaluation.
Rebinding (another binding with the same name) is strongly discouraged (see declaration semantics). The krl-compiler
and the krl-parser
it embeds, and the IDE plugins will give a warning, "Duplicate declaration" when you do this.
In a rule, binding can occur explicitly in the rule's prelude (see declarations) and implicitly (there is no equal sign) in the setting
clause. A setting
clause can occur as part of the rule selection (see Capturing Values) , as an outcome of an action (see User-Defined Actions), or as part of the foreach
portion of a rule (see Looping in Rules).
Remember that name binding, or declaration, is very different from variable assignment.
Entity variable assignment
The state of a pico is maintained in entity variables.
The state of a pico can be changed only in the postlude of a rule, and since rules are evaluated only as part of a pico's reaction to an event, the state of a pico can be changed only by events.
An entity variable assignment works very much like variable assignment in many programming languages. The difference is that it is denoted by the colon-equals symbol :=
instead of a simple equals sign. The reason for the difference is so that programmers will be aware of the difference between changing the value of an entity variable and binding a name to a value. It was decided to use the longer symbol for its less frequent application, simply to save keystrokes.
Pico persistent state
A major advantage of using picos in distributed computations is that each pico maintains its own state. This is kept as the values of the entity variables used in each of the rulesets installed in the pico.
Picos follow the "actor" model from which they gain their power for concurrent computations. While many picos may be executing concurrently, each pico is only handling one message at a time. As other messages come in for that same pico, they are queued up for later consideration.
A pico responds to two kinds of messages: queries and events, as introduced in the previous section. Picos also follow the "event condition action" pattern. When a rule reacts to an event, it conditionally takes actions. Then, it may mutate its state by assigning new values to its entity variables in the rule postlude.
A simplified grammar for a rule is shown here (extracted from a more complete grammar):
When a rule selects, and is evaluated, it can have effects on the outside world by (conditionally) taking action (line 4). Changes to its own state can only happen in the postlude (line 5). Names can be bound to values in the event expression, the prelude, and for values returned by an action. But name-value binding doesn't change anything, either within the pico itself, or in the greater world.
The stylized diagram below shows the pico as a boundary between itself and the outside world. Changes to itself can occur only when a selected rule changes an entity variable (as part of the rule's postlude). A selected rule can also, if conditions are met, take action on things outside of the pico.
Operators produce new values
KRL has a rich set of built-in Operators. However, they do not operate on structures to change them in-place. Rather they produce completely new structures.
This can lead to code like the following, which a programmer might expect to change an entity variable. Supposing ent:var
contains the following structure (an array of maps):
then this map operation put
might be expected to increment the value mapped from "b"
The value computed by the second line is either discarded (since it is not bound to another name) or returned (if it were the last line of a function).
Even if these lines were part of a function which was invoked with its value assigned to the entity variable in a rule postlude, it would replace the array of maps with just one of the maps. Having the function return just temp
would result in no change to the entity variable, because the operator doesn't reach into the structure and make a change.
Order of rule evaluation
As noted earlier, when multiple rules within a ruleset select for an event, they will be evaluated in the order they appear in that ruleset. More precisely, the pico engine creates a list of rules to evaluate, called a "schedule" (See What Happens When an Event Is Raised?).
Once the schedule of rules has been created, the pico engine evaluates them in that order. The schedule shrinks by one each time a rule evaluation completes. Evaluation for the event ends when the schedule is empty.
The schedule can grow when one of the rules raises an event in its postlude. The pico engine determines which rules select on the raised event and adds all such rules, in order, to the end of the schedule. Since these rules are added to the end of the schedule, they will not evaluate until after all rules on the original schedule have evaluated. This can lead to unexpected evaluation orders. Within a ruleset, lexical ordering of rules determines evaluation ordering. But this applies each time the pico engine looks for rules selecting on an event. Once when the event is received from the outside world, and again whenever an event is raised.
Here is an example of such unexpected order of rule evaluation. We consider these three rules (from the ruleset published here) with unrelated portions elided. The set_up_url
rule (lines 1-7) is used manually to establish a URL for use by the record_probe_temp_to_sheet
rule (lines 16-26). We wanted the URL to change monthly, and a new rule was added and placed earlier so it would be evaluated first when the wovyn:new_temperature_reading
event came in:
Upon receiving the new temperature reading event, the pico engine schedules two rules for evaluation. First, we check for a new month (lines 8-15), preparatory to recording to a sheet. The idea is to change the URL for the first reading of a new month, before it gets recorded. When this happens, the check_for_new_month
rule raises an event for the first rule to handle. The first rule comes first, right, so we might expect it to be evaluated before the third rule (and we did expect this – true story).
However any rules selected by a raised event are added at the end of the schedule. So, in this case, the rules actually evaluate in this order:
check_for_new_month
record_probe_temp_to_sheet
set_up_url
(for first reading of new month)
because the first two listed were added to the schedule when the event came in, while the third was only added to the schedule later during the evaluation of the first. So the first reading of a new month went to the old URL and subsequent readings for that month went to the new one.
The bug was fixed by repeating the postlude (lines 4-5) of the set_up_url
rule as the postlude of the check_for_new_month
rule (replacing line 13). It is tempting to avoid repetition of code (see DRY vs WET) by using rule chaining with raise
but that doesn't work in a case like this.
https://en.wikipedia.org/wiki/Don%27t_repeat_yourself#DRY_vs_WET_solutions
Interference between rulesets
For queries, both the pico and the ruleset are specified in the /sky/cloud URL. However, for events, the ruleset is not specified. As shown in the Sky Event API page, the URL specifies the pico (by giving one of its ECIs) and the domain and type of the event. Any and all rulesets installed in the pico can react to an event, whereas for a query only the identified ruleset will respond.
When an event comes in to a pico, the engine looks at all of the rulesets installed in the pico (in an undetermined order). Every rule which selects on the event is added to a schedule of rules to evaluate (within a ruleset, rules will be added in the same order they appear in the ruleset). After all rulesets have contributed to the schedule, the engine begins to evaluate the rules selected, one after the other. If a rule postlude raises an event, the engine again examines all of the rulesets installed in the pico and adds the rules which select to the end of the schedule.
Usually only one or a few rules will select on a typical event, and often only a single ruleset will be involved. Rules within a ruleset typically use the same event domain, and that domain id is usually different from ruleset to ruleset. Even so, care must be taken to ensure that the rulesets installed in a pico do not interfere with each other.
As an example of what can go wrong, consider the wrangler:ruleset_installed
event (discussed also in the Initializing entity variables and Wrangler pages). Whenever a ruleset is installed in a pico, Wrangler will raise this event. The engine will then consider all of the rulesets installed in the pico (including the one just installed). Unless you have a ruleset keeping track of all rulesets installed in a pico, you probably only want the rule in the ruleset just installed to select. The idiom commonly used to ensure this is shown here:
When Wrangler raises this event, one of the event attributes is an array of ruleset identifiers (for the rulesets installed) named rids
and this where
clause ensures that the rule can select only if that array contains the ruleset identifier of this ruleset. So, when other rulesets are installed in the pico later, this rule in this ruleset won't select.
For another example, when a pico requests the creation of a new child pico, Wrangler will raise the wrangler:new_child_created
event to the parent pico once the new child pico exists (but before it has initialized itself). If any ruleset installed in the pico has a rule which selects for this event, that rule will be evaluated even if it doesn't apply.
The request for a new child pico usually originates from a rule evaluating within the parent pico:
In this case the child pico will have a random name
, and the same backgroundColor
as the parent pico. In addition to the required event attribute, this rule is asking for two other attributes to be included, named expr
and txn_id
(this example is taken from the Generate KRL code using KRL page). Once Wrangler has created the new child pico (per these specifications) it will raise the wrangler:new_child_created
event (and all rules in all rulesets installed in the pico will have a chance to be selected).
The rule which the developer intends to select will be in the same ruleset and will look like this:
The key to making this work is meta:txnId
(part of the meta library) which is a unique identifier for this schedule of rules to evaluate. This ensures that this rule will select only one time, when the child pico it asked for is created. So if, later, the developer manually creates a new child pico in the developer UI About tab, this rule will not be selected.
Rule name vs event salience
This confusion is common among new KRL programmers. The name of a rule is not the same as the name of an event to which it is listening (which is salient for the rule).
The rule above is named send_twilio_sms_message
but selects when the pico receives the twilio:new_sms_message
event. The name is solely for reference (in logs for example) and must be unique within a ruleset. The select when
clause is part of the declaration of what events are salient for this rule (or, as we say, for which the rule selects). Incidentally, notice that rule names are typically verbs while event names are usually nouns.
A common mistake would be to use the code below (perhaps in some other ruleset) to trigger the send_twilio_sms_message
rule:
This way of thinking is also evidence of a misconception: rules are not like procedures which we can call by raising an event.
Here is a common pattern which highlights the difference between rule names and salience:
Notice two rules, with (of course) different names. But both select on the twilio:new_sms_message
event, so they are both added to the rule evaluation schedule, and will be evaluated in the order they are written. If the to
attribute is not in the list of known numbers (checked in line 3), the last
keyword in line 5 of the postlude will empty the rule evaluation schedule. This means that even though the send_twilio_sms_message
rule (of lines 8-11) was selected, it will not be evaluated.
String constants vs computed strings
As in most programming languages, KRL has string constants. At compile-time, a string is produced based on the characters listed between quotes.
Double-quoted strings
When KRL is compiled, such strings are compiled into JavaScript string constants. For example:
binds the name alphabet
to the (expected) string.
As in JavaScript, such strings cannot contain line breaks.
Chevron-quoted strings
KRL also provides chevron-quoted strings. Such strings can contain line breaks. They can also contain double quote characters without escaping.
binds the name alphabet
to the same value as the double-quoted example above.
The value of a chevron-quoted string is determined at run-time, not at compile-time. They can differ each time they are used if they contain one or more beestings.
If such a string is bound to a name in the global
block of a ruleset, the value will be determined at the time the ruleset is installed in a pico. If contained in a function, the value will be determined each time the function is called. If contained in a rule’s prelude, the value will be determined each time the rule selects. If contained in a rule’s postlude, the value will be determined each time the rule fires (or fails to fire, depending on which postlude it appears in).
Chevron-quoted strings are particularly useful in generating HTML, or other languages which contain double quote characters. Some examples:
This is a string where leading and following newlines are wanted.
This example contains two beestings, a ternary expression and a simple expression. Notice that the closing chevron quote is on a new line. This is because the compiler handles the ambiguous >>>
as closing the quote and then seeing a >
instead of including the >
at the end of the quote and then closing it (issue #588).
This example contains two simple beestings.
Chevron-quoted strings can contain beestings which themselves contain chevron-quoted strings.
In this example, the second to last beesting, #{is_self => << title="this is you">> | ""}
, contains a nested chevron-quoted string (chosen because it specifies a string with two double quote characters). That string could also have been written #{is_self => " title=\"this is you\"" | ""}
which is a trifle less readable.
Thinking KRL is like Java
It isn’t. Consider this ruleset, written with functions/events named in the Java way of thinking (but raising a “terminal event” in line 14):
It looks like a class that might hold a simple private variable/value named stuff
. But, when installed in a pico, it doesn’t create a new instance; the pico pre-existed.
Here is a naive use of the holder
module:
When we send the user:checker
event, both rulesets get scheduled for evaluation, in the order written. The first one, checker
, chooses a random value in line 8, persists it in line 11, and saves it away in line 12. Then it’s the turn of tester
which give us the verdict. It will show false
! Why? The initial schedule was: first, java_pattern / checker
then java_pattern / tester
but line 12 adds holder / setter
to the end of the schedule, so the latter rule won’t evaluate until after the tester
rule has completed. The misconception is evident in “saves it away in line 12” which should be thought of as “schedules it to be saved away, later, in line 12”.
Here is a correct usage:
The correct usage depends on the ruleset used as a module raising a terminal event, as holder
does in line 14. This is an event that the ruleset doesn’t use react to itself, but provides for emitters of the holder:setter
event, so that they will be notified when it has completed.
Common errors
Sometimes a simple typo can cause hard-to-find bugs. Documented here are such incidents.
Regular expression capture groups
Using the setting
clause with a capture group in the select when
part of a rule, without having actually indicated a capture group in the regular expression.
Here, the programmer expected to bind the name caller_id
to a portion of the event attribute named number
. But caller_id
remains unbound (and thus evaluates to null
when used) because no capture group was specified in the regular expression! A simple typo which can lead to a hard-to-find bug. It should have been written:
with the capture group indicated by enclosing it in parentheses.
Regular expression capture vs. explicit binding
At first glance, these two ways commonly used to bind an event attribute value to a name for use within a rule, appear to be equivalent:
This code requires the event attribute attr_name
to match the pattern (at least one character) or the rule will not even select. Because a regular expression operates on a single line of text, if the attribute value contained multiple lines, only the first line would be bound to the name local_name
and any remaining lines would be discarded.
In this code, the entire value of the attribute attr_name
is bound to the name local_name
.
Functions and function calling
In KRL a function is defined and bound to a name.
The function later is used by referring to it by name, followed by arguments in parentheses. With functions which don't take arguments are used, it is easy to forget the ()
after the function name. Doing so, however, doesn't invoke the function, producing the expected value, but the function itself becomes the value. This can lead to astonishing errors.
Dots vs. colons vs. map references
Dots introduce operators. Dots should not be used to refer to a key within a map (a tendency of JavaScript programmers).
Colons appear between a module name (or library name) and the name of a value (often a function) provided by that user-defined module (or built-in library). If you (typo of course) use a dot instead of the colon, you'll get an unknown operator error message.
Map references use curly braces immediately following the map (or map name) and containing the key (or hash path) whose value is desired.
Array references use square brackets.
Map references and strings
It's easy to get a map reference wrong by typing a quoted name when a bare word is needed. In the code shown here, a string constant (the binding name) is used (in line 13) instead of the value bound to that name:
The intention is that, in rule one
we have (at line 5) a map named service
, and we want to save its serviceEndpoint
value, keyed by the first of its recipientKeys
. We use ent:se
(also a map) to hold this mapping from keys to endpoints. The bug in line 13 comes from getting confused about referencing something by its name (as done twice in line 5) with referencing something by the value a name maps to (as should have been done in line 13). Assuming that a recipient key will never be literally "their_key"
, line 13 will always set se
to null
and the http:post
will always fail.
The empty string is not null
When using the Testing tab, and leaving a parameter field empty, it is a common error to expect that named event attribute to be null
. Instead it is an empty string.
Using the Universal Operator isnull()
against the event attribute will return false
however, because the empty string is not null
.
Similarly, using the Universal Operator defaultsTo()
with the event attribute will not replace the empty string with the first argument to defaultsTo()
. The following name/value binding will not work as expected when the event attribute named name
is the empty string:
Instead, consider using the || operator:
See "Compound Predicates" in the Predicate Expressions page.
Also, the string "null"
is also not null
. But "null".decode()
(see the String Operators page) will evaluate to null
. As will null.decode()
, so if you’re not sure, decode.
Regular expressions and case sensitivity
The String Operator match()
takes a regular expression, which can be flagged as case-insensitive. If you forget to do this, it will return false
when you expect it to return true.
For example, the first use of the match
operator will return false
but the second will return true
as expected:
When a string is used to create a regular expression (using the Universal Operator as()
), as in the first line of the following, there is no way yet to specify the needed i
flag. So, in this case the second line shows a workaround (featuring the String Operator uc()
).
Operator precedence
Expressions involving infix operators won't normally require parentheses, but these can be used when the built-in precedence doesn't suit your needs.
Obscure bugs can occur. For example, this code (adapted from production code, from this ruleset):
Overall, the expression is concatenating strings. When one of them was meant to be conditionally included, the code of lines 9 and 10 was written. Whatever need_detail
is, the string before the =>
is not empty and hence truthy, the entire expression evaluates to "<p>Moves: ... </p>". Both the concatenated string being tested by => and the one after the alternative | will not be part of the result. The ternary expression binds more loosely than string concatenation. The correct code would be:
enclosing the ternary expression in parentheses. Notice that line 5 also includes a ternary expression, but this is enclosed within a beesting, avoiding the precedence problem.
Screenshot of the actual change made to the code:
Providing attributes when you want to generate an event
From within a rule, you can generate an event. There are two ways to do this, depending on which pico will be reacting to the event, and when you want the event to be received.
event:send
This is an action (which can only be used in the action part of a rule) and is provided by the event
library (see the page of the same name). The library is built-in to the pico engine, and so you can use it without a use module
clause in the meta
block of your ruleset. Its first argument is a Map, which has among others the key "eci"
whose value is the ECI of the pico that you want to receive the event message. Usually this is another pico, not the one which is evaluating your rule. Other keys in the map include "domain"
and "type"
(also acceptable "name"
) which provide the two parts of the event identifier, and "attrs"
whose value is itself a Map giving the event attributes.
The event is sent asynchronously to the receiving pico, which may or may not be hosted by the same pico engine. In the rare case when the ECI is one for this same pico, the one which is evaluating your rule, the event will be queued up because your pico is busy. It will be handled in a different and later execution cycle than the one going on now.
raise ... event ... attributes ...
This a statement that can appear in a postlude, and is very different, although it also generates an event, but one that is always sent to the very same pico which is evaluating your rule.
The rules that select on this event will be evaluated in the same execution cycle as the current rule, just a little later.
The event attributes are provided in a Map introduced by the attributes
keyword in the statement.
A common typo
It is common to use "attributes"
when you mean "attrs"
or vice versa. See this commit for an example of such a typo.
An empty array is truthy
When used in a Boolean expression, an empty array will evaluate as true
. Of course, you wouldn’t write something like:
But, you might be checking for pending incoming subscriptions, where the inbound
function returns an array of such:
and handlethem
would be called even when the array was empty. Instead use the length
operator (one of the Array operators):
which works because among the numbers, zero alone is falsy.
In general, KRL truthiness follows JavaScript. And empty array evaluates to true in JavaScript as well.
domain ≠ rid
The domain of an event expression is often related to, but not identical with, the ruleset identifier. For example, one might be tempted to use meta:rid
when an event domain is expected, because of the similarity.
But a ruleset identifier, or RID, (e.x. byu.hr.connect
) contains periods which are not syntactically correct in a bare word in an event expression, such as
So, in this case you can either just spell out the event domain, as in
Or do something fancy like
This example was inspired by this commit.
Thought an event attribute was sent
event:send
is an action in the event library (sometimes called a module, as it uses the same syntax as modules (the colon separating the module name and the function/action/value it provides)).
The event:send
action generates an event that is sent to a pico, and takes two arguments, the first being a map with certain expected keys, and the second being a string (optional) containing the host of the receiving pico.
What is wrong with this code?
Answer: "co_id"
is not one of the expected keys in the map (which is the first (and only here) argument to the event:send
action). The author was thinking that this would send an attribute of that name as one of the event attributes. Instead, since there was no "attrs"
key in the map, no attributes were sent. Since "co_id"
was not an expected key, the action just ignored it silently. The corrected code is:
This example was inspired by this commit, and finishes an attempt to follow the best practice from the “Managing Pico Lifecycle” document.
Initializing entity variables on ruleset installation
It is a common practice to react to the wrangler:ruleset_installed
event with a rule that gives entity variables an initial value for the pico in which the ruleset was just installed for the first time.
An example of what this looks like:
Things can go wrong here.
First, it is essential to guard the selection with this where
clause. Otherwise, as more rulesets are installed into the same pico, this rule would select on those and re-initialize ent:apps
to the initial value. If in the meantime, it had been changed, those changes would be erased!
Second, if the pico’s owner installs the same ruleset a second time, ent:apps
will also be re-initialized to the initial value.
To avoid this second problem, it is best practice to guard against it with code like this:
This cautionary tale was inspired by this commit which followed an emergency fix to manually restore ent:apps
to a value that had accumulated over time between the first installation of a ruleset into a pico and a second, ill-advised, installation months later.
Using regular expressions with multi-line strings
It is possible in the select when
clause of a rule to check for the existence of event attributes by name, matching them against a regular expression and binding the value to a name (see the page Capturing Values).
However, when the value matched is a string, only the first line of that string will be retained. See this commit for an example of working around this.
Ruleset identifier (RID) vs. KRL filename
By convention, most KRL programmers use the ruleset identifier as the name of the file containing the ruleset, with .krl
as the filename extension. This is not enforced by the KRL compiler (as a similar convention is enforced in Java). If you decide to not follow the convention, be aware of potential pitfalls.
The RID must be used in a sky/cloud/
URL, and is the value of meta:rid
within a ruleset. It must also be used as the rid
attribute in the wrangler:install_ruleset_request
event, when accompanied by the absoluteURL
attribute. In contrast, the url
attribute for that same event will end with the entire filename where the ruleset is located (and so will include the .krl
extension.
The pico engine enforces uniqueness of rulesets installed in any given pico that it hosts by using the RID, not the URL from which it is installed.
Copyright Picolabs | Licensed under Creative Commons.