Expression language functions
RiskScape allows you to define custom functions using the built in RiskScape expression language. This can be helpful if you want to reuse or highlight a particular expression within your project.
Note
Expression language functions are currently included in the Beta plugin.
Take the following bit of a pipeline as an example:
select({
*,
if(
loss < 0,
then: '-$' + abs(round(loss, 2))
else: '$' + round(loss, 2)
) as formatted_loss
})
This bit of pipeline formats the numeric loss as a formatted string value showing the dollar amount so that it looks nice in a report or in a spreadsheet. This intent of this expression might not be obvious to a casual reader, and while commenting can make that clearer, turning it in to an expression function can also be a good option. It will also allow the function to be reused across models within your project.
We can convert this to an expression function by adding this to your project:
[function format_dollars]
framework = expression
description = Convert a floating point value in to a formatted dollar amount string
argument-types = [floating]
return-type = text
source = (val) -> \
if(val < 0, then: '-$' + str(abs(round(val, 2))), else: '$' + str(round(val, 2)))
With this in your project, we can test it on the command like so:
$ riskscape expression eval "format_dollars(-2.5555)"
-$2.56
Using this new function, our pipeline code now becomes a lot simpler, like this:
select({*, format_dollars(loss) as formatted_loss})
argument-types
Expression functions can be defined with or without an argument-types
parameter.
When given, these types are displayed in the function’s reference and are used to validate
the use of the function within another expression.
If we tried to give our format_dollars
from the earlier example some text instead of
a floating point number, it would fail with a type mismatch error.
It is also valid to leave out the argument-types
parameter entirely and let the function’s
body accept whatever arguments it’s given and decide whether it works or not.
return-type
The return-type
parameter is also optional for expression functions, but can be useful when
you want to force a particular type to be returned from your function.
Be aware that if the value your expression returns can not be converted in to the
desired return-type
, then your model will fail at that point.
Chaining complex expressions
In addition to using if()
and switch()
functions for control logic, you can also
use the map()
function for cases where you might want to ‘chain’ multiple expressions
together.
For example, the following code is a customized spatial sampling function that always
returns both the max and the average hazard intensity value. It does this by using map()
to take the result of one expression (i.e. the sample()
result), and then apply another expression
to that (taking the mean()
and max()
).
[function custom_sample]
framework = expression
source = '''
(exposure, coverage) ->
map({
# first sample all intersections and turn that into a list of sampled hazard values
all_values: map(sample(exposure, coverage), h -> h.sampled)
},
# then take the list of sampled values and return the mean and max
sampled -> {
max: max(sampled.all_values),
mean: mean(sampled.all_values),
})
'''
Nullable arguments
Just like Python functions, one thing to be aware of when adding an expression function is how RiskScape handles Nullable types.
RiskScape defaults to null-safe behaviour, so unless you explicitly state that your function can accept nullable types, RiskScape will not call your function when something is null.
For example, let’s say you define a very simple exposure function like this:
[function my_exposed]
framework = expression
source = '''
(exposure, hazard) ->
if(hazard > 0,
then: 'Exposed',
else: 'Not exposed')
'''
However, when you use this function in your model, you get a lot of ‘null’ results where you would expect to see ‘Not exposed’. This is because the hazard intensity measure in your model will typically always be nullable, and so in cases where the element-at-risk is not exposed (i.e. the hazard value is null), your function won’t get called at all.
The solution in this case would to update the function definition to make it explicit that the function can accept a nullable hazard argument, e.g.
[function my_exposed]
framework = expression
argument-types = [exposure: anything, hazard: nullable(floating)]
source = '''
(exposure, hazard) ->
if(hazard > 0,
then: 'Exposed',
else: 'Not exposed')
'''