在计算机编程中,同像性(homoiconicity,来自希腊语单词 homo,意为与符号含义表示相同)是某些编程语言的特殊属性,它意味着一个程序的结构与其句法是相似的,因此易于通过阅读程序来推测程序的内在涵义。如果一门编程语言具备了同像性,说明该语言的文本表示(通常指源代码)与其抽象语法树(AST)具有相同的结构(即,AST 和语法是同形的)。该特性允许使用相同的表示语法,将语言中的所有代码当成数据,来存取以及转换,提供了“代码即数据”的理论前提。
同像语言中,程序的主要呈现方式,也是语言本身原始类型中的数据结构。这使得元编程(metaprogramming)更加容易,因为程序代码可以被视为资料:语言中的反射(运行时检查程序的实体)取决于单一的、性质相同的结构,而且它不必去处理,其它一些不同结构所导致的复杂语法。换句话说,同像性是程序的源代码即是基本的数据结构,而这个语言本身知道如何存取源码的文本。
Lisp编程语言是具有同像性质的典型范例。它的设计很容易进行对列表的操作,而且其语法结构即采用嵌套列表形式的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使用S-表达式作为资料和源码的外部表示。可以用基本函数READ
读取S-表达式。READ
回传Lisp资料:列表,符号,数字,字串。基本函数EVAL
使用以资料形式呈现的Lisp源码,计算副作用并得出返回结果。结果由基本函数PRINT
打印出来,从Lisp资料产生一个外部的S-表达式。
Lisp资料是含有不同类型的列表:(子)列表,符号,字串和整数。
((:name "john" :age 20) (:name "mary" :age 18) (:name "alice" :age 22))