Homoiconicity,它是如何工作的?

问题描述 投票:38回答:6

有人可以建议解释同性恋概念的文章,特别是使用Clojure。为什么Clojure是同性的,但在其他语言如Java中很难做到这一点?

lisp clojure
6个回答
24
投票

在我继续处理一些事情之前,我想添加另一个答案,这里还有一个参考 - 与homoiconicity相关的部分相当短,但Rich Hickey正在做解释!第9频道有Rich Hickey的this nice video和Brian Beckman谈论Clojure。可以理解的是,并发性是主要的焦点,但同性化确实得到了自己的(短)屏幕时间,在此期间,Rich很好地解释了read(将程序员写下的具体语法转换为内部表示的函数)之间的相互作用。从列表等)和eval。他有这个漂亮的图表显示eval从未知道它评估的代码来自read在文本文件上操作...... Arthur已经解释了背后的要点,但是嘿,无论如何看,它是一个非常好的视频!


免责声明:我将在下一个单杠下面提到Java和Python作为示例。我想说清楚以下只是一个粗略的草图,为什么我认为制作一个homoiconic,Lisp风格的宏启用的Java或Python可能很困难;不过,这只是一个学术练习,我不想考虑是否有任何理由首先尝试。另外,我不想暗示具有Lisp样式宏的语言的语法必须包含树结构的显式分隔符; Dylan(无表情的Lisp?)显然提供了一个反例。最后,我使用表达式Lisp样式宏,因为我只检查Lisp样式宏。例如,Forth语言有一个不同的宏设施,除了我知道它可以启用邪恶酷炫的代码之外,我并不理解。显然,语法扩展可以通过多种方式实现。随着这个...


我想谈谈你的问题的第二部分 - 大多数编程语言被认为是不是同性的?我将不得不在这个过程中触及Lisp的语义,但是因为Nils已经提供了关于术语“homoiconic”本身的良好信息源的链接,并且Arthur已经描述了read - > macro expand - > compile cycle as found在Clojure中,我将在接下来的基础上进行构建。首先,让我引用Alan Kay的一篇文章(摘自Wikipedia文章,该文章也链接到原始来源):

[...]互动LISP和TRAC都是“同性恋”,因为它们的内部和外部表示基本相同。

(那些[...]位隐藏了很多文本,但要点没有改变。)

现在,让我们自问一个问题:Java的Java内部表示是什么? ......好吧,这甚至没有意义。 Java编译器确实具有Java的某种内部表示,即抽象语法树;为了构造一个“homoiconic Java”,我们必须使AST表示成为Java中的第一类对象,并设计一种允许我们直接编写AST的语法。这可能会相当困难。

Python提供了一个非同音语言的例子,它有趣的是它目前附带了一个以ast模块形式的AST操作工具包。该模块的文档明确声明Python AST可能在发布之间发生变化,这可能会也可能不会令人沮丧;仍然,我想一个勤奋的程序员可以采用ast模块,设计一种语法(可能是基于S表达式,可能是基于XML的)直接描述Python AST并使用ast在常规Python中构造该语法的解析器,从而获得可靠的使用Python语义创建同色语言的第一步。 (我相信我前段时间遇到过Lisp编译为Python字节码的方言......我想知道它是否会在某种程度上做类似的事情?)

即使这样,问题仍然是从这种同质性中提取具体的好处。它被视为Lisp语言系列成员的有益属性,因为它允许我们编写编写更多程序的程序,其中宏是最值得注意的。现在,虽然通过在Lisp中操作Lisp代码的内部表示非常容易,但是以一种方式启用宏,而Lisp执行模型也以同样重要的方式启用它们:Lisp程序只是一个集合Lisp形式;它们由Lisp函数eval处理,它负责确定表达式的值并在正确的时间产生适当的副作用; Lisp的语义正是eval的语义。内部工作如何在保持这种语义错觉的同时快速合理的问题是一个实现细节;一个Lisp系统有义务将一个函数eval暴露给程序员,并且好像该函数正在处理Lisp程序一样。

在现代Lisp系统中,它是eval合同的一部分,它执行额外的预处理阶段,在此阶段,在评估代码(或编译和运行,视情况而定)之前扩展宏。那个特殊的工具不是Lisp系统的必要部分,但它很容易插入到这个执行模型中!另外,我想知道这不是唯一使Lisp类宏转换可管理的执行模型,这意味着任何寻求合并Lisp风格宏的语言都必​​须采用类似的执行模型。我的直觉告诉我,情况确实如此。

当然,一旦用符号直接写入与AST并行的语言并使用类似Lisp的执行模型和评估器函数/对象,人们必须想知道它是否是任何机会Lisp的另一种方言......即使它AST并行语法恰好是基于XML的。不寒而栗


7
投票

当我学习Lisp时,当我了解到lisp在两个阶段“编译”,读取和编译以及代码用相同的数据结构表示时,同理性的想法是有意义的:

  • 首先你想到你头脑中的s表达
  • 然后在文件中键入s-expression作为字符
  • 然后读者将文件中的字符转换为s表达式。它不是编译程序,只是从字符构建数据结构,这是阅读阶段的一部分。
  • 然后读者查看每个表达式并确定它们是否是宏,如果是,则运行宏以生成另一个s表达式。所以在这一点上我们已经从s表达式转换为字符到s表达式,然后从s表达式转到不同的s表达式。
  • 然后将这些s表达式编译成.class文件,这些文件可以由jvm运行,这是“编译”阶段的第二个。

