同像性
在计算机编程中,同像性(homoiconicity来自希腊语单词,homo-意为相同,icon含义表像),是某些编程语言的特殊属性,这意味着用此语言书写的程序,可用使用这个语言将其作为数据来操纵,因此只要阅读程序自身,就能推论出来这个程序的内部表示。该属性经常被归结成,这个语言将“代码当作数据”。
简介
在同像性编程语言中,程序的主要表示方式,也是属于这个语言自身的原始类型的一种资料结构。这使得在这种语言中的元编程,比在没有这个属性的语言中要更加容易:在这种语言中的反射(在运行时检查程序的实体),取决于单一的、同质的结构,而且它不必去处理以复杂语法形式出现的其它一些结构。同像性语言典型的包括对语法宏的完全支持,这允许编程者以简明方式来表达程序变换。
Lisp编程语言,是具有同像属性的典型范例,它设计得易于进行列表操纵,而且其结构用具有嵌套列表形式的S-表达式来给出,它可以由其他LISP代码来操纵[1]。这类语言的其他例子有Clojure(一种现代流行的LISP方言),Rebol和Refal,以及最近的Julia等编程语言。
历史
同像性一词的原始来源,是论文《编译器语言的巨集指令扩展》[2]。其依据是早期具影响力的论文《TRAC文本处理语言》[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。两者都是面向功能性的(一为列表,另一为字符串),都用一种语言与用户交谈,并且都具有 “同像性”,因为它们内部和外部表示本质上相同。它们都具有动态创建新函数的能力,然后可随著用户的喜好而精工细作。它们唯一最大的缺点是,以它们写出的程序看起来就像,苏美尔人把布尔那·布里亚什国王的信写成巴比伦楔形文![...]
用途及优点
同像性的一个优点是,向这个语言扩展新概念变得更加简单,因为表示代码的资料,可在程序的元层和基础层之间传递。函数的抽象语法树,可以作为元层中的资料结构来合成和操纵,然后再被求值。它可以更容易理解如何操纵代码,因为它可以被理解为简单的资料(因为语言本身的格式同于资料格式)。
同像性的典型演示是元循环求值器。
实作方法
所有范纽曼型架构的系统,其中包括绝大多数当今的通用计算机,由于原始机器代码在记忆体中的执行方式,其资料类型是位元组,故而可以隐含地描述为具有同像性。但是这个特征也可以在编程语言层别上抽象出来。
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
,其类型分别是字符串和整数:
* (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
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
另见
参考文献
- 引用
- ^ Wheeler, David A. Readable Lisp S-expressions. [2022-01-29]. (原始内容存档于2022-01-29).
- ^ McIlroy, Douglas. Macro Instruction Extensions of Compiler Languages. Comm. ACM. 1960, 3 (4): 214–220. doi:10.1145/367177.367223.
- ^ Mooers, C.N.; Deutsch, L.P. TRAC, A Text-Handling Language. Proceeding ACM '65 Proceedings of the 1965 20th national conference. 1965: 229–246. doi:10.1145/800197.806048.
- ^ Kay, Alan. The Reactive Engine (学位论文). University of Utah. 1969 [2014-03-28]. (原始内容存档于2018-09-15).
- ^ 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 Homoiconic Languages. [2020-04-23]. (原始内容存档于2013-04-23).
- ^ Lispy Elixir. 8thlight.com. [2022-01-29]. (原始内容存档于2022-03-05).
Elixir, on the surface, is not homoiconic. However, the syntax on the surface is just a facade for the homoiconic structure underneath.
- ^ Why we created Julia. julialang.org. [2020-04-23]. (原始内容存档于2019-02-19).
We want a language that’s homoiconic, with true macros like Lisp, but with obvious, familiar mathematical notation like Matlab.
- ^ metaprogramming. docs.julialang.org. [2020-04-23]. (原始内容存档于2013-05-04).
Like Lisp, Julia represents its own code as a data structure of the language itself.
- ^ Shapiro, Ehud Y.; Sterling, Leon. The art of Prolog: advanced programming techniques. MIT Press. 1994. ISBN 0-262-19338-8.
- ^ R Language Definition (PDF): 6, 2021-05-18 [2022-01-29], (原始内容 (PDF)存档于2022-04-22),
... the semantics are of the FPL (functional programming language) variety with stronger affinities with Lisp and APL. In particular, it allows “computing on the language”, which in turn makes it possible to write functions that take expressions as input, something that is often useful for statistical modeling and graphics.
- ^ "expression: Unevaluated Expressions", [2022-01-29], (原始内容存档于2022-01-29)
- ^ Homoiconic languages (archived), in true Blue blog at Oracle
- ^ Ramsay, S.; Pytlik-Zillig, B. Code-Generation Techniques for XML Collections Interoperability. dh2012 Digital Humanities Conference Proceedings. 2012 [2020-04-23]. (原始内容存档于2016-03-03).
- ^ Metaprogramming in mathematica. Stack Exchange. [2020-04-23]. (原始内容存档于2018-08-20).
Mathematica is [...] Homoiconic language (programs written in own data structures - Mathematica expressions. This is code-as-data paradigm, like Lisp which uses lists for this)
- ^ Notes for Programming Language Experts. Wolfram Language. Wolfram. 2017 [2020-04-23]. (原始内容存档于2022-01-04).
外部链接