This page covers a multitude of general topics about expressions.
Note: The Expression Editor allows you to easily write expressions. Learn more.
An expression is a statement composed of one or more literal values, operators, functions, and variables. Expressions are evaluated by the rules engine to produce a single value. The construction of Appian expressions will be immediately familiar to anyone experienced building formulas in spreadsheet software.
Use this article to learn how to create more sophisticated expressions and use them throughout your application. Expressions are a versatile tool that allow you to build powerful logic in your processes.
Expressions can access contextual data via variables like process variables, node inputs, constants, CDTs, and process properties. Beyond their returned value, expressions do not modify any data inside or outside the Appian system.
You can create an expression by typing it directly into an expression field or through the Expression Editor. Expression fields are marked by an Expression Editor icon.
Below is an expression with its various parts labeled.
New Ticket
and ` by ` are text literals and return in the expression output as written.pv!ticketId
variable returns the Ticket ID value, and the pp!initiator
variable returns the user who started the process.userDisplayName
rule takes a user as a parameter and returns the user's first and last name.The above expression could result in the following: New Ticket AN-9867 by John Smith
More information on using these different parts appears in the sections below.
This section describes a recommended approach that follows Appian best practices.
When you write an expression that can be applied in multiple places around your system, such as one that retrieves information on the most recent meeting event, it is a best practice to create a rule out of it.
An expression rule is a stored expression with a user-defined name, description, definition, and a central location that can be reused within any expression field. They are accessible through the Expression Editor or can be typed into an expression.
See also: Expression Rules, Create Constant, Save Selected Expression As…
Similar to expression rules, instead of defining the same literal value multiple times in your system, such as the number of days until a deadline, define the literal value as a constant.
See also: Constants
A literal is a static value stated in the expression, such as 2, 3.14, or "hello".
The following types of literals are supported:
Expresses a series of text characters. It is stated as text entered between double quotation marks.
"Hello World"
An integer number is any whole number.
82
or -82
A decimal number possesses a decimal point and can express fractional numbers.
1.234
Certain built-in expression values exist in order to concisely express concepts or conditions. Supported special literals include:
true
for the Boolean value true.
false
for the Boolean value false.
null
for an empty (null) value that has no specific data type.
Special literals can be used within any variable as a default value and can be cast to any data type.
An array is an ordered list of data items that can be selected by indices computed at run-time. Arrays can be empty or constructed with literal values, variables, or functions.
{{1, 2}, {3, 4}} = {1, 2, 3, 4}
When entering an array as part of an expression, you must use braces ({}) to enclose it and separate items using commas (,) unless you are referencing a variable.
{2, 3, 9, 1}
To add text in an array, enclose each item in quotation marks ("").
{"a", "b", "c", "d"}
Items in a list that contain data queries may be evaluated in parallel to reduce the overall evaluation time.
In order to select one or more values from an array, use the index operator [] or index().
For example, with an index operator and an array of pv!multiple = {10, 20, 30}
:
pv!Multiple[2]
yields 20
pv!Multiple[{2, 3, 2}]
yields {20, 30, 20}
Example with index() and array of {10, 20, 30}
:
index({10, 20, 30}, 2, 1)
yields 20
See also: Array Functions
A map is an ad-hoc data structure created inline in an expression using the a!map() function. In contrast, a custom data type is a reusable data structure with a specific name and predefined fields.
For example, to create a map with two fields called label
and value
, enter the following:
1
a!map(label: "Item", value: "Entry")
To create an array of maps, enter the following:
1
{a!map(label: "Item one", value: "Entry one"), a!map(label: "Item two", value: "Entry two")}
Fields within a map that contain data queries may be evaluated in parallel to reduce the overall evaluation time.
A dictionary is very similar to a map; it is an ad-hoc data structure mapping fields to values. However, values indexed out of a dictionary often need to be cast to their respective type before use. For this reason, maps should generally be used instead of dictionaries.
To create a dictionary with two fields called label
and value
, enter the following:
1
{label: "Item", value: "Entry"}
Data types Date, Time, and Date and Time are stored as numbers.
For date and time values, the local value (based on the designer time zone) is not stored. Instead, it is stored in Greenwich Mean Time (GMT) and then converted to the user's time zone when displayed on the screen.
To advance either Date or Date and Time by a day, add 1 to the value.
To advance Time by a minute, add 60*1000
to the value. Adding 1 to Time only advances it by a millisecond.
Example Input | Yields |
---|---|
date(2012, 4, 30) - date(2012, 4, 25) |
5 days |
datetime(2012, 4, 25, 12) - datetime(2012, 4, 25, 10) |
0.0833 days -or- 2.0 hours |
date(2012, 4, 25) + 5 |
4/30/2012 |
There are many operators that can be used in expressions to perform data manipulation. The operators provided are divided into two different categories.
To Perform... | Use | Example |
---|---|---|
Addition | + | 10+8 yields 18 |
Subtraction/Negation | - | 10-8 yields 2 - OR - if value is 97 , then -value is -97 |
Multiplication | * | 2*5 yields 10 |
Division | / | 10/5 yields 2 |
Exponentiation | ^ | 2^8 yields 256 |
Percentage (divide by 100) | % | 97% yields 0.97 |
Example Input | Yields |
---|---|
{1, 2, 3} * 10 |
{10, 20, 30} |
{1, 2, 3} + {1, 2, 3} + {1, 2, 3} |
{3, 6, 9} |
{1, 1, 1, 1, 1} + {1, 2} |
{2, 3, 2, 3, 2} |
tointeger({}) + 1 |
1 |
BEST PRACTICE: Apply arithmetic to lists directly, rather than through MNI, to make your process model look cleaner and work faster.
Note: If operating on two arrays that are not the same length, the shorter list will be repeatedly extended until it is the same length as the longer list. This applies to Date and Date/Time values as well: pv!list_of_dates+1
returns the next day for the entire list.
To Perform... | Use | Example |
---|---|---|
Less than | < | 10<2 yields false |
Greater Than | > | 10>2 yields true |
Less Than or Equal | <= | 10<=2 yields false |
Greater Than or Equal | >= | 10>=2 yields true |
Not Equal to | <> | 10<>2 yields true |
Equal to | = | 10=2 yields false |
exact()
function rather than the =
operator to improve performance.Example Input | Yields |
---|---|
1={1, 2, 3} |
{true, false, false} |
{1, 2, 3}<>{4, 2, 6} |
{true,false,true} |
exact({1, 2, 3},{1, 2, 3}) |
true |
1=tointeger({}) |
false |
0=tointeger({}) |
true |
"Hello"="HELLO" |
true (case-insensitive) |
exact("Hello","HELLO") |
false (case-sensitive) |
{1,2} = {1,2,1,2} |
{true, true, true, true} |
{"a","b"} = {"a","a","b","b"} |
{true, false, false, true} |
Note: In the case of comparing an array of text values to a scalar text value, the array elements will not be compared item by item with the scalar text value. In order to compare item by item, you must wrap the scalar value in a list. For example, use {"a", "b", "c"} = {"c"}
instead of {"a", "b", "c"} = "c"
to compare each element of the array to the value "c"
. This only applies to the text data type.
The precedence of operators evaluated in an expression follows the standard Order of Operations:
So, if an expression includes two or more operators, the operator higher on the list is applied first, then the second highest, and so on. In order to ensure an operation occurs before another, enclose it within parentheses.
A variable identifies the data to use when evaluating an expression. It uses syntax similar to Microsoft Excel sheet!cell syntax, where the Appian data domain is the sheet and a named variable is the cell syntax. Appian variables are always of a specific data type.
The variables you can use in an expression depend on the context in which the expression is designed and evaluated. They are listed in the Data tab of the Expression Editor and include Appian variables and user-defined process variables.
For a full listing or variables delivered with Appian, see Process and Report Data.
In the following example, the expression returns the value of a process variable:
'pv!variable.a-name'
.Variables of complex and custom data types use the dot operator to access field values. You can also use the index()
function.
Let's say you have a custom data type "Person" with the following structure:
1
2
3
4
5
Person
|- firstName (Text)
|- lastName (Text)
|- homeAddress (Address)
|- city (Text)
You create a variable (process variable, node input, or local variable) called "personA" of type Person. To access the "firstName" value of the variable, enter any of the following:
index(pv!personA, "firstName", "")
pv!personA["firstName"]
pv!personA.firstName
Note: Appian recommends using index()
to access the index value of a CDT in cases where the variable may have a null value, or the field name is not available. The index()
function allows you to assign a default value through its default value parameter. See also: index() function.
The dot notation is useful when accessing nested fields of custom data type, provided that the nested field is not null.
To access the "city" value of the "personA" variable, enter either of the following:
index(pv!personA, "homeAddress", "city", "")
pv!personA.homeAddress.city
Local variables differ in that they are defined and set within the expression and are only available inside the function that defines them.
The a!localVariables() function defines a local variable and immediately assigns it a value. For example, in the following expression, local!username
is set to the current user's username and then used several times in the rest of the expression:
1
2
3
4
5
6
7
8
a!localVariables(
local!username: loggedInUser(),
if(
len(local!username) > 10,
local!username & " is a long username!",
local!username & " seems like a fairly short username!"
)
)
Using local variables can avoid duplicating logic, make expressions easier to read, and avoid needlessly evaluating a part of the expression multiple times. They can also be used to store a user's input as they interact with the interface.
Local variable definitions that contain data queries may be evaluated in parallel to reduce the overall evaluation time.
Some functions allow variables to be used in one or more of their inputs. These variables are in the fv!
domain and are only available when configuring that function's input. They are auto-suggested in the Expression Editor and listed in the documentation for relevant functions.
For example, the a!foreach() looping function has several variables available in its expression parameter, such as fv!item
:
1
=a!forEach(items: { 1, 2, 3 }, expression: fv!item + 1)
Because it is a function variable provided by the a!foreach()
function, fv!item
cannot be used outside of a!foreach()
. In this case, it is only provided for the expression parameter, so it cannot be used in the items parameter either. As a!forEach()
iterates through the items parameter, it evaluates the expression multiple times, each time providing the current item value to fv!item
.
When writing an expression that includes text and data, use an ampersand (&
) to string (or concatenate) the information together.
For example, use the following expression to display a salutation when using process variables for the title and name data:
1
="Dear " & pv!title & " " & pv!name & ","
To return the following value:
1
Dear Mr. John Smith,
Comments are a way for designers to leave notes in the expression to explain what the expression does.
You start a comment with /*
and end it with */
. Any content between these two symbols is ignored when the expression evaluates.
For example:
1
"Dear "& pv!title /* pv!title should contain either "Mr" or "Mrs" */ & pv!name &","
Includes a comment but still evaluates to the following:
1
"Dear "& pv!title & pv!name &","
Appian functions, custom function plug-ins, rules, and data types are stored within the Appian system.
You can use Appian functions, custom function plug-ins, and rules in expressions to perform operations using arguments you pass to them. The expression then returns a result based on your arguments.
You can use data types in expressions to construct a complex data type value by creating a type constructor.
Arguments or fields within a type constructor that contain data queries may be evaluated in parallel to reduce the overall evaluation time.
Both options of passing arguments and creating type constructors are explained in detail below.
See also: Appian Functions, Expression Rules, Data Types
Functions and rules are defined by their logic and parameters. These parameters accept arguments that determine how the function will evaluate.
For example, the user()
function retrieves the first name of a user using a process variable and a literal text as arguments through the following syntax:
Passing arguments can be done by position or keyword.
Passing arguments by position is required for Appian functions and custom function plug-ins and is best for rules that take three or fewer arguments.
To pass arguments by position, enter the values in order.
For example, the syntax for the joinarray()
function is the following:
joinarray( array, [separator] )
To pass values using positional arguments, enter the following:
1
=joinarray({1,2,3,4},"|")
This evaluates with array as {1,2,3,4}
and separator as |
, returning 1|2|3|4
.
Required Arguments
You must enter values for every required argument in a function or rule or the expression results in an error. For rules, all inputs are required when passing by position.
Optional Arguments
To pass a value for an optional argument, enter values for all arguments defined before the parameter you're entering a value for, even if they are also optional. If not, the expression may apply the argument to the wrong parameter and result in an error or undesired results.
Optional arguments are surrounded by brackets []
in the function documentation.
For example, the toxml()
function has four parameters, three of which are optional.
toxml( value, [format], [name], [namespace] )
To use the default for [format] and [namespace], but specify a value for [name], you must also configure the [format] parameter.
For example:
=toxml(pv!somePersonNameCDT, false(), "person")
evaluates with the following:
pv!somePersonNameCDT
false()
"person"
Whereas, =toxml(pv!somePersonNameCDT, "person")
would evaluate with the following:
pv!somePersonNameCDT
"person"
Unlimited Arguments
Some functions take an unlimited number of arguments, such as sum(). This is denoted by an ellipsis in their function description with the Expression Editor.
For example:
sum( addend, … )
Passing arguments by keyword is only supported in system functions and rules. It is a best practice to pass arguments by keyword when a function or rule takes more than one argument.
See also: System Functions
To pass arguments by keyword, specify the name of the parameter, followed by a colon, then the argument value.
Appian recommends entering a line break after each keyword argument.
For example:
A rule called feedMessageForNewCase
returns the feed message for a case management application using the following definition:
1
="Priority " & ri!priority & ": " & ri!caseSummary & " [#" & ri!caseId & "]"
It includes the inputs priority
, caseSummary
, and caseId
.
To pass arguments by keyword, you could enter the following:
1
2
3
4
5
=rule!feedMessageForNewCase(
priority: pv!priority,
caseSummary: pv!summary,
caseId: pv!id
)
To evaluate into the following:
Priority 1: Basic users cannot connect to server [#100005]
Keywords do not need to be in the order the arguments are defined.
For example, the following still results in the text above:
1
2
3
4
5
=rule!feedMessageForNewCase(
caseId: pv!id,
caseSummary: pv!summary,
priority: pv!priority
)
Optional Arguments
All arguments are optional when passing by keyword. If you do not pass an argument for a parameter, the parameter receives a null value of the parameter type.
For example:
1
2
3
4
=rule!feedMessageForNewCase(
caseId: pv!id,
caseSummary: pv!summary
)
Evaluates with the following:
pv!id
pv!summary
null
Keyword Requirements
The keyword (or rule input name) for a parameter is defined by the designer. They are searched first by the case-sensitive name, then case-insensitive, such that both rules below will evaluate:
1
2
3
4
5
6
7
8
9
=rule!feedMessageForNewCase(
caseId: pv!id,
caseSummary: pv!summary
)
=rule!feedMessageForNewCase(
CASEId: pv!id,
CASESummary: pv!summary
)
If a keyword is not matched with a parameter name, the argument is ignored and the parameter receives a null value.
When specifying the keyword, use single quotes around it if it contains characters other than letters/numbers/underscores or begins with an underscore (similar to namespaces).
For example:
1
2
3
=rule!person(
'_firstName': "John"
)
Note: Passing arguments by keyword is not supported when creating rules for the Web Content Channel, process reports, or events. If used, it will cause the process or task to pause by exception.
Appian functions, custom functions, rules, and data types are supported as arguments.
The syntax for passing them is similar to passing variables by inserting the object's domain and pointing to its name similar to the following:
fn!sum
rule!myrule
type!Person
Most functions can be passed as arguments using the sntax above. For example, passing the sum()
function to the reduce()
function:
1
=reduce(fn!sum, 0, {1, 2, 3}) yields 6
Note: The following Appian functions cannot be passed as an argument:
a!localVariables()
function, such as apply(a!localVariables . . . )
load()
function, such as apply(fn!load . . . )
with()
function, such as apply(fn!with . . . )
a!save()
function, such as apply(a!save . . . )
All rules can be passed as arguments using the syntax above. For example, passing a rule rule!isnumbereven
to the any()
function:
1
=any(rule!isnumbereven,{-1,0,1,2}) yields true
If you pass a rule, interface, or decision to an expression and it is later deleted, the expression will still evaluate. If you inspect an import package, however, and it contains expressions that require the deleted rules, the rules will not be listed as missing precedents.
See also: Deleting Expression Rules
Passing a data type for a type comparison:
1
=if(typeof(ri!input) = type!User, user(ri!input, "email"), group(ri!input, "groupName"))
When using a data type in an expression, data type names are case-sensitive. Specifying the namespace of the data type improves readability but is optional when the name of the data type is unique.
If the data type name is not unique, the system returns a validation error indicating the namespace must be specified. When specifying the namespace, use single quotes around the domain and data type name if the namespace contains characters other than letters/numbers/underscores or begins with an underscore.
For example:
1
'type!{http://www.appian.com/ae/types/2009}User'
If you pass a data type to an expression and it is later deleted, the expression will still evaluate. If you inspect an import package, however, and it contains expressions that require the deleted data types, the data types will not be listed as missing precedents.
See also: Deleting Data Types
To construct values for complex system and custom data types in an expression, create a type constructor. Type constructors accept an argument for each data type field and return a value of the specified data type.
Creating a type constructor is similar to passing arguments to functions except the domain is required for the type constructor to evaluate and data type names are case-sensitive.
For example:
If a Person CDT has the following structure:
1
2
3
4
5
6
7
8
Person
|- firstName (Text)
|- lastName (Text)
=type!Person(
firstName: "John",
lastName: "Smith"
)
returns [firstName=John, lastName=Smith]
Entering the namespace of the data type is optional. If the name of the data type is unique, the namespace is looked up when the expression is saved and shown when the expression is viewed again.
For example, enter the following rule and save it:
1
2
3
4
=type!Person(
firstName: "John",
lastName: "Smith"
)
When you view it again, it shows up as the following:
1
2
3
4
='type!{https://cdt.example.com/suite/types/}Person'(
firstName: "John",
lastName: "Smith"
)
If the data type name is not unique, the system prompts you to enter the fully qualified name including namespace when saving the expression. Remember to use single quotes around the full name since the namespace contains special characters.
Tip: Appian recommends using keyword parameters with type constructors as shown in the above example to ease CDT change management.
See above: Passing Arguments
When you save an expression that uses a data type value, and the data type is subsequently deleted, the expression continues to reference the deleted data type.
For example, if the data type Person is deleted, the expression in the examples above will show up as the following:
1
2
3
4
='type!{https://cdt.example.com/suite/types/}Person^1'(
firstName: "John",
lastName: "Smith"
)
The above is not the case, however, for all expressions. Expressions in third-party credentials and the following objects will not change upon type deletion because they always reference the latest version of a type:
See also: Delete Data Types
All arguments are optional in a type constructor. Fields that are not assigned a value are set to null.
For example:
1
2
3
4
=type!PagingInfo(
startIndex: 1,
batchSize: 2
)
returns [startIndex=1, batchSize=2, sort=]
For fields that are themselves a complex data type, type constructors can be used to define their values.
For example:
When a Person CDT has the following structure:
1
2
3
4
5
6
7
8
9
10
Person
|- firstName (Text)
|- lastName (Text)
|- address (Address)
|- street (Text)
|- city (Text)
=type!Person(
firstName: "John"
)
returns [firstName=John, lastName=, address=]
1
=isnull(type!Person(firstName: "John").address)
returns true
1
2
3
4
5
6
7
=type!Person(
firstName: "John",
address: type!Address(
street: "123 Abc St",
city: "Reston"
)
)
returns [firstName=John, lastName=, address=[street=123 Abc St, city=Reston]]
Best Practice: For readability when creating saving it as a rule, enter line breaks after each argument when passing by keyword.
If you enter keywords that don't match any fields in the data type, the keywords are ignored.
Note: Type constructors are not supported when creating rules for the Web Content Channel, process reports, or events. If used, it will cause the process or task to pause by exception.
In order to evaluate expressions faster, Appian automatically evaluates queries in parallel. So instead of waiting for each query to evaluate one after another, independent queries will be evaluated at the same time if there are additional resources available, reducing the overall time it takes to evaluate the expression. This includes database queries, web service calls, custom plugins, and even Appian functions that query data stored within Appian.
Queries are evaluated in parallel in any place where there's no way for one part of the expression to affect another part, such as:
Of course, any parts of the expression that depend on a query will only be evaluated after that query is complete, thus ensuring that the result will be the same regardless of whether the expression is evaluated serially or in parallel.
You may see this reflected in the performance view results for a particular interface. See the performance view page for more information on how to interpret performance results when an interface is evaluated in parallel.
Let's look an example to illustrate how it would be evaluated in parallel. Say you want to get the list of all account managers who don't currently have an active customer account. You may use an expression like the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a!localVariables(
local!customers: {
rule!getAllDomesticCustomers(),
rule!getAllInternationalCustomers()
},
local!accountManagers: rule!getAccountManagersByRegionAndVertical(
regions: rule!getAllRegions(),
verticals: rule!getAllVerticals()
),
local!AMsWithoutAccounts: difference(local!accountManagers, local!customers.manager),
a!forEach(
items: local!AMsWithoutAccounts,
expression: rule!displayUserName(fv!item)
)
)
where rule!displayUserName
calls the user
function, which is considered a query because it retrieves user information from Appian. When every part of this expression is evaluated serially, it looks something like this:
However, when there are resources available to evaluate queries in parallel, the evaluation would look like this:
local!customers
and local!allAccountManagers
evaluate in parallel because independent local variables are evaluated in parallel when they contain a query.rule!getAllDomesticCustomers
and rule!getAllInternationalCustomers
evaluate in parallel because they are separate items in a list and both are queries.local!AMsWithoutAccounts
is not evaluated in parallel because it depends on both local!customers
and local!allAccountManagers
.a!forEach
iterations evaluate in parallel after evaluating local!AMsWithoutAccounts
because rule!displayUserName
calls the user
function, which is considered a query.All of this happens without making any changes to the overall expression; Appian can automatically detect which parts of the expression are independent and evaluate them in parallel to decrease the overall evaluation time.
Appian expressions have the following properties:
Consider the following simple expression:
1
=user(pp!initiator, "firstName") & " " & user(pp!initiator, "lastName")
One might expect that evaluation will proceed from left to right, and thus the user()
function returning the first name will be evaluated first, returning the first name, followed by the literal single space and concluding with the second user()
function returning the last name. However, the expression's value is the same if evaluation proceeds from right to left. It's even the same if the middle happens first and then the two user()
functions are evaluated.
This is only true if expression functions only return values and do not have side effects. A side effect is any other change in the system besides the value a function returns. None of the functions shipped with Appian have side effects. Plug-in developers must ensure their functions conform to this restriction to avoid non-deterministic behavior due to expression evaluation optimizations and compensations.
The following list of cases where functions are not run when one might expect is intended to be illustrative, not exhaustive:
if()
function is used in an expression, the parameter that is not selected is not evaluated.Although configured using functions, smart services work differently. In interfaces and in certain web APIs, smart services are guaranteed to be executed exactly once. In any other expression, they are not executed. This is because executing a smart service's associated function does not by itself execute the smart service. The function merely returns the desired smart service configuration, and that in turn is executed by the framework if and only if it is in the right place (writer functions work the same way).
The advanced evaluation options below apply mainly to creating dynamic behavior in user interfaces. They work, however, anywhere in the system, except analytics reports and process event nodes.
With partial evaluation, you can evaluate arguments in a rule or type constructor and defer complete evaluation of the function until the remaining arguments are available. In computer science publications, this is commonly called partial application or partial function application.
For example, when creating a Tempo report using expressions, you may want to create multiple filters the user can interact with in order to narrow down results in a grid, such as filtering items to those updated between two dates.
In this case, you would set up the expression with two date components (such as startDate and endDate ) each passing its value to a rule or data type that requires multiple arguments. The startDate and endDate inputs would save their values into a query that expects both a start date and an end date to filter by.
Normally, if all required arguments are not passed, the expression would not evaluate and the interface would not render.
In order to have your interface render and later accept arguments to the remaining parameters, you need to set up your function for partial evaluation.
To construct a partial function, create the expression as usual and leave an underscore character for arguments to be evaluated later.
Deferring one argument: index(_, {1, 1, 3}, 0)
Deferring two arguments: index(_, _, 0)
Deferring one argument: a!pagingInfo(startIndex:_, batchSize:10)
Deferring two arguments: a!pagingInfo(startIndex:_, batchSize:_)
Add the arguments to be evaluated later on to the end of the function and enclose them in parentheses as shown in the examples below:
Arguments are applied to the underscores in the order they are listed from left to right.
Partial function that takes one argument:
index(_, {1, 1, 3}, 0)({10, 20, 30})
a!pagingInfo(startIndex: _, batchSize: 10)(1)
Partial function that takes two arguments:
index(_, _, 0)({10, 20, 30}, {1, 1, 3})
a!pagingInfo(startIndex: _, batchSize: _)(1, 10)
Arguments are applied by matching up their keywords. They can only be passed by keyword to partial functions that are defined by keyword.
For example, a!pagingInfo(1, _)(batchSize: 10)
is not allowed.
Partial function that takes one argument: a!pagingInfo(startIndex: _, batchSize: 10)(startIndex: 1)
Partial function that takes two arguments: a!pagingInfo(startIndex: _, batchSize: _)(startIndex: 1, batchSize: 10)
When the blank arguments are filled for the examples above, the functions will evaluate as follows:
Passing Arguments by Position: index({10, 20, 30}, {1, 1, 3}, 0)
Passing Arguments by Keyword: a!pagingInfo(startIndex: 1, batchSize: 10)
Arguments that make up a partial function are evaluated immediately. For example, in concat(now(), _)
, now()
returns the time at which the expression is evaluated to return a partial function.
Functions that take an unlimited number of inputs, such as sum()
, product()
, and difference()
, can also be partially evaluated by using the underscore syntax.
Inputs are applied for each underscore in order, and leftovers are added at the end as shown in the examples below:
1
2
3
4
5
6
7
8
9
sum(1, _)(2, 3, 4) returns sum(1, 2, 3, 4)
sum(1, _)(pv!array) returns sum(1, 2, 3, 4) where pv!array is {2, 3, 4}
sum(_, 2)(1, 3, 4) returns sum(1, 2, 3, 4)
sum(_, 2, _)(1, 3, 4) => sum(1, 2, 3, 4)
myrule(_)(1, 2) returns myrule(1, 2)
Best Practice: Appian recommends including the exact number of underscores when the number of arguments is known even though multiple arguments can be passed to a partial function when it is marked with only one underscore. This helps with the readability and maintainability of the expression.
For example, in the following expressions:
myrule(_, 2, _)(1, 3, 4, 5)
which equates to myrule(1, 2, 3, 4, 5)
myrule(_, 2, _, _, _)(1, 3, 4, 5)
which equates to myrule(1, 2, 3, 4, 5)
Note: Rules created for the Web Content Channel, operators, and the following functions do not support partial evaluation:
a!localVariables()
function, such as a!localVariables(local!a:20, _)
"Hello"=_
For a function recipe that showcases a looping function that uses partial evaluation, see also: Boolean Results
Rules can execute a function, rule, data type, or partial function passed as an argument indirectly by way of a rule input. For example, a rule that formats data for a Grid component can indirectly evaluate a rule input function based on the PagingInfo value passed to the rule each time a user interacts with the grid.
See also: Grid Tutorial
To do this, create a rule with an input of type Any Type and define the rule as =ri!inputName()
where inputName
is the name of your input based on what it will represent. For example gridDataFn
.
You can then pass 0, 1, or more parameters to this rule depending on the number of parameters that the function, rule, or data type entered as the inputName
expects.
For example, if you have a rule called myrule
with the inputs ri!inputName
(Any Type) and ri!input
(Any Type), and it is defined by =ri!inputName(ri!input)
the following expressions evaluate as shown:
myrule( fn!sum, {1, 2, 3})
returns 6
myrule( fn!average, {1, 2, 3})
returns 2
myrule( fn!sum( _, 4), {1, 2, 3})
returns 10
1
2
3
4
5
myrule(
a!pagingInfo(
startIndex: _, batchSize: 10),
1
)
returns [startIndex=1, batchSize=10, sort=]
This functionality is especially useful when defining reusable rules that encapsulate the logic for a interface.
For example, an expression rule is set up to configure a grid component in a UI, and the rows in the grid represent vehicle data. The rule defines the grid columns, label, instructions, etc., and takes as its input a reference to the source of the data. This data will sometimes come from process variables or from an external database through queries. The rule that returns the grid component with vehicle data will then be called as follows:
If the data comes from process variables:
1
=showVehicleGrid( todatasubset( pv!vehicles, _))
If the data comes from a query:
1
=showVehicleGrid( getVehiclesForClient( pv!clientId, _))
If the data comes from a rule that calls a query:
1
=showVehicleGrid( determineVehiclesToShow( _, pv!someInput1, pv!someInput2))
In the above examples, the rule showVehicleGrid
takes a partial function that expects one parameter. showVehicleGrid then applies a PagingInfo value to the partial function to return one page of data in a data subset.
Note: Rules created for the Web Content Channel do not support indirect evaluation.
In order for an expression to execute and logic to evaluate when the system attempts to derive information at runtime, the initiator must have sufficient user rights.
For example, if an expression is initiated by a user who does not have sufficient rights to access a resource requested by the expression, it will encounter an exception that halts the evaluation.
When creating an expression for a process model, the designer or task owner may require the rights instead.
See also: User Contexts for Expressions in Process
The validate button in the Expression Editor only checks for the following:
To test the logic of your expressions, create it through the expression rule designer.
See also: Testing an Expression Rule
Expressions