因此,从你的大脑到.class文件,它几乎都是s表达式。你甚至可以编写写s表达式的s表达式。所以你可以说“代码是数据”或“代码就是数据”,因为这听起来更好。


5
投票

“homoiconicity”的整个想法略有混淆,并不适合Lisp。 Lisp中的内部和外部表示不同。外部表示基于文件中的字符。内部表示基于Lisp数据(数字,字符串,列表,数组......),并且是非文本的。这和人物一样怎么样?有内部表示,没有相应的外部表示(例如编译代码,闭包,......)。

Lisp和许多其他编程语言之间的主要区别在于,Lisp有一个简单的源代码数据表示 - 一个不基于字符串的数据表示。

显然,代码可以在基于文本的编程语言中表示为字符串。但是在Lisp中,源可以用原始Lisp数据结构来表示。外部表示基于s表达式,s表达式是将分层数据表示为文本的简单模型。内部模型的表示基于列表等。

这就是评估者得到的:内部表征。不是1到1版本的文本输入,但已解析。

基本型号:

  • READ将外部s表达式转换为数据
  • EVAL以Lisp数据的形式获取Lisp表单并对其进行评估
  • PRINT将Lisp数据转换为外部s表达式

请注意,READ和PRINT适用于任意Lisp数据,它具有打印表示和读取器,而不仅适用于Lisp表单。根据定义,表单是Lisp编程语言中的有效表达式。


4
投票

这是一个做符号分化的简短程序。这是LISP操纵自己的代码的一个例子。尝试将其翻译成另一种语言,看看为什么LISP对这类事情有好处。

;; The simplest possible symbolic differentiator

;; Functions to create and unpack additions like (+ 1 2)
(defn make-add [ a b ] (list '+ a b))
(defn addition? [x] (and (=(count x) 3) (= (first x) '+)))
(defn add1   [x] (second x))
(defn add2   [x] (second (rest x)))

;; Similar for multiplications (* 1 2)
(defn make-mul [ a b ] (list '* a b))
(defn multiplication? [x] (and (=(count x) 3) (= (first x) '*)))
(defn mul1   [x] (second x))
(defn mul2   [x] (second (rest x)))

;; Differentiation. 
(defn deriv [exp var]
  (cond (number? exp) 0                                                              ;; d/dx c -> 0
        (symbol? exp) (if (= exp var) 1 0)                                           ;; d/dx x -> 1, d/dx y -> 0
        (addition? exp) (make-add (deriv (add1 exp) var) (deriv (add2 exp) var))     ;; d/dx a+b -> d/dx a + d/dx b
        (multiplication? exp) (make-add (make-mul (deriv (mul1 exp) var) (mul2 exp)) ;; d/dx a*b -> d/dx a * b + a * d/dx b
                                        (make-mul (mul1 exp) (deriv (mul2 exp) var)))
        :else :error))

;;an example of use: create the function x -> x^3 + 2x^2 + 1 and its derivative 
(def poly '(+ (+ (* x (* x x)) (* 2 (* x x))) 1))

(defn poly->fnform [poly] (list 'fn '[x] poly))

(def polyfn  (eval (poly->fnform poly)))
(def dpolyfn (eval (poly->fnform (deriv poly 'x))))

2
投票

正如Rainer Joswig指出的那样,有充分的理由怀疑同质性这一概念的实用性,以及Lisps是否实际上是同构的。

homoiconiticy的原始定义集中在语言的内部和外部表示之间的相似性。规范的例子是Lisp,带有s表达式。

该定义和示例选择存在(至少)两个问题。

第一个异议涉及外部代表。在Lisp的情况下,我们假设外部表示是一个s表达式。然而,在大多数实际编程环境中,程序源的实际表示是包含字符串的文本文件。只有在解析了这个文本之后,表示才真正是一个s表达式。换句话说:在实际环境中,外部表示不是s表达式,而是文本。

第二个反对意见涉及内部代表。由于性能原因,Lisp解释器的实际实现通常不会在内部直接对s表达式进行操作。尽管可以根据对s表达式的案例分析来定义Lisp,但通常不会这样实现。因此,内部表示在实践中实际上不是s表达式。

事实上,人们甚至可能围绕同质性的概念提出进一步的问题:对于一个封装良好的机器,我们无法通过定义观察其内部运作;在该视图中,对机器的内部表示做出任何陈述都是毫无意义的。更一般地说,原始定义存在的问题是,程序的单个外部和单个内部表示的想法与现实不匹配。实际上,存在一整套表示,包括程序员大脑中的电子,从屏幕发出的光子,程序文本,机器代码以及在CPU中移动的电子。

我在一篇名为Don't say “Homoiconic”的文章中更广泛地写了这篇文章


1
投票

它似乎很明显,但第一个来源可能是:

http://en.wikipedia.org/wiki/Homoiconicity

http://c2.com/cgi/wiki?DefinitionOfHomoiconic

通常解释同质性,您也可以找到原始来源。正如使用Lisp的例子所解释的那样,它离Clojure不远。

© www.soinside.com 2019 - 2024. All rights reserved.