KRL Guide
Conditional Statements 101
Getting used to KRL Conditional Expressions
Most languages nowadays support “if-then” statments to run conditional statements. KRL is no different. However, using them may be a little more unconventional and needs a little bit of practice.
Conditionals in Rulesets
In essence, an ruleset contains a list of conditional expressions known as rules. A ruleset will be installed on a Pico, but its rules will only be run under the conditions that you give them. When defining a rule, the very first line you will write is select when <domain> <type>
, where <domain> and <type> are given by you. A ruleset may contain many rules with the same domains and different types, which makes it behave like an if-then or a switch statement. An example of a ruleset with multiple rules with the same domain but different types is given below. This is a very basic rule where the user wishes to change the topping on their pizza. 🍕
ruleset custom_pizza {
meta { ... }
global { ... } rule pepperoni {
select when pizza pepperoni always {
ent:pizza := 'Pepperoni Pizza 😋'
}
} rule cheese {
select when pizza cheese always {
ent:pizza := 'Cheese Pizza 😋'
}
} rule pineapple {
select when pizza pineapple always {
ent:pizza := 'Pineapple Pizza 🤢'
}
}}
As seen here, the domain across these three rules is pizza
, but the types are pepperoni
, cheese
, and pineapple
. Rules can even share the same domain and type, yet still run under different conditions. If you want to know how to do this, see this post and this post from the KRL manual.
Conditionals in Rules
Understanding how rules works is important in understanding how to use conditional expressions. According to the KRL Manual, a nice way of remembering how rules operate is: when, if, then. As discussed above, each rule wil select when its appropriate domain and type are raised by an event. Once the rule has been fired, an optional prelude block of code may be run in order to bind variables to incoming event attributes, and a postlude block of code to perform any actions with those variables.
An if-statement can only go after the prelude and before the postlude. Both the prelude and the postlude code blocks are optional, but if you attempt to place an if-statement inside of them, you will get an error. Any conditional logic that can’t be acheived within the single if-statement must be performed through ternary operators inside of the pre or postlude. If you are unfamiliar with ternary operations, click here and here for more information.
Let’s look at another example with the pepperoni pizza rule!
ruleset custom_pizza {
meta { ... }
global { ... } rule pepperoni {
select when pizza pepperoni pre {
topping = event:attr("topping")
} if topping then
send_directive("Topping", { "body", "New topping!" }) fired {
ent:pizza := topping + " Pepperoni Pizza 😋"
}
}
The most important takeaway from this rule is the order in which it is written. Remember: when, if, then.
- When :
select when pizza pepperoni
- If:
if topping then send_directive(“Topping”, { “body”, “New topping!” })
. Here we are checking to see if there was any event attribute that was sent to the rule that gives us a topping to add to our delicious pepperoni pizza. If there was a topping attribute passed to us, then we will send a directive AND thefired
postlude block will run. If there was no topping event attribute given to the rule, then no directive will be sent, and the postlude will not execute. The most common operations used for an ifi-statement are directives andnoop()
. - Then:
ent:pizza := topping + “ Pepperoni Pizza 😋”
. This is not to be confused with thethen
keyword that is part of the if-statement. This refers to the action of the rule. In other words, if the rule is selected, then what do you want to do with it? In our case, we checked to see if there was a topping, and now we want to add it to our pizza, so that is what we decide to do in the postlude.
Keep in mind that there are many cases where you will not need any conditional statement between the prelude and the postlude. In this case, the rule will take action by defualt, and appropriate postlude is always
instead of fired
or notfired
. Helpful information on the rules postlude are found here.
Conditionals in Functions
Last but not least, conditional expressions in functions are expressed solely through ternary operators. If you try to place an if-statement inside of a function, your ruleset will not be able to register.
When first transitioning over to programming in KRL, this can be hard to grasp, but with practice you will find that you can accomplish everything you need by other means. A ruleset is driven by events and rules, not by functions. Functions are helpful for reducing code duplication, enhancing readability, and retrieving persistent data but functions are not what is going to be the main driving factor of your ruleset.
Many times you will find that your natural object-oriented-programming approach to solve a problem will be to write if <expression> then <action>
in your function. You may want to sort an array, check to see if an element exists in a list, or return your reaction if the topping for the pizza is anything other than pepperoni.
Data structures still have access to universal, array, and map operators, so it takes some practice to shift your mindset from solving a problem with an if-statement to using map()
, reduce()
, or filter()
. These three operators are the most common uses of solving conditional logic in functions.
Since KRL functions return the last line, if I wanted to return my reaction to the topping, I can do something like this:
ruleset custom_pizza {
meta { ... }
global {
getReaction = function(topping) {
reaction = (topping == "Pepperoni" => "Yum" | "Gross")
reaction
}
Although it may be a little more of a mental exercise to use ternary operations sometimes, you can acheive anything you could with an if-statement.
TL;DR
- A rule is one big conditional expression.
- When writing a rule, an if-statement can only go after the prelude and before the postlude. If there is no if-statement, then the rule will be fire (action will be taken) by default. Make sure to understand postludes.
- Only ternary conditional operators can be used in functions, but universal, map, and array operators can still be used to accomplish the same purpose of an if-statement.
- Pepperoni pizza is the best.