DRAFTThis page is not published. Only visible in development mode.

Closures & Functions

RQL scripts can define functions two ways: anonymous closures bound to variables (let $double = ($x) { $x * 2 }) and named functions declared with udf. Both turn a repeated computation into one definition, both live only for the request that defines them, and they differ in exactly one practical way: where they can be called.

Define a closure, call it

A closure is a parenthesized parameter list followed by a block, assigned to a variable with let. Calling the variable runs the body; when the call is the final statement of a request, its result comes back as a one-value frame:

$ Define a Closure, Call It
$ ctrl+enter to run

Closures capture their scope

The body can reference variables from the enclosing scope - that is what makes it a closure rather than just a function. Here $base is not a parameter; it is captured:

$ Closures Capture Their Scope
$ ctrl+enter to run

Parameters win over captures of the same name. Inside the body, $x is the argument, not the outer variable:

$ A Parameter Shadows a Captured Variable
$ ctrl+enter to run

Bodies are blocks

A closure body is a full block: it can declare its own variables, and its last expression is the return value:

$ Bodies Are Blocks
$ ctrl+enter to run

Closures compose - a closure can call another closure it captured:

$ Closures Call Closures
$ ctrl+enter to run

Using closures in queries

To feed a closure's result into a pipeline, bind it with let and use the variable:

$ Bind the Result, Use It in a Query
$ ctrl+enter to run

What does not work is calling the closure variable inline inside a pipeline expression - map and filter do not resolve $f(4) as a callable:

$ Closure Variables Cannot Be Called Inline
$ ctrl+enter to run

Named functions with udf

When the whole point is to call the function from inside a pipeline, declare it with udf instead. A named udf is callable anywhere a built-in function is:

$ Named Functions Work Inline
$ ctrl+enter to run

udf bodies use explicit return, and return exits immediately - combined with if that gives guard-clause style logic:

$ return Exits Early
$ ctrl+enter to run

A udf returning a boolean works as a reusable predicate:

$ Use a udf as a Predicate
$ ctrl+enter to run

And a udf can wrap statements, not just expressions - including writes. This is the pattern for "do this parameterized thing N times" inside one request:

$ Wrap a Write in a udf
$ ctrl+enter to run

One request, then gone

Closures and udfs are script constructs, not schema objects. They exist from their definition to the end of the request - the next request starts clean:

$ Functions Live for One Request
$ ctrl+enter to run
Need a function that persists?
A udf that several requests or several clients should share is a stored procedure: declared with create procedure, stored in a namespace, and invoked with call. Reach for closures and udfs to structure the logic inside one script; reach for procedures to give that logic a permanent name in the database.