同像性

在计算机编程中，同像性（homoiconicity來自希臘語單詞，homo-意為相同，icon含義表像），是某些编程语言的特殊屬性，这意味着用此语言书写的程序，可用使用这个语言将其作为数据来操纵，因此只要阅读程序自身，就能推论出来这个程序的内部表示。该属性经常被归结成，这个語言将“代碼當作-{zh-hans:数据;zh-hant:資料}-”。

簡介
在同像性編程語言中，程序的主要表示方式，也是属于这个語言自身的原始類型的一种資料結構。這使得在这种语言中的元編程，比在没有这个属性的语言中要更加容易：在这种語言中的反射（在運行時檢查程序的實體），取決於單一的、同質的結構，而且它不必去處理以複雜語法形式出现的其它一些結構。同像性语言典型的包括对语法宏的完全支持，这允许编程者以简明方式来表达程序变换。

Lisp編程语言，是具有同像属性的典型範例，它設計得易于進行列表操纵，而且其結構用具有嵌套列表形式的S-表达式来给出，它可以由其他LISP代码来操纵。這類語言的其他例子有Clojure（一种現代流行的LISP方言），Rebol和，以及最近的Julia等編程語言。

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

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

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

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

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

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

其他被认为具有同像性的语言包括：

 Curl
 Elixir
 Io
 Julia
 Prolog
 R
 Rebol
 
 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 

編程典範
編程語言語義