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
- Interact controls — slider / input / select / toggle / button
- Interact text template — template 语法
- Interact + Vega-Lite chart — chart 也能引用 computed
- Timer — 自动播放(也能驱动 computed)
- Shared state (namespace)
- Plain-text fallback principle