Computed — derived values

Use computed name = expression to declare a derived variable — auto-recomputes when source variables change. Pull "complex expressions" out of templates so interact blocks stay readable, composable, and chain-derivable.


When to use

  • ✅ Same expression referenced multiple times in template (avoid duplication)
  • ✅ Multi-step computation (intermediate values are meaningful and worth naming)
  • ✅ Conditional categorization (use ternary to map numbers to labels)
  • ✅ Reuse across charts / templates (multiple blocks share one derivation)
  • ❌ Simple one-off expressions (write directly in template, more direct)
  • ❌ Complex business logic / side effects / async (the mini DSL is pure-function — no IO / state mutation)

Basic syntax

\```interact
slider weight 40 120 70 1
slider height 1.4 2.1 1.7 0.01
computed bmi = weight / (height * height)
template stl:
[BMI] -> [{bmi:.1f}]
\```

computed <name> = <expression> declares a derived variable. Expressions evaluate in declaration order.

Renders: drag any slider → bmi recomputes → template updates live.


Expression mini-DSL — full spec

Arithmetic

Operator Syntax Example
Add / sub / mul / div + - * / a + b * c
Modulo % n % 10
Power ** or pow(a,b) 2 ** n or pow(2, n)

Comparison + logical

Operator Syntax Example
Comparison == != < > <= >= age >= 18
Logical && || ! enabled && !muted
Ternary cond ? a : b bmi < 18.5 ? "thin" : "normal"

Built-in functions

Category Functions
Basic math abs(x) sign(x) min(a,b,...) max(a,b,...)
Powers / roots pow(a,b) sqrt(x) exp(x) log(x) log2(x) log10(x)
Trigonometry sin(x) cos(x) tan(x) asin(x) acos(x) atan(x) atan2(y,x)
Rounding floor(x) ceil(x) round(x) trunc(x)
Strings length(s) upper(s) lower(s) substring(s,start,end)
Casts parseFloat(s) parseInt(s) toString(n)

Constants

Name Value
pi 3.14159...
e 2.71828...

Not supported (safety design)

  • ❌ eval / Function constructor
  • ❌ DOM / window / document access
  • ❌ fetch / network
  • ❌ setTimeout / async
  • ❌ if/else statements (use ternary)
  • ❌ for/while loops (use vega-lite transforms or svg multi-element generation)
  • ❌ Function definitions / closures (no function foo() {...})

Design goal: pure-function expressions, absolutely safe (the reader needs no sandbox), easy for LLMs to generate without errors.


Examples

Example 1: BMI calculation + classification

\```interact
slider weight 40 120 70 1
slider height 1.4 2.1 1.7 0.01
computed bmi = weight / (height * height)
computed category = bmi < 18.5 ? "Underweight"
                  : bmi < 24   ? "Normal"
                  : bmi < 28   ? "Overweight"
                  :              "Obese"
template stl:
[BMI] -> [{bmi:.1f}] ::mod(category="{category}")
\```

Renders: BMI number + auto-classified label. category uses nested ternaries to map number to label.

Example 2: compound interest — multi-step chained derivation

\```interact
slider principal 1000 100000 10000 1000
slider rate 0 0.15 0.05 0.01
slider years 1 30 10 1
computed multiplier = pow(1+rate, years)
computed compound = principal * multiplier
computed gain = compound - principal
computed gainPercent = (gain / principal) * 100
template stl:
[Principal] -> [${principal:.0f}]
[Multiplier] -> [{multiplier:.3f}x]
[Final value] -> [${compound:.0f}]
[Net gain] -> [${gain:.0f} ({gainPercent:.1f}%)]
\```

Renders: 4 output lines. multiplier → compound → gain → gainPercent chained derivation; each intermediate value referenced in template.

Example 3: physics — spring oscillator position

\```interact
slider amplitude 0 5 2 0.1
slider frequency 0.1 5 1 0.1
slider phase 0 6.28 0 0.1
slider t 0 10 0 0.1
computed angularFreq = 2 * pi * frequency
computed position = amplitude * sin(angularFreq * t + phase)
computed velocity = amplitude * angularFreq * cos(angularFreq * t + phase)
template stl:
[Position x(t)] -> [{position:.3f} m]
[Velocity v(t)] -> [{velocity:.3f} m/s]
\```

Renders: 4 sliders control spring parameters; 2 lines output position + velocity (based on SHM physics formula). angularFreq = 2πf is the intermediate derivation.

Example 4: toggle-driven conditional branches

\```interact
slider price 0 1000 100 1
toggle isPremium false
toggle hasCoupon false
computed memberDiscount = isPremium ? 0.2 : 0
computed couponDiscount = hasCoupon ? 0.1 : 0
computed totalDiscount = min(memberDiscount + couponDiscount, 0.3)
computed finalPrice = price * (1 - totalDiscount)
template stl:
[Original] -> [${price}]
[Member off] -> [{(memberDiscount*100):.0f}%]
[Coupon off] -> [{(couponDiscount*100):.0f}%]
[Total off] -> [{(totalDiscount*100):.0f}% (≤30% cap)]
[Final price] -> [${finalPrice:.2f}]
\```

Renders: toggles switch member / coupon; totalDiscount uses min(...) to apply 30% cap. Logic stays in declarations, not imperative.


Plain-text fallback behavior

computed declarations are fully visible in plain-text readers:

\```interact
slider weight 40 120 70 1
slider height 1.4 2.1 1.7 0.01
computed bmi = weight / (height * height)
template stl:
[BMI] -> [{bmi:.1f}]
\```

→ Readers see computed bmi = weight / (height * height) and fully understand the derivation. Plain text doesn't evaluate, but the expression itself is well-documented.


Common pitfalls

1. Circular dependency

\```interact
computed a = b + 1     ← ❌ Mutual dependency
computed b = a + 1
template stl:
[a] -> [{a}]
\```

→ Rho's topological sort fails. Computeds can't form a cycle. For "previous value + 1" patterns, use button self-reference (see Interact controls Example 3).

2. computed references undeclared variable

\```interact
slider x 0 10 5 1
computed y = x + z      ← ❌ z not declared
template stl:
[y] -> [{y}]
\```

→ Expression throws "undefined z". Declare all variables (slider/input/computed) first.

3. Expression syntax errors

computed bmi = weight / height ** 2          ← ⚠️ Precedence confusion; actually weight / (height ** 2)
computed bmi = (weight / height) ** 2        ← ❌ Wrong formula
computed bmi = weight / (height * height)    ← ✅ Recommended: explicit parens
computed bmi = weight / pow(height, 2)       ← ✅ Recommended: use pow function

Use parentheses to express precedence explicitly — don't rely on implicit precedence; avoids reader (and LLM) miscomputation.

4. Ternary nesting too deep

computed grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : score >= 60 ? "D" : "F"

3-level ternary is near readability limit. For 4+ levels, use multi-line format:

computed grade = score >= 90 ? "A"
              : score >= 80 ? "B"
              : score >= 70 ? "C"
              : score >= 60 ? "D"
              :               "F"

Or split into multiple computeds:

computed isHigh = score >= 90
computed isMid = score >= 70 && score < 90
computed grade = isHigh ? "A" : isMid ? "C" : "F"

5. Expression with string operations

input city "Shanghai"
computed greeting = "Hello, " + city + "!"     ← ⚠️ Some implementations support, some don't

mini DSL string concatenation support varies by reader implementation. Safest: write {city} interpolation directly in template.

6. Floating-point precision loss

computed total = 0.1 + 0.2     ← May produce 0.30000000000000004
template stl:
[Result] -> [{total}]

→ Floats are IEEE 754 — finite precision. For money / ratios, force precision via format spec: {total:.2f} → 0.30.

7. Too many computeds

\```interact
slider a 0 10 5 1
computed b = a * 2
computed c = b + 1
computed d = c / 3
computed e = pow(d, 2)
computed f = sqrt(e)
... (15 computeds)
\```

→ Maintenance nightmare. Redesign: combine multiple computeds into one expression (if intermediates aren't referenced); or split across multiple interact blocks (sharing via namespace).


Computed vs template expression — choose

Scenario Use
One-off simple expression Write {a + b} directly in template
Same expression in template ≥ 2 times Extract as computed
Multi-step derivation (intermediates have meaningful names) Multiple computeds chained
Reused across templates / charts computed

See also