Fahrenheit features a powerful reactive binding mechanism that allows variables to stay in sync with expressions automatically.
| Operator | Meaning |
|---|---|
a = expr | Immediate assignment = evaluates expr once and stores the result in a. |
a := expr | Reactive binding := a is kept in sync: whenever any variable referenced in expr changes, a is automatically recomputed. |
Assigning normally (a = value) to a previously bound variable removes the binding.
int b = 5;
int a := b; # a is bound to b; a is now 5
b = 42; # a automatically becomes 42
# Expressions work too
int c := b + 10; # c is always b + 10; now 52
# Removing the binding
a = 0; # a is no longer reactive; a = 0
b = 99; # a stays 0
Bindings are also recursive across complex components like Structs, Arrays, and Maps. Whenever a child element of a bound structure (or its sub-elements, deeply) changes, the parent is notified and the binding is recomputed.
structure Point { int x; int y; }
any p = Point(0, 0);
int total := p.x + p.y; # total changes whenever p.x or p.y changes
any list = [1, 2];
any arr = [list, 3];
any first := arr[0][0]; # bound to the inner list's first element
list.set(0, 99); # updates 'list', which notifies 'arr', and 'first' becomes 99
Triggers are reactive blocks inside a structure definition that run automatically when a field is written. They use the on keyword.
| Form | When it fires |
|---|---|
on (boolExpr) { ... } | Fires after any field write on the struct where boolExpr evaluates to truthy. |
on (field changed) { ... } | Fires only when the value of field actually differs from its previous value. |
structure Alarm {
int level;
boolean triggered = false;
# Boolean-condition trigger
on (this.level > 5) {
this.triggered = true;
print("ALERT: level is " + this.level);
}
}
a = Alarm();
a.level = 10; # triggers fire: triggered becomes true
a.level = 3; # level > 5 is false, no fire
The changed keyword allows you to react only when a specific field's value is modified.
int changeCount = 0;
structure Trackable {
any state;
on (state changed) {
changeCount += 1;
print("State is now: " + this.state);
}
}
t = Trackable();
t.state = "on"; # fires (null -> "on")
t.state = "off"; # fires ("on" -> "off")
t.state = "off"; # does NOT fire (value unchanged)
Note: Trigger bodies execute in the calling scope, meaning any variable assignment inside a trigger is visible to the rest of the script.
Triggers are temporarily disconnected during structure construction to prevent premature execution before the object is fully initialized.