Functions (Lambda)¶
A Fn is a user-defined lambda function (type code 100, RAY_LAMBDA). It
wraps a compiled function object built from Rayfall source — for example
(fn [x] (* x x)) — and can be applied either directly or inside a
query.
Construction¶
Compile a lambda from its Rayfall source with Fn::new. The source must be a
(fn …) expression:
Passing anything that is not a (fn …) expression is an error:
Direct call¶
call applies the lambda and evaluates immediately. It works on a scalar…
let square = Fn::new("(fn [x] (* x x))")?;
assert_eq!(square.call(&[Value::i64(5)])?.as_i64()?, 25);
…or element-wise over a vector:
let square = Fn::new("(fn [x] (* x x))")?;
let out = square.call(&[Value::vec(&[2i64, 3, 4])])?;
assert_eq!(out.as_slice::<i64>()?, &[4, 9, 16]);
Multiple arguments are passed in order:
let add = Fn::new("(fn [x y] (+ x y))")?;
assert_eq!(add.call(&[Value::i64(10), Value::i64(32)])?.as_i64()?, 42);
Applying inside a query¶
apply turns a lambda into an Expr, so it can
feed a projection, a predicate, or an aggregation. It returns a Result because
the lambda is bound into the global environment on first use (the query DAG
compiler resolves it by name):
use rayforce::{col, Fn, Table, Value};
let t = Table::new(
&["id", "value"],
&[Value::sym_vec(&["a", "b", "c"]), Value::vec(&[2i64, 3, 4])],
)?;
let square = Fn::new("(fn [x] (* x x))")?;
let out = t
.select()
.col("id")
.agg("squared", square.apply([col("value")])?)
.execute()?;
assert_eq!(out.column("squared")?.as_slice::<i64>()?, &[4, 9, 16]);
Because apply yields a plain Expr, you can wrap it like any other
expression — for instance in an aggregate:
use rayforce::{col, sum, Fn, Table, Value};
let t = Table::new(&["value"], &[Value::vec(&[2i64, 3, 4])])?;
let square = Fn::new("(fn [x] (* x x))")?;
let out = t
.select()
.agg("sum_sq", sum(square.apply([col("value")])?))
.execute()?;
assert_eq!(out.column("sum_sq")?.get(0)?.as_i64()?, 29); // 4 + 9 + 16
Lambdas from a loaded file¶
A lambda defined in a Rayfall file and bound by name — e.g. after
(load "prelude.rfl") — is fetched with Fn::from_global, not Fn::new
(new compiles source; from_global looks up an existing binding):
use rayforce::{eval, Fn, Value};
eval("(load \"prelude.rfl\")")?; // defines a `sq` lambda in the global env
let sq = Fn::from_global("sq")?;
assert_eq!(sq.call(&[Value::i64(9)])?.as_i64()?, 81);
from_global also reuses the existing name when the lambda is later used in a
query via apply, so no extra binding is generated.
new vs from_global vs from_value
Fn::new("(fn …)")— compile source into a new lambda.Fn::from_global("name")— take a lambda already bound in the env (including anything pulled in by(load …)).Fn::from_value(v)— wrap an existing lambdaValue, e.g. one returned byeval.
Introspection¶
source returns the original text (when built with Fn::new), and a Fn
renders as that source rather than the engine's opaque lambda: