热门问题
时间线
聊天
视角
SECD抽象機
来自维基百科,自由的百科全书
Remove ads
SECD機是有高度影響力的一種虛擬機和抽象機器,它意圖用作函數式程式語言編譯器的編譯目標。四個字母分別表示:Stack、Environment、Control、Dump,它們是這個機器的內部暫存器。暫存器Stack、Control和Dump指向堆疊(的某種實現),而Environment指向關聯數組(的某種實現)。
這個機器是第一個專門設計用來求值lambda演算表達式的機器。它最初描述於Peter J. Landin的1964年論文《表達式的機器求值》中[1]。Landin發表的描述非常抽象,留下了很多實現選擇待定(就像一種操作語義)。因此SECD機經常以一種更具體的形式出現,比如Peter Henderson的LispKit Lisp編譯器,它自從1980年開始發行。自此它已經被用作了一些其他的實驗編譯器的目標。
Remove ads
Landin的貢獻
D. A. Turner在2012年指出,《ALGOL 60修訂報告》(Peter Naur的1963年版)規定了依據複製規則的過程調用,這避免了具有對標識符的系統性改變的變量捕獲[3]。這種方法被加入到了ALGOL 60實現之中,但是在函數是頭等公民的函數式編程語言之中,自由變量在調用棧上可能被錯誤的解引用[4]。
D. A. Turner注意到,Peter J. Landin通過他的SECD機解決了這個問題,在這個機器中函數轉而通過在堆中的閉包來表示[5]。
非正式描述
在開始求值一個表達式的時候,這個表達式被裝載為控制C
的唯一元素。環境E
、堆疊S
轉儲D
開始時為空。
在C
的求值期間,表達式被轉換成具有ap
(就是apply)作為唯一算符的逆波蘭表示法(RPN)。例如,表達式F (G X)
(一個單一的列表元素)被轉變為列表X:G:ap:F:ap
。
C
的求值進行得類似於其他的RPN表達式。如果在C
中的第一個項目是值,把它壓入堆疊S
。更準確的說,如果這個項目是個標識符,剛壓入到堆疊的這個值,將綁定於在當前環境E
中的那個標識符。如果這個項目是個抽象,則構造一個閉包來保存它的自由變量的綁定(它們在E
中),並把這個閉包壓入堆疊。
如果這個項目是ap
,從堆疊彈出兩個值並完成應用(將第一個應用於第二個)。如果應用的結果是個值,把它壓入堆疊。
但是,如果應用是將一個抽象(已表示為閉包)應用於一個值,它結果的那個lambda演算表達式,自身可能就是應用(而非一個值),因而不能壓入堆疊。在這種情況下,S
、E
和C
的當前內容被壓入D
(它是這些三元組的堆疊),S
被重新初始化為空,而C
被重新初始化為這次應用的結果,具有E
包含針對這個表達式的自由變量的環境,增加上對這個應用的實際參數值的綁定。求值繼續如上那樣進行。
求值完成由C
為空來指示,在這種情況下結果在堆疊S
之上。接著彈出在D
上的最近保存的求值狀態,並把已經完成的計算的結果壓入堆疊,位於從D
恢復的內容之上。恢復狀態的求值繼續如上那樣進行。
如果C
和D
二者為空,則整體求值完成,結果在堆疊S
之上。
Remove ads
暫存器和內存
SECD機是基於堆疊的。函數從堆疊得到它們的實際參數。在指令流之中給內建指令的實際參數立即編碼在它們之後。
像所有內部資料結構一樣,堆疊是個列表,具有S
暫存器指向列表頭部或開始處。由於列表資料結構,堆疊不需要連續的內存塊,所以只要有一個單一空閒內存單元,就有堆疊空間可以獲得。即使在所有單元都已經使用了時候,垃圾回收可能產生額外的空閒內存。明顯的,SECD結構的特定實現者可以將堆疊實現為正規的堆疊結構,從而改進這個虛擬機的整體效能,假定在這個堆疊的尺寸上施加嚴格限定的話。
C
暫存器指向要求值的代碼或指令列表的頭部。一旦這裡的指令已經被執行,類似於常規機器中的「指令指針」(或程序計數器),C
將指向在列表中的下一個指令,除非後續指令總是在執行期間指定而不預設的包含在後續內存位置上,如在常規機器的情況下那樣。
當前變量環境由E
暫存器管理,它指向一個列表的列表。每個個體列表表示一個環境層級:當前函數的那些形式參數位於這個列表的頭部,在當前函數中是自由的但受到外圍函數所約束的那些變量,在E
的另一個元素中。
D
暫存器指向轉儲的頭部, 它被用作其他暫存器的值臨時存儲,比如在函數調用期間。它可以比擬於其他機器的返回堆疊。
SECD機的內存組織,類似於多數函數式語言解釋器所用的模型:一些內存單元,其中每個都持有要麼一個「原子」(一個單一的值比如13
),要麼表示一個空或非空列表。在後者情況下,這個單元持有兩個到其他單元的指針,一個標識第一個個元素,而另一個標識排除第一個元素的列表。這兩個指針傳統上分別叫做car
和cdr
,但是更現代的術語是「head」和「tail」經常用作其替代。一個單元可以持有的不同類型單元,可以用通過一個標誌來區別。它還區分原子的常見不同類型(整數、字符串等)。
所以,持有數字1, 2, 3
的列表,通常寫為(1 2 3)
,可以表示為如下:
地址 标志 内容(对于整数是值,对于列表是car和cdr) 9 [ integer | 2 ] 8 [ integer | 3 ] 7 [ list | 8 | 0 ] 6 [ list | 9 | 7 ] ... 2 [ list | 1 | 6 ] 1 [ integer | 1 ] 0 [ nil ]
內存單元3到5不屬於這個列表,這個列表的那些單元可以隨機的分布在內存中。單元2是這個列表的頭部,它指向持有第一個元素值的單元1,和只包含2
和3
的列表(開始於單元6)。單元6指向持有2
的單元和單元7,它表示只包含3
的列表。它指向包含值3
的單元8,並把指向空列表(nil
)作為cdr
。在SECD機中,單元0總是隱含的表示空列表,所有不需要特殊的標誌來指示空列表(需要做的就是簡單的指向單元0)。
在列表中cdr
必須指向另一個列表就是一個約定。如果car
和cdr
二者都指向原子,則產生一個有序對,通常寫為(1 . 2)
。
指令
nil
:將一個nil
指針壓入堆疊。ldc
:將一個常量實際參數壓入堆疊。ld
:將一個變量的值壓入堆疊。這個變量是由一個有序對實際參數來指示。這個有序對的car指定層級,而cdr指定位置。所以(1 . 3)
給出當前函數(層級1)的第三個參數。sel
:接受兩個列表實際參數,並從堆疊彈出一個值。如果彈出的值是非nil
則執行第一個列表,否則執行第二個列表。在這些列表指針被製作成C
的新值之前,把到跟隨在sel
之後指令的指針保存於轉儲之上。join
:從轉儲彈出一個列表引用,並使其成為C
的新值。這個指令出現在sel
的兩個可選列表的結束。ldf
:接受表示一個函數的一個列表實際參數。它構造一個閉包(包含這個函數和當前環境的有序對),並把它壓入堆疊。ap
:從堆疊彈出一個閉包和一個形式參數值的列表。通過安裝這個閉包的環境作為當前環境,將這個形式參數列表壓入到環境列表之前,清除堆疊,並設置C
為這個閉包的函數指針,將這個閉包應用於這些形式參數。以前的S
、E
和C
的下一個值被保存於轉儲之上。ret
:從堆疊彈出一個返回值,從轉儲恢復S
、E
和C
,並把這個返回值壓入新的當前堆疊。dum
:將一個虛設(dummy)即空列表,壓入到環境列表之前。rap
:作用如同ap
,只是它將出現的虛設環境替代為當前的環境,這使得遞歸函數成為可能。
還存在一些用於基本函數的指令,比如car
、cdr
、列表構造、整數加法、I/O等。它們都從堆疊得到任何必須的實際參數。
Remove ads
參見
- ISWIM
- Krivine機
引用
延伸閱讀
外部連結
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads