Lou*_*uis 47 lisp macros scheme haskell lazy-evaluation
我习惯于从Haskell那里进行懒惰的评估,并且发现自己因为我已经正确地使用了懒惰的评估而感到厌烦.这实际上是非常具有破坏性的,因为我使用的其他语言主要是懒得评估一些非常笨拙的东西,通常涉及自定义迭代器的推出等等.所以只是通过获取一定的了解,其实我已经自己做了少在我原来的语言生产力.叹.
但我听说AST宏提供了另一种干净的方式来做同样的事情.我经常听到诸如"懒惰评估使宏多余"的陈述,反之亦然,主要来自于对Lisp和Haskell社区的争吵.
我已经涉足各种Lisp变种中的宏.它们看起来像是一种非常有组织的复制和粘贴代码块的方式,可以在编译时处理.他们当然不是Lispers认为的圣杯.但这几乎可以肯定是因为我无法正确使用它们.当然,让宏系统在与语言本身组合在一起的相同核心数据结构上工作是非常有用的,但它仍然基本上是一种复制和粘贴代码的有组织方式.我承认,基于与允许完全运行时更改的语言相同的AST的宏系统是强大的.
我想知道的是,如何使用宏来简明扼要地进行懒惰评估呢?如果我想逐行处理文件而不会搞砸整个事情,我只返回一个列表,其中有一个映射到它的行读取例程.这是DWIM的完美例子(尽我所能).我甚至不必考虑它.
我显然没有得到宏.我已经使用过它们并且在炒作时并没有特别留下深刻的印象.因此,我缺少一些我没有通过在线阅读文档获得的东西.有人可以向我解释这一切吗?
Rya*_*per 60
延迟评估使宏变得多余
这纯属无稽之谈(不是你的错;我之前听过).确实,您可以使用宏来更改表达式求值的顺序,上下文等,但这是宏的最基本用法,使用ad-hoc宏而不是函数来模拟惰性语言真的不方便.因此,如果你从那个方向来到宏,你的确会感到失望.
宏用于通过新的语法形式扩展语言.宏的一些特定功能是
做(1)的宏可以非常简单.例如,在Racket中,异常处理表单with-handlers
只是一个扩展为宏的宏call-with-exception-handler
,一些条件和一些延续代码.它的使用方式如下:
(with-handlers ([(lambda (e) (exn:fail:network? e))
(lambda (e)
(printf "network seems to be broken\n")
(cleanup))])
(do-some-network-stuff))
Run Code Online (Sandbox Code Playgroud)
该宏基call-with-exception-handler
于在它们被引发时处理所有异常的原语实现了"异常的动态上下文中的谓词和处理程序子句"的概念.
更复杂的宏的使用是LALR(1)解析器生成器的实现.parser
表单只是另一种表达形式,而不是需要预处理的单独文件.它需要语法描述,在编译时计算表,并生成解析器函数.动作例程是词法范围的,因此它们可以引用文件中的其他定义或偶数lambda
变量.您甚至可以在动作例程中使用其他语言扩展.
在极端情况下,Typed Racket是通过宏实现的Racket的类型方言.它有一个复杂的类型系统,旨在匹配Racket/Scheme代码的习语,并通过动态软件合同(也通过宏实现)保护类型函数,与非类型模块互操作.它由一个"类型模块"宏实现,它扩展,类型检查和转换模块体以及辅助宏,用于将类型信息附加到定义等.
FWIW,还有Lazy Racket,一种懒散的Racket方言.它不是把每个功能分为宏来实现,而是通过重新绑定lambda
,define
以及功能应用语法,创造宏和力的承诺.
总之,懒惰的评估和宏有一个小的交叉点,但它们是非常不同的东西.并且宏肯定不会被惰性评估所包含.
scl*_*clv 23
惰性求值可以替代宏的某些用途(那些延迟评估以创建控制结构的用途)但反过来却不是真的.您可以使用宏来使延迟的评估结构更加透明 - 请参阅SRFI 41(Streams)以获取示例:http://download.plt-scheme.org/doc/4.1.5/html/srfi-std/srfi -41/SRFI-41.html
除此之外,您还可以编写自己的懒惰IO原语.
然而,根据我的经验,严格语言中普遍使用的懒惰代码往往会导致开销,而运行时中普遍使用的懒惰代码从一开始就有效地支持它 - 请注意,这实际上是一个实现问题.
Con*_*nal 23
懒惰是指示性的,而宏则不是.更准确地说,如果您将非严格性添加到外延语言中,结果仍然是指示性的,但如果添加宏,则结果不是指示性的.换句话说,惰性纯语言中表达式的含义仅仅是组件表达式的含义; 而宏可以从语义上相等的参数产生语义上不同的结果.
从这个意义上说,宏更强大,而懒惰相应地在语义上表现得更好.
编辑:更确切地说,宏是非指示性的,除了关于身份/平凡的表示("外延"的概念变成空的).
Rai*_*wig 10
Lisp始于上一个千年的50年代后期.参见符号表达式的递归函数及其计算机的计算.宏并不是那个Lisp的一部分.这个想法是用符号表达式计算,它可以代表各种公式和程序:数学表达式,逻辑表达式,自然语言句子,计算机程序,......
后来发明了Lisp宏,它们是Lisp本身的上述思想的应用:宏使用完整的Lisp语言作为转换语言将Lisp(或类似Lisp)表达式转换为其他Lisp表达式.
您可以想象,使用Macros,您可以作为Lisp的用户实现强大的预处理器和编译器.
典型的Lisp方言使用严格的参数评估:在函数执行之前评估函数的所有参数.Lisp还有几种具有不同评估规则的内置表单.IF
就是这样一个例子.Common Lisp IF
是一个所谓的特殊运算符.
但是我们可以定义一个新的类似Lisp的语言,使用延迟评估,我们可以编写宏来将该语言转换为Lisp.这是宏的应用程序,但到目前为止还不是唯一的.
这种Lisp扩展的一个例子(相对较旧)使用宏来实现一个代码转换器,它提供了延迟评估的数据结构,它是Common Lisp 的SERIES扩展.
宏可用于处理惰性评估,但只是其中的一部分.宏的要点是,归功于它们基本上没有任何语言固定.
如果编程就像玩乐高积木一样,使用宏也可以改变砖块的形状或它们构建的材料.
宏不只是延迟评估.这可以作为fexpr
(在lisp历史中的宏观前兆).宏是关于程序重写,这fexpr
只是一个特例...
作为一个例子,考虑我在业余时间写一个小的lisp到javascript编译器,最初(在javascript内核中)我只有lambda支持&rest
参数.现在支持关键字参数,因为我重新定义了lambda在lisp中的含义.
我现在可以写:
(defun foo (x y &key (z 12) w) ...)
Run Code Online (Sandbox Code Playgroud)
并调用该函数
(foo 12 34 :w 56)
Run Code Online (Sandbox Code Playgroud)
执行该调用时,在函数体中,w
参数将绑定到56,参数将绑定z
到12,因为它未被传递.如果将不受支持的关键字参数传递给函数,我也会收到运行时错误.我甚至可以通过重新定义编译表达式的含义来添加一些编译时检查支持(即添加检查"静态"函数调用形式是否将正确的参数传递给函数).
中心点是原始(内核)语言根本不支持关键字参数,我能够使用语言本身添加它.结果就像从一开始就在那里; 它只是语言的一部分.
语法很重要(即使技术上可以使用图灵机).语法塑造了你的想法.宏(和读取宏)使您可以完全控制语法.
关键的一点是,代码重写代码并没有像C++模板元编程那样使用瘫痪的笨拙的大脑类似的语言(其中只是制作if
一个令人惊叹的成就),或者使用一个甚至笨重的低于正则表达式替换像C预处理器一样的引擎.
代码重写代码使用相同的完整(和可扩展)语言.它一直在低迷;-)
确实编写宏比编写常规代码更难; 但这是问题的"基本复杂性",而不是人为的复杂性,因为你被迫使用像C++元编程这样的愚蠢的半语言.
编写宏更难,因为代码是一个复杂的东西,在编写宏时,你会编写复杂的东西,自己构建复杂的东西.甚至不是更常见的是上升一级并编写宏生成宏(这就是旧的lisp笑话"我正在编写编写代码的代码,我编写的代码来自").
但宏观力量简直无穷无尽.