同像性

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

## 簡介

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

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

## 歷史

同像性一詞的原始來源，是論文《編譯器語言的巨集指令擴展》。其依據是早期具影響力的論文《TRAC 文本處理語言》：

> TRAC 的主要設計目標之一，是其輸入腳本（用戶所輸入），應該同一於指示 TRAC 處理器內部動作的文本。換句話說，TRAC 過程應該是以字串形式儲存於記憶體中，正如同用戶在鍵盤上鍵入的那樣。如果 TRAC 過程本身演化出新的過程，這些新過程也應該在同一個腳本中陳述出來。TRAC 處理器在其動作中，將此腳本解釋為它的程序。換句話說，TRAC 翻譯器程序（處理器），將這個計算機有成效地轉換為，具有新程序語言即 TRAC 語言的新計算機。在任何時候，程序或過程資訊都應當能夠，以同於 TRAC 處理器在執行期間作用於其上的形式來顯示出來。我們期望內部的字符代碼表示，同一於或非常相似於，外部的代碼表示。在當前的 TRAC 實作中，內部字符表示基於 ASCII，因為 TRAC 過程和文本，在處理器內部和外部，都具有相同的表示，所以術語同像性（homoiconic）一詞是適用的，homo 涵義相同，icon 義為表示。
> 
> [...]
> 
> 跟從沃倫・麥卡洛克的提議，依據查爾斯・桑德斯・皮爾士的術語，參見道格拉斯・麥克羅伊的「編譯器語言的巨集指令擴展」，ACM 通訊，頁 214-220; 1960 年 4 月。
> 
> 

艾倫・凱在他 1969 年的博士論文中，使用並可能由此推廣了同像性這個術語：

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

## 用途及優點

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

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

## 實作方法

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

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

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

## 同像性語言的編程範例

### Lisp

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

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

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

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

將 `COS` 這項變更為 `SIN`：

求值表達式：

將表達式列印到字串：

從字串中讀取表達式：

### Prolog

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

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

我們可以查詢資料庫來得到一個子句的主體：

`call` 類似於 Lisp 的 `eval` 函數。

### Rebol

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

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

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

這個塊有類型 `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
```

## 另見

* 標記法的認知維度，程式語言語法的設計原理。
* 串接程式語言。
* 面向語言編程。
* 符號式編程。
* 自修改代碼。
* LISP，同像性語言的可能最周知的例子。
* 元編程，對同像性非常有用的編程技術。
* 實化。

## 參考文獻

**引用**

## 外部連結

* Definition of Homoiconic at the C2 Wiki （頁面存檔備份，存於網際網路檔案館）