04. Control Flow, `Cells`, and Records
Frothy grows by composing a small set of control forms rather than by adding many special cases.
Conditionals
Use if, when, and unless with explicit booleans. Frothy does not use
general truthiness.
if adc.percent: A0 > 50 [
led.on:
] else [
led.off:
]
If an if has no else and the condition is false, the expression yields
nil.
Here is the same control style in a shape you can reuse:
to classify with percent [
cond [
when percent < 20 [ "low" ];
when percent < 80 [ "ok" ];
else [ "high" ]
]
]
classify: 12
classify: 50
classify: 95
What happens here is plain:
- the first true branch wins
- the chosen block returns an ordinary value
- that value becomes the result of the whole
cond
Loops
Use while for stateful repetition and repeat for counted repetition.
repeat 3 as i [
led.blink: i + 1, 40
]
while discards the body value and yields nil when it finishes.
The useful pattern is “keep state in visible places, then return a value after the loop”:
to sumTo with limit [
here total is 0;
here i is 0;
while i < limit [
set total to total + i;
set i to i + 1
];
total
]
sumTo: 5
sumTo: 10
This stays readable because nothing is hidden:
- the condition reads ordinary names
- the body mutates ordinary places
- the final expression returns the useful result
Cells
Cells is the fixed-size mutable indexed store in Frothy. Use cells(n) at
top level to create a fixed store you can reuse and update.
frame is cells(8)
set frame[0] to 12
set frame[1] to true
Elements begin as nil. The safe mental model is still “small fixed indexed
storage”, and record values also work inside cells.
For plain counted storage, keep it simple:
histogram is cells(4)
set histogram[0] to 0
set histogram[1] to 0
set histogram[2] to 0
set histogram[3] to 0
to bucket with sample [
if sample < 25 [
0
] else [
if sample < 50 [
1
] else [
if sample < 75 [ 2 ] else [ 3 ]
]
]
]
to countSample with sample [
here slot is bucket: sample;
set histogram[slot] to histogram[slot] + 1
]
countSample: 10
countSample: 62
countSample: 91
countSample: 62
That is an effective Cells example because index position is the whole point.
If you keep forgetting what index 2 means, you probably want records.
Records
Frothy also supports records:
record Point [ x, y ]
origin is Point: 0, 0
Records are fixed-layout shaped data. They are a better fit than Cells when
the thing you care about is named fields rather than indexed storage.
The first useful record example is just “one coherent piece of state with names”:
record Sprite [ x, y, visible ]
player is Sprite: 3, 4, true
player->x
player->visible
That reads better than state[0] and state[2] because the shape is carried
in the value.
Field mutation is ordinary Frothy mutation:
set player->x to player->x + 1
set player->visible to false
player->x
player->visible
This is the normal pattern:
- keep the long-lived thing in a top-level slot
- read the fields directly
- mutate only the place that changed
You can also write small helpers against that shaped value:
to moveRight with sprite, step [
set sprite->x to sprite->x + step
]
moveRight: player, 2
player->x
That is often the right level of abstraction in Frothy. You do not need a class system or an object bag just to move one thing across the board.
Records Inside Cells
Once you want several shaped values in a fixed indexed store, combine the two surfaces:
record Pixel [ x, y, on ]
pixels is cells(2)
set pixels[0] to Pixel: 1, 2, true
set pixels[1] to Pixel: 5, 6, false
pixels[0]->x
pixels[1]->on
You can mutate through the indexed place too:
set pixels[1]->on to true
pixels[1]->on
This is a good fit for “array of state records” work such as actors, points, or simple display updates.
Nested Records
Record fields may themselves hold records. That is enough for small hierarchies without turning the language into a dynamic object system.
record Point [ x, y ]
record Actor [ pos, glyph ]
ghost is Actor: Point: 7, 2, "@"
ghost->pos->x
ghost->glyph
The access path stays literal. ghost->pos->x only works because pos is a
record field and that nested record has an x field.
Records And Persistence
Records are useful partly because they persist cleanly when their fields are persistable:
record Counter [ value ]
counter is Counter: 0
set counter->value to counter->value + 1
save
set counter->value to 9
restore
counter->value
After restore, the value is back to 1.
That makes records a strong fit for saved board state, counters, cursor position, display mode, or other overlay-owned data.
Choosing The Shape
Use Cells when:
- order and index position matter most
- the storage is fixed-size
- the lanes are simple enough that numbers like
0and1stay meaningful
Use records when:
- the thing has named parts
- field names make the code easier to read
- you want one coherent shaped value
Use both when:
- you want a fixed indexed container full of shaped values
Records are part of the language you can use directly on the board and in the prompt.
Next: Inspection and the live workflow .