Functions¶
Functions are defined in the [functions] section of a .laceext file and called from rule bodies and other functions.
Defining functions¶
[functions.resolve_scope_notif]
params = ["notif_cfg", "actual", "expected", "op"]
body = """
when is_null(notif_cfg)
return null
when is_concrete(notif_cfg)
return notif_cfg
return map_match(notif_ops(notif_cfg), actual, expected, op)
"""
params-- list of parameter names. Parameters are bound as bare identifiers and read like hook context fields:notif_cfg, not$notif_cfg.$-prefixed names are reserved forlet/forbindings.body-- the function body as a multi-line string using the same rule language.exposed-- optional,truemakes the function callable from other extensions (see below).
Function rules¶
return exprexits the function and produces a value.- A function that reaches the end without
returnreturnsnull. exitis not valid in functions -- usereturn nullfor early termination.set $name = expris valid in functions for reassigning bindings (not available in rule bodies).- Functions may call other functions in the same extension, built-in primitives and tag constructors.
- A call on its own line is a statement -- the function runs for its side effects and the return value is discarded.
- No recursion. The call graph must be a DAG.
- Functions are pure by default -- they cannot
emitor accessresultdirectly. Exception: exposed functions (see below).
Local vs exposed functions¶
By default, functions are local to the extension. Add exposed = true to make a function callable from other extensions:
[functions.pushNotification]
params = ["event"]
exposed = true
body = """
emit result.actions.notifications <- {
callIndex: event.callIndex,
conditionIndex: event.conditionIndex,
trigger: event.trigger,
scope: event.scope,
notification: event.notification
}
return event
"""
Calling exposed functions¶
Other extensions that declare the owning extension in their require can call the function using qualified syntax:
laceNotifications.pushNotification({
callIndex: call_index,
conditionIndex: -1,
trigger: "baseline_spike",
scope: metric_name,
notification: structured({
metric: metric_name,
actual: actual,
average: $avg,
threshold: $threshold,
multiplier: multiplier
})
})
(from laceBaseline's exposed check_spike function, whose parameters are stats, metric_name, actual, call_index and multiplier)
The format is extensionName.functionName(args).
Exposed function execution model¶
The exposed function executes inside the owning extension's interpreter:
emitattributes to the owner -- entries go into the owner's declared result paths.result.runVarsemits use the owner's namespace prefix, not the caller's.- The owner's
requireview, primitives, tag constructors and internal functions are in scope, not the caller's. - Exposed functions still accept and return values like any other function.
Restrictions¶
- Calling a non-exposed function from another extension raises
is not an exposed function. - The caller must list the owning extension in its
require. Omitting it is a runtime error when the call fires. - Cross-extension recursion is forbidden --
extA.foocannot callextB.barthat calls back intoextA.foo.
Tag constructors¶
Every one_of variant declared in an extension's [types] section is callable by its tag -- from .lace option values and from rule bodies. The call takes the variant's fields as positional arguments and produces { tag: "<tag>", <field>: ... }. With laceNotifications loaded:
| Constructor | Produces |
|---|---|
text("Service down") |
{ tag: "text", value: "Service down" } |
template("high-severity") |
{ tag: "template", name: "high-severity" } |
structured({ error: call.error }) |
{ tag: "structured", data: { error: ... } } |
op_map({ "gt": text("slow"), "default": text("?") }) |
{ tag: "op_map", ops: { ... } } |
Primitives¶
Built-in functions provided by the executor. Available in all rule bodies and functions. All implementations provide these identically.
compare(a, b) -> string | null¶
Returns the op key describing the actual relationship between a and b:
| Condition | Returns |
|---|---|
a lt b |
"lt" |
a eq b |
"eq" |
a gt b |
"gt" |
a neq b for booleans (not ordered) |
"neq" |
Either is null |
null |
| Incomparable or mismatched types (e.g. int vs string, bool vs int) | null |
compare describes one relationship, so it never returns "lte" or "gte" -- an op_map keyed on those never matches. For numbers and strings (lexicographic) the result is "lt", "eq" or "gt"; for booleans "eq" or "neq".
map_get(map, key) -> any | null¶
Looks up key in map. Falls back to map["default"] if key is absent. Returns null if neither exists or if map is null.
map_get({ "lt": "below", "default": "other" }, "gt") # "other"
map_get({ "lt": "below" }, "gt") # null
map_get({ "eq": "match" }, "eq") # "match"
map_match(map, actual, expected, op) -> any | null¶
Resolves the best matching key in a map for a validation failure. The op argument is accepted for call-site symmetry with the scope context but is not consulted. Tries in order:
- The string representation of
actualas a key (literal match, e.g."404") - The result of
compare(actual, expected)as a key (op key match) "default"as a key
Returns null if no key matches or if map is null.
map_match({"404": t1, "gt": t2, "default": t3}, 404, 200, "eq")
# t1 -- actual "404" matches literal key
map_match({"lt": t2, "default": t3}, 1200, 500, "lt")
# t3 -- compare(1200, 500) = "gt", no "gt" key, falls to default
map_match({"lt": t4}, 100, 500, "lt")
# t4 -- compare(100, 500) = "lt", matches
is_null(v) -> bool¶
Returns true if v is null, false otherwise.
type_of(v) -> string¶
Returns the type name of v:
| Value | Returns |
|---|---|
| String | "string" |
| Integer | "int" |
| Float | "float" |
| Boolean | "bool" |
| Object/map | "object" |
| Array | "array" |
| Null | "null" |
to_string(v) -> string¶
Converts any value to its string representation. Null returns "null", booleans return "true" / "false", numbers return their decimal form, strings return themselves.
replace(str, pattern, replacement) -> string | null¶
Returns a copy of str with all occurrences of pattern replaced by replacement. The replacement is converted via to_string() before substitution. If str or pattern is null, returns str unchanged (so null for a null str).