同像性

维基百科,自由的百科全书
跳转至: 导航搜索

在计算机编程中,同像性(homoiconicity,來自希臘語單詞 homo,意為與符號含義表示相同)是某些编程语言的特殊屬性,它意味着一个程序的结构与其句法是相似的,因此易于通过阅读程序来推测程序的内在涵义。如果一门编程语言具备了同像性,说明该语言的文本表示(通常指源代码)與其抽象語法樹(AST)具有相同的結構(即,AST 和語法是同形的)。该特性允許使用相同的表示語法,將語言中的所有代碼當成資料,來存取以及轉換,提供了“代码即数据”的理论前提。

同像語言中,程序的主要呈現方式,也是語言本身原始類型中的資料結構。這使得元編程(metaprogramming)更加容易,因為程序代碼可以被視為資料:語言中的反射(運行時檢查程序的實體)取決於單一的、性質相同的結構,而且它不必去處理,其它一些不同結構所導致的複雜語法。換句話說,同像性是程序的源代碼即是基本的資料結構,而這個語言本身知道如何存取源碼的文本。

Lisp編程语言是具有同像性質的典型範例[1]。它的設計很容易進行對列表的操作,而且其語法結構即採用嵌套列表形式的S-表达式。LISP 程式以列表的形式來編寫; 所以可在運行時存取本身擁有的函數和程序,並以編程的方式重新設計自己。具有同像屬性的語言通常有對句法巨集的全面支持,允許程序員以簡明的方式來表達程序的變換。這類語言有 Clojure(現代流行的 LISP 方言),Rebol 和 Refal,以及最近的 Julia 等等編程語言。

歷史[编辑]

同像性一詞的原始來源是論文《編譯器語言的巨集指令擴展》。根據早期具影響力的論文《TRAC,文本處理語言》中提到: “主要設計目標之一是TRAC的輸入腳本(用戶所輸入的)應該與指示TRAC處理器內部動作的文本相同一致。換句話說,TRAC程序應該是以字串被儲存於記憶體中,正如同用戶在鍵盤上鍵入它們一樣。如果TRAC程序本身發展成為新的程序,同一個腳本中也應該陳述列出這些新程序。TRAC處理器在其操作中將此腳本直譯為其程序。換句話說,TRAC解析器(處理器)有成效地將計算機轉換為具有新程序語言(TRAC 語言)的新計算機。程序或過程資訊在任何時候的呈現,都應該相同於TRAC處理器執行期間對其作用的形式。我們期望內部代碼的字符表示,和外部代碼表示相同一致或非常相似。在本TRAC實作中內部字符立基於ASCII,因為TRAC程序和文本在處理器內部和外部都具有相同的表示,所以術語同像性一詞是適用的,從涵義相同於符號呈現的意義。[...]”

依照道格拉斯·麥克羅伊的提議,依據Peirce,C.S.McIlroy M.D.的術語,“編譯器語言的巨集指令擴展”,ACM通訊,頁214-220; 1960年4月。

艾倫·凱在他1969年的博士論文中使用並可能推廣了同像性這個術語: “所有先前的系統之外,顯著的一組例外是Interactive LISP[...]和TRAC。兩者都是函數導向的(一為列表,另一為字符串),都用一種語言與用戶交談,並且都具有 “同像性的”,因為它們內部和外部表示本質上相同。它們都具有動態創建新函數的能力,然後可隨著用戶的興趣進階發展。他們唯一最大的缺點是,以它們寫出的程序看起來像是蘇美人把Burniburiach國王的信寫成巴比倫楔形文![...]”

用途,優缺點[编辑]

同像性的一個優點是,以新概念擴展語言通常變得更簡單,因為表示代碼的資料可在程序的元和基本層之間傳遞。函數的抽象語法樹可以作為元層中的資料結構來組成和操作,然後被評估。它可以更容易理解如何操作代碼,因為它可以被理解為簡單的資料(語言本身的格式亦同為資料格式)。

允許這樣做的簡單性也帶來了一個缺點:一個博客認為,至少在類似LISP的列表導向的語言的情況下,它會消除許多能幫助人們分析語言結構的視覺線索,而可能導致陡峭的學習曲線。參見文章 “The Lisp Curse”。

同像性的典型演示是元循環求值(meta-circular evaluator, 同REPL)。

實作方法[编辑]

所有范紐曼型架構的系統,其中包括絕大多數當今的通用計算機,由於原始機器代碼在記憶體中執行的資料類型是位元組,可以隱含地描述為具有同像性。但是這個功能也可以在編程語言層別就抽取出來。

Lisp及其方言例如Scheme,Clojure,Racket等,使用S-表達式來實現同像性。

同像性語言的編程範例[编辑]

Lisp[编辑]

Lisp使用S-表達式作為資料和源碼的外部表示。可以用基本函數READ讀取S-表達式。READ回傳Lisp資料:列表,符號,數字,字串。基本函數EVAL使用以資料形式呈現的Lisp源碼,計算副作用並得出返回結果。結果由基本函數PRINT打印出來,從Lisp資料產生一個外部的S-表達式。

Lisp資料是含有不同類型的列表:(子)列表,符號,字串和整數。

((:name "john" :age 20) (:name "mary" :age 18) (:name "alice" :age 22))

以下Lisp源碼範例使用列表,符號和數字。

(* (sin 1.1) (cos 2.03))      ; 中綴表示法為 sin(1.1)*cos(2.03)

使用基本函數LIST產生上面的表達式,並將變量EXPRESSION設置為結果

(setf expression  (list '* (list 'sin 1.1) (list 'cos 2.03)) )  
-> (* (SIN 1.1) (COS 2.03))    ; Lisp傳回並打印結果

(third expression)    ; 表達式中的第三項
-> (COS 2.03)

COS這項變更為SIN

(setf (first (third expression)) 'SIN)
; 變更之後的表達式為 (* (SIN 1.1) (SIN 2.03)).

評估表達式

(eval expression)
-> 0.7988834

將表達式打印到字串

(print-to-string expression)
->  "(* (SIN 1.1) (SIN 2.03))"

從字串中讀取表達式

(read-from-string "(* (SIN 1.1) (SIN 2.03))")
->  (* (SIN 1.1) (SIN 2.03))     ; 傳回一個其中有列表,數字和符號的列表

Prolog[编辑]

Rebol[编辑]

另見[编辑]

參考[编辑]

注释[编辑]

  1. ^ Lisp and TRAC...both are “homoiconic” in that their internal and external representations are essentially the same.Alan Kay (1969) The Reactive Engine, PhD thesis (Accessed 20061229)

外部連結[编辑]