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:

pricing.blueprint
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.

The export
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.

Parameter and return forms
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:

Three returns, three exits
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:

A reserved word as a return name
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:

A misspelled argument
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:

access-checks.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:

orders.blueprint
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 as billing.add-vat keeps 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 fn line.
  • 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.