在計算機編程中,同像性(homoiconicity來自希臘語單詞,homo-意為相同,icon含義表像),是某些編程語言的特殊屬性,這意味着用此語言書寫的程序,可用使用這個語言將其作為數據來操縱,因此只要閱讀程序自身,就能推論出來這個程序的內部表示。該屬性經常被歸結成,這個語言將「代碼當作資料」。

簡介

在同像性編程語言中,程序的主要表示方式,也是屬於這個語言自身的原始類型的一種資料結構。這使得在這種語言中的元編程,比在沒有這個屬性的語言中要更加容易:在這種語言中的反射(在運行時檢查程序的實體),取決於單一的、同質的結構,而且它不必去處理以複雜語法形式出現的其它一些結構。同像性語言典型的包括對語法宏的完全支持,這允許編程者以簡明方式來表達程序變換。

Lisp編程語言,是具有同像屬性的典型範例,它設計得易於進行列表操縱,而且其結構用具有嵌套列表形式的S-表達式來給出,它可以由其他LISP代碼來操縱[1]。這類語言的其他例子有Clojure(一種現代流行的LISP方言),RebolRefal英語Refal,以及最近的Julia等編程語言。

歷史

同像性一詞的原始來源,是論文《編譯器語言的巨集指令擴展》[2]。其依據是早期具影響力的論文《TRAC英語TRAC (programming language)文本處理語言》[3]

TRAC的主要設計目標之一,是其輸入腳本(用戶所輸入),應該同一於指示TRAC處理器內部動作的文本。換句話說,TRAC過程應該是以字串形式儲存於記憶體中,正如同用戶在鍵盤上鍵入的那樣。如果TRAC過程本身演化出新的過程,這些新過程也應該在同一個腳本中陳述出來。TRAC處理器在其動作中,將此腳本解釋為它的程序。換句話說,TRAC翻譯器程序(處理器),將這個計算機有成效地轉換為,具有新程序語言即TRAC語言的新計算機。在任何時候,程序或過程資訊都應當能夠,以同於TRAC處理器在執行期間作用於其上的形式來顯示出來。我們期望內部的字符代碼表示,同一於或非常相似於,外部的代碼表示。在當前的TRAC實作中,內部字符表示基於ASCII,因為TRAC過程和文本,在處理器內部和外部,都具有相同的表示,所以術語同像性(homoiconic)一詞是適用的,homo涵義相同,icon義為表像。

[...]

跟從沃倫·麥卡洛克的提議,依據查爾斯·桑德斯·皮爾士的術語,參見道格拉斯·麥克羅伊的「編譯器語言的巨集指令擴展」,ACM通訊,頁214-220; 1960年4月。

艾倫·凱在他1969年的博士論文中,使用並可能由此推廣了同像性這個術語[4]

所有先前的系統中,顯著的一組例外是Interactive LISP[...]和TRAC。兩者都是面向功能性的(一為列表,另一為字符串),都用一種語言與用戶交談,並且都具有 「同像性」,因為它們內部和外部表示本質上相同。它們都具有動態創建新函數的能力,然後可隨著用戶的喜好而精工細作。它們唯一最大的缺點是,以它們寫出的程序看起來就像,蘇美爾人把布爾那·布里亞什國王的信寫成巴比倫楔形文![...]

用途及優點

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

同像性的典型演示是元循環求值器

實作方法

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

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

其他被認為具有同像性的語言包括:

同像性語言的編程範例

Lisp

Lisp使用S-表達式作為資料和源碼的外部表示。S-表達式可以用原始Lisp函數READ讀取。READ返回Lisp資料:列表、符號、數字和字串。原始Lisp函數EVAL使用以資料形式表示的Lisp代碼,計算副作用並得出返回結果。結果由原始Lisp函數PRINT打印出來,它從Lisp資料產生一個外部的S-表達式。下面示例採用Common LispSBCL實現。

以下Lisp示例,構造出的列表含有結構類型person:它有兩個屬性nameage,其類型分別是字符串和整數:

* (defstruct person name age)
PERSON

* (person-name (cadr (list (make-person :name "john" :age 20) (make-person :name "mary" :age 18) (make-person :name "alice" :age 22))))
"mary"

以下Lisp代碼示例,使用了列表、符號和數值:

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

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

* (defvar expression)
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
; 變更之後的表達式為 (* (SIN 1.1) (SIN 2.03)).

求值表達式:

* (eval expression)
0.79888344

將表達式打印到字串:

* (princ-to-string expression)
"(* (SIN 1.1) (SIN 2.03))"

從字串中讀取表達式:

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

Prolog

Prolog是同像性語言並且提供了很多反射設施。

1 ?- X is 2*5.
X = 10.

2 ?- L = (X is 2*5), write_canonical(L).
is(_, *(2, 5))
L = (X is 2*5).

3 ?- L = (ten(X):-(X is 2*5)), write_canonical(L).
:-(ten(A), is(A, *(2, 5)))
L = (ten(X):-X is 2*5).

4 ?- L = (ten(X):-(X is 2*5)), assert(L).
L = (ten(X):-X is 2*5).

5 ?- ten(X).
X = 10.

6 ?-

在第4行建立一個新子句。算符:-分隔一個子句的頭部和主體。通過assert/1將它增加到現存的子句中,即增加它到「數據庫」,這樣我們可以以後調用它。在其他語言中可以稱為「在運行時間建立一個函數」。還可以使用abolish/1retract/1從數據庫中移除子句。注意在子句名字後的數,是它可以接受的實際參數的數目,它也叫做元數

我們可以查詢數據庫來得到一個子句的主體:

7 ?- clause(ten(X),Y).
Y = (X is 2*5).

8 ?- clause(ten(X),Y), Y = (X is Z).
Y = (X is 2*5),
Z = 2*5.

9 ?- clause(ten(X),Y), call(Y).
X = 10,
Y = (10 is 2*5).

call類似於Lisp的eval函數。

Rebol

Rebol可巧妙的演示將代碼當作數據來操縱和求值的概念。Rebol不像Lisp,不要求用圓括號來分隔表達式。下面是Rebol代碼的例子,注意>>表示解釋器提示符,出於可讀性而在某些元素之間增加了空格:

>> repeat i 3 [ print [ i "hello" ] ]
1 hello
2 hello
3 hello

在Rebol中repeat事實上是內建函數而非語言構造或關鍵字。通過將代碼包圍在方括號中,解釋器不求值它,而是將它當作包含字的塊:

[ repeat i 3 [ print [ i "hello" ] ] ]

這個塊有類型block!,並且使用近乎賦值的語法,可以進一步的將它指定為一個字的值,這種語法實際上可以被解釋器理解為特殊類型set-word!,並採用一個字跟隨一個冒號的形式:

>> block1: [ repeat i 3 [ print [ i "hello" ] ] ] ;; 将这个块的值赋值给字`block1`
== [repeat i 3 [print [i "hello"]]]
>> type? block1 ;; 求值字`block1`的类型
== block!

這個塊仍可以使用Rebol中提供的do函數來解釋,它類似於Lisp中的eval。有可能審查塊的元素並變更它們的值,從而改變要求值代碼的行為:

>> block1/3 ;; 这个块的第三个元素
== 3
>> block1/3: 5 ;; 设置第三个元素的值为5
== 5
>> probe block1 ;; 展示变更了的块
== [repeat i 5 [print [i "hello"]]]
>> do block1 ;; 求值这个块
1 hello
2 hello
3 hello
4 hello
5 hello

另見

參考文獻

外部連結

Wikiwand in your browser!

Seamless Wikipedia browsing. On steroids.

Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.

Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.