Schemas and Diagnostics Reading Schemas Lesson 11
Value kinds: shapes, vectors, units, bounds
Mental Model
The phrase “value kind” appears in two related places:
- The parser sees base value kinds:
number,string,symbol,vector,form, and so on. - A plugin can give a name to a narrower contract:
length,point,fill-rule,shape-form,duration,note-or-event.
The named kind is not new syntax. If a slot is typed point, you do not
write point(160 120) or :point [160 120]. You write the value whose
shape satisfies the kind:
(circle :center [160 120])
Read a named kind in this order:
- Underlying shape - should the value be a number, string, symbol, vector, form, or union?
- Refinement - does the kind add a vector length, unit rule, closed member list, allowed head list, or list of alternatives?
- Surface value - what do you actually type in the document?
You do not define value kinds while authoring a document. You read the plugin docs and write values that satisfy them.
Cross-references are also implemented as value-kind refinements, but they deserve their own authoring model. Chapter 13 covers them.
Worked Example
The reference shapes plugin can be summarized like this:
(circle ...)
:center point optional
:radius length optional
:fill fill-rule optional
(badge ...)
:label string optional
:shape shape-form optional
length: number
point: vector, length 2, element number
fill-rule: symbol, members evenodd | nonzero
shape-form: form, heads circle | rect
Now write the source from the contract:
(circle :center [160 120] :radius 32 :fill evenodd)
(badge :label "dot"
:shape (circle :center [0 0] :radius 1))
Read the first form slowly:
:center pointmeans “write a vector with two numeric elements.”:radius lengthmeans “write a number.”:fill fill-rulemeans “write one of the listed symbols.”
Read the second form:
:label stringmeans “write quoted text.”:shape shape-formmeans “write a nested form, but only with an allowed head.”
The important habit: start from the slot line, then jump to the named kind line. The slot tells you which kind applies; the kind tells you which value shape is accepted.
Underlying Shape First
Before worrying about refinements, make the broad shape match.
(circle :center "160,120" :radius 32)
This fails before the validator even cares about length 2. point is
vector-underlying, so a string is the wrong underlying shape. Repair by
using brackets:
(circle :center [160 120] :radius 32)
Likewise:
(circle :center [160 120] :radius "32")
:radius is length, and length is number-underlying. A string
produces wrong_underlying. Repair with a number:
(circle :center [160 120] :radius 32)
Vector Shapes
A vector refinement usually answers two questions:
- How many elements?
- What kind should each element have?
For point:
point: vector, length 2, element number
That accepts:
[0 0]
[160 120]
[1.5 -2]
It rejects a vector with too few or too many elements:
(circle :center [160] :radius 32)
Likely diagnostic: vector_length_mismatch. Repair by writing exactly
two elements:
(circle :center [160 120] :radius 32)
It also rejects a vector whose element has the wrong kind:
(circle :center [160 "top"] :radius 32)
The second element is a string, but point needs numbers. Repair the
element:
(circle :center [160 120] :radius 32)
Nested vectors use the same idea. If a plugin documents
points: vector, element point, then the outer value is a vector and
each element must satisfy point:
(shape :points [[0 0] [1 0] [1 1]])
This is not the same shape:
(shape :points [0 0 1 0 1 1])
That is one flat vector of numbers, not a vector of points.
Unit Shapes
A unit refinement applies to a number-underlying kind. The plugin may allow unitless numbers, require a unit, or allow only specific suffixes.
Example contract:
duration: number, unit required, allowed s | ms | b
Accepted values:
0.5s
250ms
4b
Rejected because the unit is missing:
(delay :wait 4)
Likely diagnostic: unit_required. Repair with an allowed suffix:
(delay :wait 4b)
Rejected because the suffix is not allowed:
(delay :wait 90deg)
Likely diagnostic: unit_not_allowed. Repair by using one of the
suffixes in the kind contract:
(delay :wait 250ms)
Remember: SJON preserves unit suffixes but does not interpret them.
The plugin decides whether b, ms, or s means anything useful.
Numeric Bounds
A numeric bound refinement applies to a number-underlying kind and constrains the value’s magnitude or integrality, orthogonal to any unit shape. The plugin may pin a minimum, maximum, either-exclusive, or require integer values.
Example contracts:
opacity: number, range [0, 1]
iteration-count: number, min 1, integer
duration-ms: number, unit required ms, range [0ms, 10000ms]
Accepted values for opacity:
0
0.5
1
Rejected because below :min:
(layer :opacity -0.1)
Likely diagnostic: number_below_min. Other diagnostics in this
family:
number_above_max— value greater than:max.number_at_or_below_exclusive_min—:exclusive-min trueand value ≤:min.number_at_or_above_exclusive_max—:exclusive-max trueand value ≥:max.number_not_integer—:integer trueand value is fractional or non-finite.numeric_bound_unit_mismatch— bound carries a unit but the value either has none or carries a different unit.
Comparison preserves exact precision when both the bound and the
value came from integer literals (9007199254740993 vs a :max
of 9007199254740992 correctly fires number_above_max, even
though both round to the same f64). For everyday plugins this
just works; the corner only matters when the bound itself
approaches 2^53.
Exercises
For each exercise, read the contract first, then repair the source.
Vector Shape
Contract:
point: vector, length 2, element number
(circle ...)
:center point optional
(circle :center [160] :radius 32)
Repair:
(circle :center [160 120] :radius 32)
Vector Element Kind
(circle :center [160 "top"] :radius 32)
Repair:
(circle :center [160 120] :radius 32)
Unit Shape
Contract:
duration: number, unit required, allowed s | ms | b
(delay ...)
:wait duration required
(delay :wait 4)
Repair:
(delay :wait 4b)
Mastery Check
-
Is a plugin-declared value kind a new SJON syntax feature?
-
When reading a named kind, what should you check first?
-
Which diagnostic points to a missing required unit suffix?
-
Which diagnostic points to a vector with the wrong number of elements?