Macros
A macro gives a title to reusable expression syntax. Expansion replaces a call with its template before compilation. The expanded expression follows the expression type and cost rules.
{:#_ada {:name "Ada"
:role "engineer"
:age 36}
:#_bob {:name "Bob"
:role "engineer"
:age 41
:mentor {:#link "#_ada"}}
:#_cy {:name "Cy"
:role "designer"
:age 29}}Define a macro
A macro document has ordered params and an expands template. An optional
description explains its purpose. A stored definition has a title. The native macro builder also accepts an authored definition without saving it.
{:description "Multiply an amount by a rate."
:params ["amount" "rate"]
:expands ["*" {:insert "amount"} {:insert "rate"}]}Parameter names are plain text. Each name must differ from the other names. A name cannot be empty or contain whitespace. A macro cannot have a built-in operator's title. Definition validation rejects unknown fields, missing required fields and invalid markers.
Call a macro
A call has the title first, then one argument for each declared parameter.
["pricing/apply-rate" ?subtotal 0.2]- subtotal
- 100.0
{:value 20.0}Expansion produces [* ?subtotal 0.2]. The variable remains a caller input.
Expansion rejects an invalid argument count.
["pricing/apply-rate" 100.0]Error
Invalid_Call
Insert and spread
An insertion marker is a map with one member. {insert name} replaces the
marker with one argument tree. {spread name} inserts each item of a list
argument into the surrounding list.
{:params ["values"]
:expands ["+" {:spread "values"}]}["sum-all" [1 2 3 4]]{:value 10}The expansion is [+ 1 2 3 4]. A spread marker needs a surrounding list and a
list argument. An insertion preserves a list or map as one argument tree.
Compose macros
A template can call another macro. Expansion resolves nested calls before the compiler validates operators and types. Expansion rejects direct and indirect cycles.
{:params ["value" "floor"]
:expands [">=" {:insert "value"} {:insert "floor"}]}{:params ["value"]
:expands ["and" ["at-least" {:insert "value"} 18] ["<" {:insert "value"} 65]]}["working-age" 36]{:value true}quote stops macro expansion inside its values. The expression evaluator still
rejects quote. Syntax preserved during expansion is not necessarily executable.
Use macros in queries
A query expands macros in expression positions, including predicates, derivations, computed columns, grouping filters, ordering and projection. Ordinary fact values remain data.
{:find [?name]
:where [
[?person "age" ?age]
[?person "name" ?name]
["working-age" ?age]]
:orderBy [?name]}{:value [{:name "Ada"} {:name "Bob"} {:name "Cy"}]}A mutation uses the same expansion rules in its selection query.
Current and pinned definitions
An unpinned call uses the current title mapping. Each accepted update records a mapping revision. A pin names that mapping revision, rather than the macro entity or its definition revision.
This update changes working-age to require an age of at least 40.
{:params ["value"]
:expands ["and" [">=" {:insert "value"} 40] ["<" {:insert "value"} 65]]}["working-age" 36]{:value false}The first recorded mapping revision of working-age is 13 in this fresh
fixture. Read the actual revision from the saved definition in your database.
A pinned call keeps that earlier rule.
[
{:title "working-age"
:mappingRevision 13}
36]{:value true}A query can pin all calls of a title with its definitions member. Expansion
consumes that metadata before query compilation.
{:definitions {:working-age 13}
:find [?name]
:where [
[?person "age" ?age]
[?person "name" ?name]
["working-age" ?age]]
:orderBy [?name]}{:value [{:name "Ada"} {:name "Bob"} {:name "Cy"}]}Expansion rejects an unknown mapping revision. It does not select the current rule.