Functions and Custom Pins
A function is a flow that other flows call by name, with named parameters and returns. This page shows how to declare one in Blueprint Language, how to call it, also from another blueprint, and how its parameters and returns appear on the canvas as custom pins.
A function and a call
add-vat takes one number and returns one; quote is a route handler that reads amount from the request body, calls add-vat and replies with its total:
blueprint "Pricing"
fn add-vat(arg amount: number) -> (total: number) {
function_return(total: number_times(amount, 1.21))
}
fn quote(connection, body) {
let {amount} = body
priced = call add-vat(amount: amount)
reply 200 {total: priced.total}
}| Source | On the canvas |
|---|---|
fn add-vat(...) |
A trigger_custom_function named add-vat; the body runs from its flow output. |
arg amount: number |
An argument: an input amount on every call, and an output amount on the function's first block that the body reads (here into number_times). |
-> (total: number) |
A return: an input total on every function_return in the body, and an output total on every call. |
fn quote(connection, body) |
Context parameters: the values a route hands its handler. |
priced = call add-vat(amount: amount) |
A function_custom_execute that runs add-vat, with its amount input wired to amount. |
priced.total |
The call's total output. |
Once stored, an export prints the same two functions with its own names: a call is named after the function it calls, and a field that is read once is written inline as body.amount.
blueprint "Pricing"
fn add-vat(arg amount: number) -> (total: number) {
function_return(total: number_times(amount, 1.21))
}
fn quote(connection, body) {
add_vat = call add-vat(amount: body.amount)
reply 200 {total: add_vat.total}
}Declaring a function
A function is fn name(parameters) -> (returns) { body }. The returns are optional, and so is pub in front: it marks a function public, for functions that other blueprints call.
Parameters
| Kind | Written as | What it is |
|---|---|---|
| Context parameter | connection, params, query, body, file |
A value the starter hands the function: a route passes connection, params, query and body, and a file route also file. |
| Argument | arg amount: number |
A value each caller passes by name. |
| Optional argument | arg limit?: number |
An argument a caller may leave out. |
A type is a pin type: value, number, condition, object, array, date and so on. Arguments and returns are value unless you say otherwise, and context parameters are object, except connection (httpconnection), file (file) and index (number). Write a type only where it differs; an export does the same.
fn find-orders(connection, arg customer_guid, arg limit?: number) -> (orders: array, count?: number) {
…
}Returns
-> (total: number) declares what the function returns, and function_return(total: ...) in the body sets the values the caller receives. A trailing ? marks a return optional. A function with several exits has a function_return on each of them:
fn score(arg a: number, arg b: number) -> (ok: condition, total: number, label) {
let sum = number_plus(a, b)
guard sum > 10 else {
function_return(ok: false, total: sum, label: "LOW")
}
guard number_times(a, b) < 1000 else {
function_return(ok: false, total: sum, label: "HUGE")
}
function_return(ok: true, total: number_times(sum, 2), label: "HIGH")
}Make every path reach a function_return: a path that ends without one gives the caller no returned values. A return whose name is a reserved word, such as true, is written in backticks, both in the declaration and in function_return:
let currentdate = now()
fn is-this-year() -> (`true`: condition) {
function_return(`true`: condition_date_equals_this_year(currentdate))
}Calling a function
x = call name(param: expression, ...) runs the function, passes each argument by name and names the call, so x.total reads the return called total. A call waits until the function has finished before the flow goes on; to run a function in the background instead, put it on the queue.
When the function is declared in the same file, a call's arguments are checked against its parameters. Here the argument on line 9 of pricing.blueprint is misspelled:
9:25: call add-vat has no parameter "amout"; did you mean amount?Calling a function in another blueprint
A call names the function it runs, and the function may be declared in another blueprint. Mark it pub in its own blueprint:
blueprint "Access checks"
pub fn is-staff-request(connection) -> (valid: condition) {
…
}and call it by that name, exactly as you would call a function in the same file:
fn create-order(connection, params, query, body) {
is_staff_request = call is-staff-request(connection: connection)
guard is_staff_request.valid else {
reply 403
}
…- The name is the link. Function names may contain
-and., so a namespace such asbilling.add-vatkeeps many functions apart (see namespaces). - Check the arguments yourself. A compile cannot see the declaration of a function in another blueprint, so it does not check a call's argument names against it. Export that blueprint to read its
fnline. - An unknown name is a warning. A call whose name matches no function is reported at its line, and has nothing to run.
Custom pins: the signature on the canvas
Most blocks have a fixed set of pins. A function's blocks do not: their pins come from the function's parameters and returns. These are its custom pins, and in source you write them once, on the fn line.
| Block | Custom pins it shows |
|---|---|
The function's trigger_custom_function |
An output per parameter, which the body reads like any other output. |
Every function_return in the body |
An input per return. |
Every call, function_custom_execute |
An input per argument and an output per return. |
A call passes and reads these pins by name. When you rename a parameter or a return, update the calls that pass or read it as well, in this blueprint and in the blueprints that call the function.
Blueprint Language Syntax The rest of the language: statements, expressions, block and storage calls, the lock, positions and checks. Best Practices When to split logic into functions, and how to share them between blueprints.Frequently asked
How do I declare a function with parameters and returns in Blueprint Language?
Write fn name(arg amount: number, arg limit?: number) -> (total: number) { ... } and end every path with function_return(total: ...). arg marks a value callers pass by name, a trailing ? makes it optional, and types default to value. A route handler takes the context parameters connection, params, query and body instead.
How do I call a function in another blueprint in RUAL?
Mark the function pub in its own blueprint, then call it by name, for example x = call is-staff-request(connection: connection), and read its returns as x.valid. A compile cannot check the arguments of a function declared in another blueprint, so match them to its fn line.
Does a RUAL function call wait for the function to finish?
Yes. A call runs the function and waits for it before the flow continues, then sets the returned values on its outputs. To run a function without waiting, put it on the queue with function_custom_execute_from_queue.