Skip to content

How to make fields agree with each other ​

Random-per-field data produces nonsense: an email that has nothing to do with the name, a total that doesn't equal price plus tax, a city in the wrong country. The fix is to draw a value once, name it with a let, and derive everything else from that name. Because a let binds a name to a single value, referencing it again always yields the same value — coherence is a property of the let, not luck.

Steps ​

  1. In a composition's let block, draw each independent value once and give it a name.
  2. Compute the dependent values from those names — either with a { "computed": "<PEL>" } let step, or directly in the output.
  3. Emit an output object whose leaves reference the names. Every leaf is a PEL expression evaluated against the same bindings, so they always agree.

Example 1 — an email that matches the name ​

json
{
  "name": "@demo/coherent",
  "version": "0.1.0",
  "generators": [
    { "name": "@demo/coherent:person",
      "let": {
        "first": { "type": "list", "source": "inline", "values": ["Ada","Grace","Alan","Linus"] },
        "last":  { "type": "list", "source": "inline", "values": ["Lovelace","Hopper","Turing","Torvalds"] }
      },
      "output": {
        "name":  "{{ concat(first, ' ', last) }}",
        "email": "{{ concat(lowercase(first), '.', lowercase(last), '@example.com') }}"
      } }
  ]
}
bash
phony generate --use @demo/coherent:person --package ./coherent --seed 42 -n 3
json
[
  { "email": "grace.lovelace@example.com", "name": "Grace Lovelace" },
  { "email": "grace.hopper@example.com",   "name": "Grace Hopper" },
  { "email": "linus.hopper@example.com",   "name": "Linus Hopper" }
]

first and last are each drawn once; both name and email read those same two values, so the email can never disagree with the name.

Note that + is arithmetic-only in PEL — build strings with concat(…), never first + ' ' + last.

Example 2 — a total that matches its price and tax ​

Compute derived numbers with computed let steps (evaluated in dependency order) so each builds on the last:

json
{ "name": "@demo/coherent:invoice_line",
  "let": {
    "price": { "type": "logic", "algorithm": "float_between", "params": { "min": 10, "max": 100 } },
    "tax":   { "computed": "round(price * 0.2, 2)" }
  },
  "output": {
    "price": "{{ round(price, 2) }}",
    "tax":   "{{ tax }}",
    "total": "{{ round(price + tax, 2) }}"
  } }
bash
phony generate --use @demo/coherent:invoice_line --package ./coherent --seed 42 -n 3
json
[
  { "price": 52.44, "tax": 10.49, "total": 62.93 },
  { "price": 97.27, "tax": 19.45, "total": 116.72 },
  { "price": 11.61, "tax": 2.32,  "total": 13.93 }
]

tax and total are both derived from the one price draw, so the arithmetic always closes.

Example 3 — a city/district/postal that never mismatch ​

When several fields must come from the same real-world record, don't draw them separately — draw one object and pick its properties apart. A list over an array of objects returns one whole record:

json
{ "name": "@demo/coherent:address",
  "let": {
    "loc": { "type": "list", "source": "inline", "values": [
      { "city": "İstanbul", "district": "Kadıköy", "postal": "34710" },
      { "city": "Ankara",   "district": "Çankaya", "postal": "06420" },
      { "city": "İzmir",    "district": "Konak",   "postal": "35250" }
    ] }
  },
  "output": {
    "city":     "{{ loc.city }}",
    "district": "{{ loc.district }}",
    "postal":   "{{ loc.postal }}"
  } }
bash
phony generate --use @demo/coherent:address --package ./coherent --seed 42 -n 3
json
[
  { "city": "İzmir",    "district": "Konak",   "postal": "35250" },
  { "city": "İzmir",    "district": "Konak",   "postal": "35250" },
  { "city": "İstanbul", "district": "Kadıköy", "postal": "34710" }
]

You never get "Ankara, Konak, 34710" — the three fields are properties of a single picked record. This is the coherent-record pattern; the same shape works for a locale-resolved asset of objects (see Serve locale-specific data).

The rule of thumb ​

  • Independent value? Draw it in a let, name it.
  • Derived value? Compute it from names — with a computed let or an output leaf.
  • Fields from the same record? Draw the whole record once (a list of objects) and read its properties; don't draw the fields separately.

Pitfalls ​

  • A reference resolves against the generator's own bindings only — its params and let results. There is no row, table, or sibling field to reach for; if you want two fields to agree, they must both derive from a shared let.
  • A whole-expression leaf keeps its type. A leaf that is exactly one {{ … }} (like "{{ tax }}") yields the underlying number/array; a leaf with surrounding text renders to a string.
  • Unknown references fail at load time, naming the bad reference — so a typo like {{ frist }} never reaches generation.

See also ​

Phony Cloud — Documentation & Specification