懒评估与macros
我习惯了从Haskell的懒惰评估,并发现自己现在已经使用懒惰评估正确地使用默认的急切语言激怒。 这实际上是非常有害的,因为我使用的其他语言主要是懒散地评估一些非常尴尬的东西,通常涉及到自定义迭代器等等。 所以,仅仅通过获取一些知识,我实际上使自己的原有语言的生产力下降 。 叹。
但是我听说ASTmacros提供了另一种干净的方式来做同样的事情。 我经常听到类似“懒惰的评估使得macros冗余”的说法,反之亦然,主要来自Lisp和Haskell社区的争吵。
我已经涉足了各种Lisp变体的macros。 他们看起来像是一个真正有组织的方式来复制和粘贴大量的代码,以便在编译时处理。 他们当然不是Lispers喜欢认为的圣杯。 但是,这几乎可以肯定是因为我不能正确使用它们。 当然,让macros观系统工作在相同的核心数据结构上,这种语言本身就是非常有用的,但它基本上仍然是复制和粘贴代码的有组织的方式。 我承认,将macros系统放在与允许完整运行时变更的语言相同的AST上是非常有用的。
我想知道的是,macros是如何简明扼要地做懒惰评估呢? 如果我想一行一行地处理一个文件,而不是浑浊起来,我只是返回一个列表,其中有一个线路阅读程序映射到它。 这是DWIM的完美例子(按照我的意思)。 我甚至不必考虑这个问题。
我显然不会得到macros。 我已经使用了它们,并没有特别留下深刻印象的炒作。 所以有些东西我错过了,我没有通过在线阅读文档。 有人可以向我解释这一切吗?
懒惰的评估可以代替macros的某些用途(那些延迟评估来创build控制结构的用法),但是反过来并不是真的。 您可以使用macros来使延迟的评估结构更加透明 – 请参阅SRFI 41(Streams)以获取如何的示例: http : //download.plt-scheme.org/doc/4.1.5/html/srfi-std/srfi -41 / SRFI-41.html
最重要的是,你也可以编写你自己的惰性IO原语。
然而,根据我的经验,在严格的语言中,普遍的惰性代码往往会引入一些开销,而在运行时中普遍存在的惰性代码被devise为从一开始就有效地支持它 – 这真的是一个实现问题。
懒惰的评估使得macros成为冗余
这是纯粹的废话(不是你的错,以前我听说过)。 的确,您可以使用macros来改变expression式评估的顺序,上下文等,但是这是macros的最基本的用法,并且使用ad-hocmacros而不是函数来模拟惰性语言并不方便。 所以如果从这个方向来看macros,你确实会感到失望。
macros是为了用新的句法forms来扩展语言。 macros的一些具体function是
- 影响expression评估的顺序,上下文等。
- 创build新的绑定表单(即影响expression式的范围 )。
- 执行编译时计算,包括代码分析和转换。
(1)的macros可以非常简单。 例如,在Racket中 , with-handlers
的exception处理表单只是一个扩展到call-with-exception-handler
,一些条件和一些延续代码的macros。 它是这样使用的:
(with-handlers ([(lambda (e) (exn:fail:network? e)) (lambda (e) (printf "network seems to be broken\n") (cleanup))]) (do-some-network-stuff))
该macros基于原始的call-with-exception-handler
来实现“在exception的dynamic上下文中的谓词和处理程序子句”的概念,该处理call-with-exception-handler
在引发它们的点处理所有exception。
macros的更复杂的用法是LALR(1)parsing器生成器的实现 。 parser
表单不是需要预处理的单独文件,而是另一种expression式。 它需要一个语法描述,在编译时计算表,并产生一个parsing器函数。 这些动作例程是词汇范围的,所以它们可以引用文件中的其他定义,甚至可以引用lambda
绑定的variables。 您甚至可以在操作例程中使用其他语言扩展。
在极端的情况下, Typed Racket是一个通过macros实现的Rackettypes的方言。 它具有复杂的types系统,旨在匹配球拍/scheme代码的成语,并通过使用dynamic软件合同(也通过macros实现)保护input函数,从而与非types模块进行互操作。 它由一个“types化模块”macros来实现,该types扩展,types检查和转换模块主体以及用于将types信息附加到定义等的辅助macros。
FWIW,还有懒散的球拍 ,一个懒散的球拍方言。 这不是通过将每个函数转换为macros来实现的,而是通过将lambda
, define
和函数应用语法重新绑定到创build和强制许诺的macros。
总之,懒惰的评估和macros有一个小小的交叉点,但是它们是非常不同的东西。 macros并不是懒惰的评价。
懒惰是外延的 ,而macros则不是。 更确切地说,如果将非严格性添加到外延语言,结果仍然是外延的,但是如果添加macros,则结果不是外延的。 换句话说,懒惰纯语言中的expression式的含义仅仅是组件expression式的含义的函数; 而macros可以从语义上相等的参数产生语义上不同的结果。
从这个意义上说,macros是更强大的,而懒惰相应地在语义上更好。
编辑 :更准确地说, 除了关于身份/平凡的外延(“外延”的概念变得空洞的地方) 之外 ,macros是非外延的。
Lisp始于上个世纪50年代末。 查看符号expression式的recursion函数和机器的计算 。 macros不是 Lisp的一部分。 这个想法是用符号expression式来计算的,它可以表示各种公式和程序:mathexpression式,逻辑expression式,自然语言句子,计算机程序,…
后来的Lispmacros被发明出来,它们是Lisp本身的一个应用。macros使用完整的Lisp语言作为转换语言,将Lisp(或Lisp类)expression式转换为其他Lispexpression式。
你可以想象,使用macros你可以实现强大的预处理器和编译器作为Lisp的用户。
典型的Lisp方言使用严格的参数评估:函数的所有参数在函数执行之前被评估。 Lisp也有几个内置表单,它们有不同的评估规则。 IF
就是这样一个例子。 在Common Lisp中IF
是一个所谓的特殊操作符 。
但是我们可以定义一个新的Lisp类(子)语言,它使用惰性评估,我们可以编写macros来将该语言转换为Lisp。 这是一个macros的应用程序,但目前并不是唯一的一个。
对于这样一个使用macros实现代码转换器的Lisp扩展(为数据结构提供延迟评估)的一个例子(比较老)是Common Lisp的SERIES扩展。
macros可以用来处理懒惰的评估,但只是它的一部分。 macros的主要观点是,由于他们基本上没有固定的语言。
如果编程就像玩乐高积木一样,用macros也可以改变砖块的形状或者它们的材质。
macros不仅仅是延迟评估。 那是fexpr
(lisp历史上的一个macros观前兆)。 macros是关于程序重写,其中fexpr
只是一个特例…
作为一个例子,考虑到我在空闲时间写了一个小小的lisp到javascript编译器,最初(在javascript内核中)我只有lambda支持&rest
参数。 现在支持关键字参数,因为我重新定义了lisp本身的lambda意思。
我现在可以写:
(defun foo (xy &key (z 12) w) ...)
并用函数调用
(foo 12 34 :w 56)
当执行该调用时,在函数体中, w
参数将被绑定到56, z
参数被设置为12,因为它没有被传递。 如果不支持的关键字parameter passing给函数,我也会得到一个运行时错误。 我甚至可以通过重新定义编译expression式的方式来添加一些编译时检查支持(即,如果“静态”函数调用表单将正确的parameter passing给函数,则添加检查)。
中心点在于原始(内核)语言完全不支持关键字参数,而且我能够使用语言本身来添加它。 如果它从一开始就是这样的结果, 这只是语言的一部分。
语法是重要的(即使技术上可能只使用图灵机)。 语法形成你的想法。 macros(和macros读)给你完全的语法控制。
关键的一点是,代码重写代码并没有像C ++模板元编程(其中只是使if
成为一个惊人的成就),或者使用低于正则expression式的偶数像C预处理器一样的替代引擎。
代码重写代码使用相同的完整(可扩展)语言。 它是一路下来的;-)
确实写macros比写常规代码更困难; 但这是问题的“基本复杂性”,而不是人为的复杂性,因为您不得不像C ++元编程那样使用哑半语言。
编写macros比较困难,因为代码是一件复杂的事情,当编写macros时,你会编写复杂的东西来构build复杂的东西。 甚至不多上一层,编写macros生成macros(这就是“我正在写代码,写代码的代码来自我”)的旧lisp笑话。
但macros观力量简直是无边无际。