Compose functions with sub-expressions

You may have noticed another syntax in examples from other sections, namely expressions inside of curly brackets. These are called sub-expressions, and they can be used to provide a calculated value to another expression, instead of just a static one.

A simple example of this is when you upload your own images to a Canvas workpad. That upload becomes an asset, and that asset can be retrieved using the asset function. Usually you’ll just do this from the UI, adding an image element to the page and uploading your image from the control in the sidebar, or picking an existing asset from there as well. In both cases, the system will consume that asset via the asset function, and you’ll end up with an expression similar to this:

image dataurl={asset 3cb3ec3a-84d7-48fa-8709-274ad5cc9e0b}

Sub-expressions are executed before the function that uses them is executed. In this case, asset will be run first, it will produce a value, the base64-encoded value of the image and that value will be used as the value for the dataurl argument in the image function. After the asset function executes, you will get the following output:

image dataurl="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0"

Since all of the sub-expressions are now resolved into actual values, the image function can be executed to produce its JSON output, just as it’s explained previously. In the case of images, the ability to nest sub-expressions is particularly useful to show one of several images conditionally. For example, you could swap between two images based on some calculated value by mixing in the if function, like in this example expression:

demodata
| image dataurl={
  if condition={getCell price | gte 100}
    then={asset 3cb3ec3a-84d7-48fa-8709-274ad5cc9e0b}
    else={asset cbc11a1f-8f25-4163-94b4-2c3a060192e7}
}

The examples in this section can’t be copy and pasted directly, since the values used throughout will not exist in your workpad.

Here, the expression to use for the value of the condition argument, getCell price | gte 100, runs first since it is nested deeper.

The expression does the following:

  • Retrieves the value from the price column in the first row of the demodata data table
  • Inputs the value to the gte function
  • Compares the value to 100
  • Returns true if the value is 100 or greater, and false if the value is 100 or less

That boolean value becomes the value for the condition argument. The output from the then expression is used as the output when condition is true. The output from the else expression is used when condition is false. In both cases, a base64-encoded image will be returned, and one of the two images will be displayed.

You might be wondering how the getCell function in the sub-expression accessed the data from the demoData function, even though demoData was not being directly inserted into getCell. The answer is simple, but important to understand. When nested sub-expressions are executed, they automatically receive the same context, or output of the previous function that its parent function receives. In this specific expression, demodata’s data table is automatically provided to the nested expression’s getCell function, which allows that expression to pull out a value and compare it to another value.

The passing of the context is automatic, and it happens no matter how deeply you nest your sub-expressions. To demonstrate this, let’s modify the expression slightly to compare the value of the price against multiple conditions using the all function.

demodata
| image dataurl={
  if condition={getCell price | all {gte 100} {neq 105}}
    then={asset 3cb3ec3a-84d7-48fa-8709-274ad5cc9e0b}
    else={asset cbc11a1f-8f25-4163-94b4-2c3a060192e7}
}

This time, getCell price is run, and the result is passed into the next function as the context. Then, each sub-expression of the all function is run, with the context given to their parent, which in this case is the result of getCell price. If all of these sub-expressions evaluate to true, then the if condition argument will be true.

Sub-expressions can seem a little foreign, especially if you aren’t a developer, but they’re worth getting familiar with, since they provide a ton of power and flexibility. Since you can nest any expression you want, you can also use this behavior to mix data from multiple indices, or even data from multiple sources. As an example, you could query an API for a value to use as part of the query provided to essql.

This whole section is really just scratching the surface, but hopefully after reading it, you at least understand how to read expressions and make sense of what they are doing. With a little practice, you’ll get the hang of mixing context and sub-expressions together to turn any input into your desired output.