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')
'''