热门问题
时间线
聊天
视角

同像性

来自维基百科,自由的百科全书

Remove ads

在计算机编程中,同像性(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。两者都是面向功能性的(一为列表,另一为字符串),都用一种语言与用户交谈,并且都具有 “同像性”,因为它们内部和外部表示本质上相同。它们都具有动态创建新函数的能力,然后可随着用户的喜好而精工细作。它们唯一最大的缺点是,以它们写出的程序看起来就像,苏美尔人把布尔那·布里亚什国王的信写成巴比伦楔形文![...]

Remove ads

用途及优点

同像性的一个优点是,向这个语言扩展新概念变得更加简单,因为表示代码的资料,可在程序的层和基础层之间传递。函数的抽象语法树,可以作为元层中的数据结构来合成和操纵,然后再被求值。它可以更容易理解如何操纵代码,因为它可以被理解为简单的资料(因为语言本身的格式同于资料格式)。

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

实作方法

所有范纽曼型架构的系统,其中包括绝大多数当今的通用计算机,由于原始机器代码在记忆体中的执行方式,其资料类型是字节,故而可以隐含地描述为具有同像性。但是这个特征也可以在编程语言层别上抽象出来。

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
; 傳回一個其中有列表,數字和符號的列表
Remove ads

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函数。

Remove ads

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
Remove ads

另见

参考文献

外部链接

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads