Fahrenheit 0.4.0

Bindings

Reactive Bindings

Fahrenheit features a powerful reactive binding mechanism that allows variables to stay in sync with expressions automatically.

Operators

OperatorMeaning
a = exprImmediate assignment = evaluates expr once and stores the result in a.
a := exprReactive 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
    

Field and Deep Bindings

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
    

Structure Triggers

Triggers are reactive blocks inside a structure definition that run automatically when a field is written. They use the on keyword.

Trigger Forms

FormWhen 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
    

Watch for Changes

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.