同像性




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


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


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




目录






  • 1 歷史


  • 2 用途,優缺點


  • 3 實作方法


    • 3.1 同像性語言的編程範例


      • 3.1.1 Lisp


      • 3.1.2 Prolog


      • 3.1.3 Rebol






  • 4 另見


  • 5 参考文献


  • 6 外部連結





歷史


同像性一詞的原始來源是論文《編譯器語言的巨集指令擴展》。根據早期具影響力的論文《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)




外部連結








Popular posts from this blog

Daylamites

Czechs

Lambaréné