Computed — derived values

用 computed name = expression 声明派生变量——自动跟随源变量更新。把"复杂表达式"从 template 里抽出来,让 interact 块可读、可组合、可链式派生。


When to use

  • ✅ 同一表达式被 template 多次引用(避免重复书写)
  • ✅ 多步计算(中间值有意义、值得命名)
  • ✅ 条件分类(用三元式把数值映射成标签)
  • ✅ 跨 chart / 跨 template 复用(多块共用同一派生)
  • ❌ 简单一次性表达式(直接写在 template 里更直观)
  • ❌ 复杂业务逻辑 / 副作用 / 异步(mini DSL 是纯函数,不支持 IO / 状态修改)

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> 声明一个派生变量。表达式按声明顺序求值。

渲染:拖任一 slider → bmi 自动重算 → template 实时更新。


表达式 mini DSL 完整规范

算术运算

运算 语法 例
加减乘除 + - * / a + b * c
取余 % n % 10
幂 ** 或 pow(a,b) 2 ** n 或 pow(2, n)

比较 + 逻辑

运算 语法 例
比较 == != < > <= >= age >= 18
逻辑 && || ! enabled && !muted
三元 cond ? a : b bmi < 18.5 ? "瘦" : "正常"

内置函数

类别 函数
数学基础 abs(x) sign(x) min(a,b,...) max(a,b,...)
幂 / 根 pow(a,b) sqrt(x) exp(x) log(x) log2(x) log10(x)
三角 sin(x) cos(x) tan(x) asin(x) acos(x) atan(x) atan2(y,x)
取整 floor(x) ceil(x) round(x) trunc(x)
字符串 length(s) upper(s) lower(s) substring(s,start,end)
类型转换 parseFloat(s) parseInt(s) toString(n)

常数

名 值
pi 3.14159...
e 2.71828...

不支持 的(安全设计)

  • ❌ eval / Function 构造
  • ❌ DOM / window / document 访问
  • ❌ fetch / 网络
  • ❌ setTimeout / 异步
  • ❌ if/else 语句(用三元式替代)
  • ❌ for/while loop(用 vega-lite 的 transform 或 svg 的多元素生成)
  • ❌ 函数定义 / 闭包(不能 function foo() {...})

设计目标:纯函数表达式,绝对安全(reader 不需要 sandbox),LLM 易生成不易出错。


Examples

Example 1:BMI 计算 + 分类

\```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 ? "偏瘦"
                  : bmi < 24   ? "正常"
                  : bmi < 28   ? "超重"
                  :              "肥胖"
template stl:
[BMI] -> [{bmi:.1f}] ::mod(category="{category}")
\```

渲染:BMI 数字 + 自动分类标签。category 用嵌套三元映射数值到标签。

Example 2:复利 — 多个派生值链式

\```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:.0f}]
[复利倍数] -> [{multiplier:.3f}x]
[终值] -> [${compound:.0f}]
[净收益] -> [${gain:.0f} ({gainPercent:.1f}%)]
\```

渲染:4 行输出。multiplier → compound → gain → gainPercent 链式派生,每个中间值都被 template 引用。

Example 3:物理 — 弹簧振子位置

\```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:
[位置 x(t)] -> [{position:.3f} m]
[速度 v(t)] -> [{velocity:.3f} m/s]
\```

渲染:4 sliders 控制弹簧参数,2 行输出位置 + 速度(基于 SHM 物理公式)。angularFreq = 2πf 是中间派生。

Example 4:toggle 驱动条件分支

\```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:
[原价] -> [${price}]
[会员折] -> [{(memberDiscount*100):.0f}%]
[券折] -> [{(couponDiscount*100):.0f}%]
[总折] -> [{(totalDiscount*100):.0f}%(≤30% 上限)]
[到手价] -> [${finalPrice:.2f}]
\```

渲染:toggle 切会员 / 优惠券;totalDiscount 用 min(...) 应用 30% 上限。逻辑全在声明里,不是命令式。


Plain-text fallback behavior

computed 声明在 plain-text reader 里完整可见:

\```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 bmi = weight / (height * height),完全理解派生关系。Plain text 不求值,但表达式本身是文档化的好形式。


Common pitfalls

1. 循环依赖

\```interact
computed a = b + 1     ← ❌ 互相依赖
computed b = a + 1
template stl:
[a] -> [{a}]
\```

→ Rho 拓扑排序会失败。computed 之间不能形成环。如果你需要"上次值 + 1"模式,用 button 的 self-reference(见 Interact controls Example 3)。

2. computed 引用未声明的变量

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

→ 表达式抛 "undefined z"。先 slider/input/computed 声明所有变量。

3. 表达式语法错误

computed bmi = weight / height ** 2          ← ⚠️ 优先级混淆,实际是 weight / (height ** 2)
computed bmi = (weight / height) ** 2        ← ❌ 错误公式
computed bmi = weight / (height * height)    ← ✅ 推荐:明确括号
computed bmi = weight / pow(height, 2)       ← ✅ 推荐:用 pow 函数

用括号显式表达优先级——别依赖隐式优先级,避免读者(和 LLM)算错。

4. 三元嵌套深度过深

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

3 层三元已经接近可读上限。4 层以上改成多行格式:

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

或拆成多个 computed:

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

5. 表达式含字符串运算

input city "上海"
computed greeting = "你好," + city + "!"     ← ⚠️ 部分实现支持,部分不

mini DSL 的字符串拼接支持度因 reader 实现而异。最稳的方式:在 template 里直接写 {city} 拼接。

6. 小数精度丢失

computed total = 0.1 + 0.2     ← 可能得 0.30000000000000004
template stl:
[结果] -> [{total}]

→ 浮点数是 IEEE 754,精度有限。Money / 比率类用 format spec 强制位数:{total:.2f} → 0.30。

7. computed 数量过多

\```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 个 computed)
\```

→ 维护噩梦。重新设计:把多个 computed 合并到一个表达式里(如果中间值不被 template 引用);或拆到多个 interact 块(用 namespace 共享)。


Computed vs template 表达式 — 选择

场景 用
一次性简单表达式 template 里直接写 {a + b}
同一表达式 template 引用 ≥ 2 次 抽成 computed
多步推导(中间值有名字意义) 多个 computed 链式
跨 template / 跨 chart 复用 computed

See also