同像性

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

**簡介**
在同像性編程語言中，程序的主要表示方式，也是屬於這個語言自身的原始類型的一種資料結構。這使得在這種語言中的元編程，比在沒有這個屬性的語言中要更加容易：在這種語言中的反射（在運行時檢查程序的實體），取決於單一的、同質的結構，而且它不必去處理以複雜語法形式出現的其它一些結構。同像性語言典型的包括對語法宏的完全支持，這允許編程者以簡明方式來表達程序變換。
Lisp編程語言，是具有同像屬性的典型範例，它設計得易於進行列表操縱，而且其結構用具有嵌套列表形式的S-表達式來給出，它可以由其他LISP代碼來操縱。這類語言的其他例子有Clojure（一種現代流行的LISP方言），Rebol和，以及最近的Julia等編程語言。

**歷史**
同像性一詞的原始來源，是論文《編譯器語言的巨集指令擴展》。其依據是早期具影響力的論文《TRAC文本處理語言》：
艾倫·凱在他1969年的博士論文中，使用並可能由此推廣了同像性這個術語：quote|所有先前的系統中，顯著的一組例外是Interactive LISP...和TRAC。兩者都是面向功能性的（一為列表，另一為字符串），都用一種語言與用戶交談，並且都具有 “同像性”，因為它們內部和外部表示本質上相同。它們都具有動態創建新函數的能力，然後可隨著用戶的喜好而精工細作。它們唯一最大的缺點是，以它們寫出的程序看起來就像，蘇美爾人把布爾那·布里亞什國王的信寫成巴比倫楔形文！...

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

**實作方法**
所有範紐曼型架構的系統，其中包括絕大多數當今的通用計算機，由於原始機器代碼在記憶體中的執行方式，其資料類型是位元組，故而可以隱含地描述為具有同像性。但是這個特徵也可以在編程語言層別上抽象出來。
Lisp及其方言例如Scheme，Clojure，Racket等，使用S-表達式來實現同像性。
其他被認為具有同像性的語言包括：- Curl- Elixir- Io- Julia- Prolog- R- Rebol- Red- SNOBOL- Tcl- XSLT- Rexx- Wolfram語言

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

**Lisp**
Lisp使用S-表達式作為資料和源碼的外部表示。S-表達式可以用原始Lisp函數READ讀取。READ返回Lisp資料：列表、符號、數字和字串。原始Lisp函數EVAL使用以資料形式表示的Lisp代碼，計算副作用並得出返回結果。結果由原始Lisp函數PRINT打印出來，它從Lisp資料產生一個外部的S-表達式。下面示例採用Common Lisp的SBCL實現。
以下Lisp示例，構造出的列表含有結構類型person：它有兩個屬性name和age，其類型分別是字符串和整數：- (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/1或retract/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

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

**參考文獻**
 引用

**外部連結**
  Definition of Homoiconic at the C2 Wiki date=20110515022228