布尔值有两个值,这是常识。我加了第三个:
未知。
| 值 | 含义 | 不是什么 |
|---|---|---|
TRUE | 已观测,为真 | |
FALSE | 已观测,为假 | |
MAYBE | 尚未观测 | 不是 None,不是错误,不是随机 |
第三态不是「一半真一半假」
这是最容易理解错的地方。很多人一听三值逻辑,想到的是 「0.5」「概率」「介于真假之间」。
不是。 未知 的含义是
信息缺失——不是「介于」,是「还不知道」。
我给人解释时用一个类比(这个类比没有进代码注释): 约人出来。
TRUE是约到了FALSE是约不到未知是消息发出去了,还没回
没回不等于失败,也不等于成功。它就是没回。
为什么不用随机数
有人会想:遇到未知就随机选一边,不就能继续跑了?
这里有一条硬规定:代码里绝不允许出现
random。整个 6500 行的解释器里,
random 这个词只出现在一句禁止使用它的注释里。
理由不是「随机不好」,而是:
底层是二进制机器,任何随机都是伪随机。 用伪随机冒充未知,等于把「我不知道」偷偷换成 「我掷了个骰子」。
这两句话听起来像,实际差得很远。前者诚实,后者假装自己有信息。
所以碰到未知,什么都不做
if 的条件是 未知 时,
then 和 else 都不执行。
x「未知」
if x
$("走了真分支")
否则
$("走了假分支")
这段代码两个分支都不会打印。不是崩溃,不是跳过, 是「不替你选」。
这叫静默观测。但「静默」不等于什么都不说—— 那样你会看到一个不动的程序,分不清是遇到未知还是程序坏了。 所以它记一笔账:
炑:静默观测已触发(第2行),状态未知,未执行任何分支。
未知来源:x
传播路径:x → y → z → w
传染情况:已传染 → 条件表达式(LogicNode)
提示:若要确定分支,先给 x 赋一个确切的值;
不确定就让它保持未知,语言不会替你猜。
未知会传染
未知参与任何运算,结果还是未知:
| 运算 | 结果 |
|---|---|
未知 + 1 | 未知 |
未知 > 1 | 未知 |
not 未知 | 未知 |
[未知][0] | 未知 |
f(未知) | 未知 |
未知 + 1 是几?猜 0 就是 1,猜 1 就是 2。
哪个都不是用户能确认的答案。
最难的部分:它从哪来
账本要回答「这个未知是怎么传过来的」。第一版实现是 事后爬一遍语法树去推断——看条件表达式是什么节点类型, 猜有没有发生运算。
它能应付「这一行」,但追不出跨行的传递:
x「未知」
y「x + 1」 ← 未知传给了 y
z「y * 2」 ← 又传给了 z
if z ← 只知道 z 是未知,不知道它是从 x 一路来的
第二版改成给值打污染标记,标记跟着值一路走。 于是「怎么传过来的」从推断变成了事实。
这里还踩过一个坑,值得单独说:
两个独立来源,不能画成一条因果链
如果 c「a + b」,而 a 和 b
是两个互不相关的未知,那么画成
a → b → c 就是在撒谎——
它在声称「a 引起了 b」,而 a 和 b 毫无关系。
所以用两个不同的连接符:
| 符号 | 含义 | 例子 |
|---|---|---|
> | 流过去:值从左边算出来,存进了右边 | x>y |
+ | 两个独立来源汇合 | a+b |
渲染出来就是:
x → y → z → w 一条传递路径
a → c 与 b → c 两个独立分支,都流进了 c
光靠一个符号表达不了这两种关系。
这门语言唯一「不是套壳」的东西
说白了,这个解释器 90% 的功能是「用更啰嗦的方式做 CPython 本来就会做的事」。三值逻辑是唯一一个例外—— CPython 里没有这个,你得自己实现。