Foundations Syntax and Atoms Lesson 05

Forms and keyword pairing

Mental Model

A form has a head and an ordered list of children:

(camera
  :ortho
  :zoom 2
  :pos [0 1])

Open in playground →

The children are:

  • :ortho - a positional flag.
  • :zoom 2 - a kvpair.
  • :pos [0 1] - a kvpair.

The key rule:

:key value

Open in playground →

pairs only when value is a non-keyword value. A keyword can never be the value of a kvpair.

This means:

(stack :mode :mask)

Open in playground →

does not mean mode = :mask. It means two positional flags: :mode and :mask.

Two flags side by side isn’t always a mistake. When both keywords are meant as independent toggles, that same parse is exactly the intent:

(text "Hello" :bold :italic)

Open in playground →

Children: :bold flag, :italic flag. Both styles apply. The gotcha bites only when one of those keywords was meant to be a value.

That separation is deliberate. Adjacent flags should remain unambiguous: if one keyword could become another keyword’s kvpair value, the two flags in (text "Hello" :bold :italic) would turn into bold = :italic. When a slot needs a value instead, use a symbol, string, or container shape that the schema documents.

Worked Example

Broken:

(camera :projection :ortho)

Open in playground →

What the parser sees:

  • :projection cannot pair with :ortho.
  • :projection is committed as a positional flag.
  • :ortho is also a positional flag when the form closes.

Repair when the schema expects a symbol:

(camera :projection ortho)

Open in playground →

Repair when the schema expects free-form text:

(camera :projection "ortho")

Open in playground →

Repair when you truly need keywords as values by wrapping them in a vector:

(stack :modes [:mask])

Open in playground →

Inside the vector, :mask is a keyword value because vectors do not have kvpairs.

Exercises

Predict the children before reading the repairs:

(stack :mode :overlay)

Open in playground →

Children: :mode flag, :overlay flag. Repair as a symbol enum:

(stack :mode overlay)

Open in playground →

(camera :zoom 2 :ortho)

Open in playground →

Children: kvpair :zoom 2, then trailing flag :ortho. No repair is needed if :ortho is meant to be a flag.

(circle :center [0 0] :radius 1 :fill :evenodd)

Open in playground →

If :fill expects a symbol member set, repair with a symbol:

(circle :center [0 0] :radius 1 :fill evenodd)

Open in playground →

Repair drill:

(badge :label "ok" :shape :circle)

Open in playground →

If :shape expects a form slot pinned to (circle ...) or (rect ...), repair it with a form value:

(badge :label "ok" :shape (circle :center [0 0] :radius 1))

Open in playground →

Kvpairs in expression heads

Data forms pair keys to values; expression heads ((lerp …), (vec3 …)) take positional arguments by default. Some expression functions opt into a labeled call form so kvpairs work there too — see Safe Expressions for the contract.

Mastery Check

  1. What does (stack :mode :mask) parse as?

  2. How do you write an enum-like value in a kvpair?

  3. Where can a keyword safely be used as a value?

  4. Why do forms with no positional children expose keyword-pairing mistakes quickly?