同像性

在计算机编程中，同像性（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