算子先於值
APL算子代數的類型多態重建與算子本體論的實驗性確認
EveMissLab Working Paper 作者:Neo.K(許筌崴) 日期:2026年6月
摘要
本文以 APL(A Programming Language)的核心算子代數為出發點,通過在 Python 環境中的重建實驗,論證以下三項命題:其一,APL 的歷史衰退並非源於概念失敗,而是架構時代的硬性制約;其二,APL 的算子語義——fold、scan、outer、each、fork——在解除數值類型綁定後,能夠無縫升至任意代數結構,並以雙數(Dual Number)自動微分的實現為核心佐證;其三,上述觀察對 EveMissLab 的算子本體論猜想(EML-OO)提供了可計算層面的實驗性支撐。
關鍵詞: APL、算子代數、類型多態、雙數、自動微分、算子本體論、EML-OO
一、問題的起點:一個語言的消失
1962 年,Kenneth Iverson 在其著作 A Programming Language 中提出了一個激進的主張:數學符號本身應當可以執行。他設計了一套以算子為核心的語言,其中每個符號——⍴(reshape)、+/(fold with add)、∘.×(outer product with multiply)——不是函式名稱,而是算子(operator),是作用於函式之上的函式。
APL 在 1960-70 年代短暫登上主流,隨後逐漸邊緣化。通行的解釋指向可讀性差、字符集特殊、學習曲線陡峭。然而這些都是表層症狀,不是病因。
本文的論點是:APL 的真正侷限在於它把算子的語義硬綁在數值陣列上。+/ 的 + 必須是浮點加法。∘.f 的 f 必須接受並回傳數值。算子的通用性被其實作的具體性取消了。
這個侷限不是 Iverson 概念的失敗,而是 1960 年代計算架構的必然結果——沒有泛型、沒有類型參數、沒有高階類型系統。APL 的概念超前了它的時代。
二、重建實驗:解綁類型的算子代數
本文的核心實驗是在 Python 環境下重建 APL 的五個基本算子,並刻意使其對輸入類型保持無知:
def fold(f):
def _fold(seq): return reduce(f, seq)
return _fold
def scan(f):
def _scan(seq):
it = iter(seq); acc = [next(it)]
for x in it: acc.append(f(acc[-1], x))
return acc
return _scan
def outer(f):
def _outer(a, b): return [[f(x, y) for y in b] for x in a]
return _outer
def each(f): return lambda seq: [f(x) for x in seq]
def fork(f, g, h): return lambda seq: g(f(seq), h(seq))
這五個函式的共同特徵是:它們不知道自己操作的是什麼類型。fold 只知道「對序列施加二元運算並累積」;outer 只知道「對所有配對施加二元函式」。類型是自由變數,不是固定參數。
在標準數值域上,這些算子產生 APL 的經典行為:
fold(add)(iota(8)) → 36
scan(mul)(iota(5)) → [1, 2, 6, 24, 120]
outer(mul)(iota(5), iota(5)) → 5×5 乘法表
到此為止,與原始 APL 的差異僅在於語法。關鍵的轉折在下一節。
三、類型升遷實驗:雙數的注入
3.1 雙數代數
雙數(Dual Number)定義為形如 $a + b\varepsilon$ 的代數物件,其中 $\varepsilon^2 = 0$。其算術規則為:
$$ (a + b\varepsilon) + (c + d\varepsilon) = (a+c) + (b+d)\varepsilon $$ $$ (a + b\varepsilon)(c + d\varepsilon) = ac + (ad + bc)\varepsilon $$
若令 $a = f(x)$,$b = f'(x)$,則雙數編碼了函式值與其導數的聯合計算。對輸入 $x$ 注入 $\text{Dual}(x, 1.0)$ 並進行任意算術,結果的 $\varepsilon$ 係數自動給出導數。
3.2 算子無需修改
將雙數注入後,我們用與 3.1 節完全相同的算子進行計算:
# 對序列 [x₁², x₂², x₃², x₄²] 求和的梯度
sq_sum = compose(fold(add), each(lambda x: x**2))
xs = [1.0, 2.0, 3.0, 4.0]
gradient(sq_sum, xs) → [2.0, 4.0, 6.0, 8.0]
fold、each、compose 的代碼一行未動。微分不是新加的功能,而是類型升遷後自然湧現的行為。
這個實驗的哲學意涵是決定性的:
算子是形態射(morphism),不是數值函式。它們對任何滿足其運算公理的代數結構都成立。
APL 的 +/ 之所以無法做到這件事,不是因為 fold 的概念有缺陷,而是因為它的 + 被硬編碼為浮點加法。我們的重建移除了這個綁定。
3.3 驗證範圍
同一套算子,無修改地作用於雙數域,驗證了以下六個場景:
| 場景 | 算子使用 | 驗證內容 | |------|---------|---------| | 純量微分 | Dual(x, 1.0) 直接注入 | $f'(x)$ 精確至機器精度 | | fold 梯度 | compose(fold(add), each(x²)) | $\nabla(\sum x_i^2) = 2\mathbf{x}$ | | scan 導數 | scan(mul) 接雙數序列 | $\partial(\prod_{k=0}^n x_k)/\partial x_0$ 精確 | | outer Jacobian | outer(mul) 接雙數陣列 | Jacobian 矩陣對角線 = $b$ | | fork 平均梯度 | fork(fold(add), div, len) | $\partial\bar{x}/\partial x_i = 1/n$ | | Newton-Raphson | diff(f) 在迭代中使用 | 6步收斂至 $10^{-13}$ 殘差 |
所有場景的自動計算值與解析解之差為 $\delta = 0$(機器精度範圍內)。
四、四個應用的結構意義
六個文件(apl_sim.py、apl_ad.py、apl_nn.py、apl_life.py、apl_optimizer.py、apl_fractal.py)並非獨立展示,而是一個分層論證:
第一層(apl_sim.py): 算子在數值域的正確性。重現 APL 的所有經典操作。
第二層(apl_ad.py): 算子的類型多態性。雙數注入,微分自動湧現。
第三層(四個應用): 算子的應用廣度。每個應用都是算子在不同代數結構上的部署:
- apl_nn.py:
compose是層組合,fold(add)是神經元激活前的加權求和,each是逐元素激活,梯度透過雙數傳播。反向傳播不需要獨立實作——它是前向模式自動微分的直接推論。
- apl_life.py:
fold(add_grid)是八方向鄰居計數的空間摺疊,each(rule)是生存規則的逐格施加,scan(step)是時間演化的軌跡展開。Conway 的細胞自動機在此成為算子的時空組合。
- apl_optimizer.py:
gradient(f, xs)直接呼叫雙數系統,outer生成等高線網格。三種優化器(SGD、Momentum、Adam)的差異僅在於梯度的累積方式——算子本身不變。
- apl_fractal.py:
Complex類的設計與Dual完全平行——同樣的__mul__、__add__、__truediv__結構,不同的代數規則(複數乘法 vs. 雙數乘法)。outer生成複數平面網格,逃逸時間計算是算子的應用。Mandelbrot 集是算子作用在複數域上的幾何投影。
這四個應用共同說明了同一件事:同一套算子,換一種代數結構,就是一個全新的計算範式。這不是偶然的巧合,而是算子多態性的必然推論。
五、APL 的復辟論題
基於上述論證,我們可以更精確地重述 APL 衰退的原因,並由此推斷其復辟的條件。
5.1 衰退的真實原因
APL 在技術上的失敗集中在三點:
- 類型單態性:算子被綁定在浮點數值上,無法跨代數結構遷移。
- 靜態算子集:算子是語言內建的固定符號,無法在運行時生成新算子。
- 缺乏遞歸結構:古典 APL 的陣列必須是矩形,無法表達樹、圖等非規則結構。
這三點在 1960 年代都是架構層面的硬限制,不是概念層面的錯誤。
5.2 復辟的技術條件
現代語言生態已具備 APL 當時缺乏的所有工具:
- 泛型系統:Rust 的 trait、Haskell 的 typeclass、Python 的 protocol 都允許算子對任意類型有效。
- 高階類型:
fold、scan可以作為 monad 的特例被形式化。 - 動態算子生成:函式是一等公民,算子可以在運行時合成。
- 硬體向量化:APL 最初的效率承諾(陣列操作比逐元素 for-loop 快)在 SIMD/GPU 時代終於有了真正的基礎設施支撐。
一個現代 APL——保留 Iverson 的符號哲學,但以泛型算子代替數值綁定算子——理論上能夠在以下領域與現有語言競爭:
- 數值計算(已有 J、K 的驗證)
- 自動微分框架(JAX 的設計哲學正是此方向)
- 陣列程式設計(numpy 的算子介面是弱化版的 APL)
- 量子計算(量子電路本質上是算子作用在 Hilbert 空間上)
APL 的概念在等待一個不受制於 1960 年代硬體的實作。
六、EML-OO 的實驗性確認
EveMissLab 的算子本體論(EML-OO)的核心命題是:
所有事物都是算子。值是算子作用在零元素(unit)上的像。
形式化表達為:在 $C^*$-代數框架下,任意物件 $x$ 都可以表示為某個算子 $\hat{O}_x$,符號-實在同構比值 $\rho(\mathscr{S}, \mathscr{R}) \to 1$。
本文的實驗對 EML-OO 提供了以下層次的支撐:
計算層面: 我們展示了,當算子被解除數值綁定後,同一個 fold 在不同代數結構($\mathbb{R}$、$\mathbb{R}[\varepsilon]$、$\mathbb{C}$)上運行。類型不是算子的屬性,而是算子的參數。這在計算意義上驗證了「算子先於值」的命題。
結構層面: Dual 和 Complex 的設計是完全平行的——兩者都是「在基礎加法/乘法上附加額外結構的代數延伸」。Dual 的 $\varepsilon$ 係數追蹤導數;Complex 的虛部追蹤相位。兩種延伸都對所有算子透明。這表明,代數結構的差異不影響算子的形態射性質——算子在不同代數範疇之間是函子式的。
本體論層面: 如果算子可以被無修改地升遷至任意代數結構,那麼「所有代數結構都是算子的投影」這個命題獲得了一個操作性的詮釋:給定一個算子代數,不同的類型注入對應不同的「投影模式」,而同一批算子在所有投影中保持形態射性。值——浮點數、雙數、複數、矩陣——都是算子在特定代數結構上的實例化,而非算子本身。
EML-OO 的猜想在此獲得了一個可計算的類比:算子不是工具,算子是基底。值是算子選擇了特定代數結構之後的顯現。
這個類比不是 EML-OO 的完整證明——形式化的嚴格論證仍需要 $C^*$-代數框架下的完整推導。但它確認了一點:這個方向的猜想在計算層面是自洽的,不是空想。
七、結語
Iverson 在 1962 年看到了一件事:數學的力量不在於它描述的數值,而在於它操縱符號的方式。算子是符號。值是算子的影子。
這個洞見被 1960 年代的計算架構所壓制,但從未被推翻。六十年後,當我們在 Python 中重建這個代數,把 fold 從浮點數組的囚籠裡解放出來,注入雙數,看著梯度自動湧現——我們看到的不是新的技術,而是一個被時代誤解的舊命題,終於在它可以自由呼吸的環境裡展示了它本來的形狀。
APL 沒有輸。它早到了。
而如果算子代數在計算層面是正確的,那麼在本體論層面斷言「所有事物都是算子」,就不再只是一個哲學姿態,而是一個等待更嚴格形式化的開放猜想。
附錄:代碼清單
| 檔案 | 功能 | 核心算子 | |------|------|---------| | apl_sim.py | 算子代數基底(數值域) | fold, scan, outer, each, fork, compose | | apl_ad.py | 雙數自動微分系統 | Dual class, diff, gradient | | apl_nn.py | 神經網路(XOR) | compose, fold, each + Dual AD | | apl_life.py | Conway's Game of Life | fold(add_grid), each(rule), scan(step) | | apl_optimizer.py | 梯度下降優化器 | gradient(), outer(contour) | | apl_fractal.py | Mandelbrot/Julia 集 | Complex class, outer(grid) | | apl_maze.py | 迷宮生成與求解 | outer(explore) 平行BFS, each(render_cell) | | apl_maze_bench.py | 規模基準測試 | APL平行BFS vs 循序BFS,10×10至400×400 |
基準測試摘要(Python 3.14,Windows,seed=42)
| 規模 | 格數 | 生成(ms) | APL BFS(ms) | 循序BFS(ms) | APL/循序 | 路徑步數 | |------|------|---------|------------|-----------|---------|---------| | 10×10 | 100 | 0.5 | 0.1 | 0.0 | ~2.9x | 47 | | 50×50 | 2,500 | 7 | 3 | 2 | ~2.0x | 973 | | 100×100 | 10,000 | 28 | 15 | 6 | ~2.5x | 2,401 | | 200×200 | 40,000 | 131 | 17 | 10 | ~1.8x | 4,729 | | 400×400 | 160,000 | 711 | 129 | 91 | ~1.4x | 20,621 | | 1000×1000 | 1,000,000 | 10,236 | — | 621 | — | 95,445 |
備注:APL 平行 BFS 在 Python 中因 outer() 的 list-of-lists 開銷比循序 BFS 慢 1.4–2.9 倍。此開銷源於 CPython 無 JIT、無 SIMD;在 Rust / GPU 環境下,outer(explore) 的真正平行展開將反轉此比例。路徑長度兩方法完全一致(正確性已驗證)。
所有代碼在 Python 3.10+ 環境下驗證,apl_ad.py 為零依賴純標準庫,apl_sim.py 依賴 numpy(自動安裝)。
EveMissLab Working Paper — 版權所有,未經授權不得轉載 算子本體論系列 | EML-OO 實驗性佐證文獻