同像性

维基百科,自由的百科全书
跳转至: 导航搜索

在计算机编程中,同像性(homoiconicity,来自希腊语单词 homo,意为与符号含义表示相同)是某些编程语言的特殊属性,它意味着一个程序的结构与其句法是相似的,因此易于通过阅读程序来推测程序的内在涵义。如果一门编程语言具备了同像性,说明该语言的文本表示(通常指源代码)与其抽象语法树(AST)具有相同的结构(即,AST 和语法是同形的)。该特性允许使用相同的表示语法,将语言中的所有代码当成资料,来存取以及转换,提供了“代码即数据”的理论前提。

同像语言中,程序的主要呈现方式,也是语言本身原始类型中的资料结构。这使得元编程(metaprogramming)更加容易,因为程序代码可以被视为资料:语言中的反射(运行时检查程序的实体)取决于单一的、性质相同的结构,而且它不必去处理,其它一些不同结构所导致的复杂语法。换句话说,同像性是程序的源代码即是基本的资料结构,而这个语言本身知道如何存取源码的文本。

Lisp编程语言是具有同像性质的典型范例[1]。它的设计很容易进行对列表的操作,而且其语法结构即采用嵌套列表形式的S-表达式。LISP 程式以列表的形式来编写; 所以可在运行时存取本身拥有的函数和程序,并以编程的方式重新设计自己。具有同像属性的语言通常有对句法巨集的全面支持,允许程序员以简明的方式来表达程序的变换。这类语言有 Clojure(现代流行的 LISP 方言),Rebol 和 Refal,以及最近的 Julia 等等编程语言。

历史[编辑]

同像性一词的原始来源是论文《编译器语言的巨集指令扩展》。根据早期具影响力的论文《TRAC,文本处理语言》中提到: “主要设计目标之一是TRAC的输入脚本(用户所输入的)应该与指示TRAC处理器内部动作的文本相同一致。换句话说,TRAC程序应该是以字串被储存于记忆体中,正如同用户在键盘上键入它们一样。如果TRAC程序本身发展成为新的程序,同一个脚本中也应该陈述列出这些新程序。TRAC处理器在其操作中将此脚本直译为其程序。换句话说,TRAC解析器(处理器)有成效地将计算机转换为具有新程序语言(TRAC 语言)的新计算机。程序或过程资讯在任何时候的呈现,都应该相同于TRAC处理器执行期间对其作用的形式。我们期望内部代码的字符表示,和外部代码表示相同一致或非常相似。在本TRAC实作中内部字符立基于ASCII,因为TRAC程序和文本在处理器内部和外部都具有相同的表示,所以术语同像性一词是适用的,从涵义相同于符号呈现的意义。[...]”

依照道格拉斯·麦克罗伊的提议,依据Peirce,C.S.McIlroy M.D.的术语,“编译器语言的巨集指令扩展”,ACM通讯,页214-220; 1960年4月。

艾伦·凯在他1969年的博士论文中使用并可能推广了同像性这个术语: “所有先前的系统之外,显著的一组例外是Interactive LISP[...]和TRAC。两者都是函数导向的(一为列表,另一为字符串),都用一种语言与用户交谈,并且都具有 “同像性的”,因为它们内部和外部表示本质上相同。它们都具有动态创建新函数的能力,然后可随著用户的兴趣进阶发展。他们唯一最大的缺点是,以它们写出的程序看起来像是苏美人把Burniburiach国王的信写成巴比伦楔形文![...]”

用途,优缺点[编辑]

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

允许这样做的简单性也带来了一个缺点:一个博客认为,至少在类似LISP的列表导向的语言的情况下,它会消除许多能帮助人们分析语言结构的视觉线索,而可能导致陡峭的学习曲线。参见文章 “The Lisp Curse”。

同像性的典型演示是元循环求值(meta-circular evaluator, 同REPL)。

实作方法[编辑]

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

Lisp及其方言例如Scheme,Clojure,Racket等,使用S-表达式来实现同像性。

同像性语言的编程范例[编辑]

Lisp[编辑]

Lisp使用S-表达式作为资料和源码的外部表示。可以用基本函数READ读取S-表达式。READ回传Lisp资料:列表,符号,数字,字串。基本函数EVAL使用以资料形式呈现的Lisp源码,计算副作用并得出返回结果。结果由基本函数PRINT打印出来,从Lisp资料产生一个外部的S-表达式。

Lisp资料是含有不同类型的列表:(子)列表,符号,字串和整数。

((:name "john" :age 20) (:name "mary" :age 18) (:name "alice" :age 22))

以下Lisp源码范例使用列表,符号和数字。

(* (sin 1.1) (cos 2.03))      ; 中綴表示法為 sin(1.1)*cos(2.03)

使用基本函数LIST产生上面的表达式,并将变量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 1.1) (SIN 2.03)).

评估表达式

(eval expression)
-> 0.7988834

将表达式打印到字串

(print-to-string expression)
->  "(* (SIN 1.1) (SIN 2.03))"

从字串中读取表达式

(read-from-string "(* (SIN 1.1) (SIN 2.03))")
->  (* (SIN 1.1) (SIN 2.03))     ; 傳回一個其中有列表,數字和符號的列表

Prolog[编辑]

Rebol[编辑]

另见[编辑]

参考[编辑]

注释[编辑]

  1. ^ Lisp and TRAC...both are “homoiconic” in that their internal and external representations are essentially the same.Alan Kay (1969) The Reactive Engine, PhD thesis (Accessed 20061229)

外部链接[编辑]