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.

edn237 B
{:#_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.

edn134 B
{: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.

edn36 B
["pricing/apply-rate" ?subtotal 0.2]
Result / 13 B
subtotal
100.0
{:value 20.0}

Expansion produces [* ?subtotal 0.2]. The variable remains a caller input. Expansion rejects an invalid argument count.

edn28 B
["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.

edn56 B
{:params  ["values"]
 :expands ["+" {:spread "values"}]}
edn21 B
["sum-all" [1 2 3 4]]
Result / 11 B
{: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.

edn81 B
{:params  ["value" "floor"]
 :expands [">=" {:insert "value"} {:insert "floor"}]}
edn99 B
{:params  ["value"]
 :expands ["and" ["at-least" {:insert "value"} 18] ["<" {:insert "value"} 65]]}
edn18 B
["working-age" 36]
Result / 13 B
{: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.

edn123 B
{:find    [?name]
 :where   [
   [?person "age" ?age]
   [?person "name" ?name]
   ["working-age" ?age]]
 :orderBy [?name]}
Result / 51 B
{: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.

edn93 B
{:params  ["value"]
 :expands ["and" [">=" {:insert "value"} 40] ["<" {:insert "value"} 65]]}
edn18 B
["working-age" 36]
Result / 14 B
{: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.

edn65 B
[
  {:title           "working-age"
   :mappingRevision 13}
  36]
Result / 13 B
{:value true}

A query can pin all calls of a title with its definitions member. Expansion consumes that metadata before query compilation.

edn167 B
{:definitions {:working-age 13}
 :find        [?name]
 :where       [
   [?person "age" ?age]
   [?person "name" ?name]
   ["working-age" ?age]]
 :orderBy     [?name]}
Result / 51 B
{:value [{:name "Ada"} {:name "Bob"} {:name "Cy"}]}

Expansion rejects an unknown mapping revision. It does not select the current rule.

Run it

build macro · expand macro.