我习惯于从haskell进行懒惰的评估,并且发现我已经正确地使用了懒惰的评估,现在我对默认语言的热切感到恼火。这实际上是非常具有破坏性的,因为我使用的其他语言主要使懒散地评估东西非常尴尬,通常涉及到定制迭代器的推出等等。因此,仅仅通过获得一些知识,我实际上已经使自己在原始语言中的工作效率降低了。叹息。
但我听说AST宏提供了另一种干净的方法来做同样的事情。我经常听到"懒惰的评估会使宏冗余",反之亦然,这主要来自于与Lisp和Haskell社区的争吵。
我涉足过各种Lisp变体中的宏。它们看起来像是一种真正有组织的复制和粘贴代码块的方法,在编译时处理。他们当然不是利斯贝尔斯认为的圣杯。但这几乎是肯定的,因为我不能正确使用它们。当然,让宏系统在语言本身所装配的核心数据结构上工作是非常有用的,但它基本上仍然是一种有组织的复制和粘贴代码的方式。我承认,基于与允许完全运行时更改的语言相同的AST的宏系统是强大的。
我想知道的是,宏是如何被用来简洁而简洁地执行懒惰的计算的?如果我想一行一行地处理一个文件而不拖泥带水的话,我只需返回一个列表,上面映射了一个行读取例程。这是dwim的完美例子(按我的意思做)。我甚至不必考虑。
我显然没有宏。我用过它们,但在炒作中没有特别的印象。所以有一些东西我错过了,我不能通过在线阅读文档得到。有人能向我解释这一切吗?
- 你为什么要回去?也许恢复你在哈斯克尔失踪的东西会更好/更容易?哈斯凯尔语中有"th"和"quasiquoting"。另外,请张贴一个例子!!
- 例子?好吧,通常是一些小例子堆积起来,导致了问题的出现。例如,如果我在Perl5中建立了一个庞大的map/grep/sort管道,每个阶段都必须评估整个该死的东西,将整个东西写入内存,然后将其送入下一个阶段。不是在每个步骤的内存中存储一个计算元素,而是整个事情需要存储。如果您从一个文件或无限序列中检索某些内容,那么这样做会变得单调乏味,通常会求助于状态,尽管是封装状态。
- 我看到过很多这样的关于宏的"有组织的复制和粘贴代码的方式",但它们正是您稍后所说的"我不获取宏"宏是编译器/计算器扩展,在编译/评估时运行的代码,使用常见的Lisp宏,您可以从头实现所有的haskell,任何编译器。所以说"宏是一种有组织的复制和粘贴代码的方式",就像说编译器是一样的,就像一个javascripter说"我使用过编译器,但在炒作中没有特别的印象",这对你来说很荒谬,也很无聊。
Lazy evaluation makes macros redundant
这完全是胡说八道(不是你的错,我以前听说过)。的确,您可以使用宏来更改表达式计算的顺序、上下文等,但这是宏最基本的用法,使用特殊宏而不是函数来模拟懒惰的语言真的不方便。所以,如果你从这个方向来研究宏,你肯定会失望的。
宏用于用新的语法形式扩展语言。宏的某些特定功能是
影响表达式计算的顺序、上下文等。
创建新的绑定形式(即影响表达式的计算范围)。
执行编译时计算,包括代码分析和转换。
执行(1)的宏可以非常简单。例如,在racket中,异常处理形式with-handlers只是一个宏,它可以扩展为call-with-exception-handler、一些条件和一些延续代码。使用方法如下:
1 2 3 4 5 6
| (with -handlers ([(lambda (e ) (exn :fail:network ? e ))
(lambda (e )
(printf "network seems to be broken
")
(cleanup ))])
(do -some -network -stuff )) |
宏基于原语call-with-exception-handler,实现了"异常动态上下文中的谓词和处理程序子句"的概念,该原语在异常出现时处理所有异常。
更复杂的宏使用是LALR(1)解析器生成器的实现。与需要预处理的单独文件不同,parser表单只是另一种表达式。它接受语法描述,在编译时计算表,并生成一个解析器函数。动作例程在词汇上是有范围的,因此它们可以引用文件中的其他定义,甚至可以引用lambda绑定变量。您甚至可以在操作例程中使用其他语言扩展。
在最末端,类型化的racket是通过宏实现的一种类型化的racket方言。它有一个复杂的类型系统,设计来匹配Racket/Scheme代码的习惯用法,它通过使用动态软件契约(也通过宏实现)保护类型化函数来与非类型化模块进行交互。它由一个"类型化模块"宏实现,该宏扩展、类型检查并转换模块主体以及用于将类型信息附加到定义等的辅助宏。
还有懒惰的球拍,一种懒惰的球拍方言。它不是通过将每个函数转换为宏来实现的,而是通过将lambda、define和函数应用程序语法重新绑定到创建和强制承诺的宏来实现的。
总之,懒惰的评估和宏有一个小的交叉点,但它们是非常不同的东西。宏当然不会被懒惰的计算所包含。
- 我认为这种误解主要源于缺乏想象力。来自于一种严格的、热切的语言,以及糟糕的元编程支持,认为if语句不需要内置的想法似乎是非常革命性的!从某种程度上说,这是事实,但对于懒惰的计算和宏来说,不仅仅是重新实现控制结构,因为您可以。
懒惰是象征性的,而宏则不是。更准确地说,如果向表示性语言添加非严格性,则结果仍然是表示性的,但如果添加宏,则结果不是表示性的。换句话说,在懒惰的纯语言中,表达式的意义仅仅是组件表达式意义的函数;而宏可以从语义相等的参数中产生语义上不同的结果。
从这个意义上讲,宏更强大,而懒惰则相应地在语义上表现得更好。
编辑:更准确地说,宏是非表示性的,除了关于标识/琐碎的表示(其中"表示性"的概念变得空虚)。
- 书呆子写…产生语义上不同的结果表明了参数的语义不平等!但重点是一个很好的问题:宏(在fexpr意义上)使表达式的语法成为其语义的一部分,不管这是否意味着什么。
- 首先,fexpr不是宏。其次,宏是在编译时运行的程序——它们不是不同的表达式形式(实际上是fexpr,这是个坏主意)。所以宏根本不会改变语言的运行时语义。第三,我认为对于你想要的概念,"作曲"这个词比"外延"更常见。
- 山姆:跟随着兰丁(见上面的链接),我用的是更具体的意义上的"外延"而不仅仅是"作曲"。例如,宏在语法上是复合的,但在表示上是非复合的。
- 养猪工人:我很同情你的POV,尤其是"宏…使表达式的语法成为其语义的一部分",这将破坏所有非平凡的等式属性。
- 山姆:我说的是整体语义(语法-->含义),而不仅仅是运行时方面。
- 我站在宏和fexpr的对比上。谢谢你的澄清。显然,语义比"运行时语义"更重要,尤其是当某些程序在运行时以外的时间运行时。
- 这里有一个更直接的联系,兰丁建议的术语和概念"外延"。
- 将宏视为编译工作流中的术语重写系统——这样就有了定义良好且行为良好的语义。
- Conal:如果你描述的是一个语义,在你的表示语义中包含宏扩展,那么你最初的答案是犯了一个更基本(但很常见)的错误,那就是认为宏的参数有语义,除了数据(表示语法)的意义外。除了某些不符合任何广泛使用的宏系统的情况外,这是考虑宏的错误方式——相反,它们通过扩展来确定结果的语义。
- 康纳:正如我所听到的,用于语义学的术语"复合"恰恰意味着:一个表达式的意义完全由它的子表达式的意义决定。例如,Cartwright和Felleisen在其可扩展的外延语言规范中将"组合性"定义为"短语的解释是其子短语解释的函数"。
- @山姆:你基本上是说你应该只考虑宏扩展的结果,而不是源代码。这意味着我们实际读取和写入的表达式没有任何有意义的语义,并且无法组合(除非我们在更改前后冗长地展开所有宏,并尝试分析扩展前后代码的语义)。这使得表示性编程变得不那么有用。
- 山姆:(关于"…错误,认为宏的参数具有语义)。是的,感谢您对我的原始语句进行了重新表述,与懒惰不同,宏不是表示性的。也就是说,它们操作的是语法,而不是语义。(这不是价值判断。我喜欢宏,顺便说一句。)
- 山姆:我相信你,当在外延语义学的上下文中使用时,"复合"可能是指外延复合。在更广泛的背景下,我欣赏兰丁的"外延性"如何包含更多的信息。
- 康纳:我不认为我同意你的看法——相反,我认为你犯了一个类别错误。在模板haskell中,th函数是它们输入的复合函数,只是它们的输入是抽象语法树,而不是haskell值。你的抱怨似乎是引用并不是标识,而是重新定义语法。
- 皮克:这取决于你所做的推理。如果你在推理你正在编写的haskell程序,那么把"if then else"作为语言的一部分就可以了。如果您正在编写一个haskell静态分析器,那么您应该遵循定义,这是一个宏扩展,并分析扩展的结果。
- 山姆:也许你在这里和自己争论。和以前一样,当我看你的评论的客观内容(我感兴趣的部分)时,我发现我们之间没有分歧。相反,我看到我们都在说,虽然函数的含义(无论是严格的还是非严格的)是表示转换,但是宏的含义是表示(语法)转换。两者都是复合的,但只有前者是表示的(==表示复合的)。因此宏具有超越懒惰功能的表现力。
- 康纳:我认为我们不同意宏输入的表示方式。显然,对于任何给定的输入,都有许多不同的意义函数。但是我认为一个th输入的适当的表示是一个ast值,而您认为适当的表示是进一步分析这个表示,并查看作为haskell程序的ast的含义。
- 萨姆:我仍然看到你在争论你的想象。我不认为你说的是我想的,我也不想,因为我不带"合适"这样的主观概念。我也不希望宏参数(asts)被解释。事实上,过滤掉主观性/观点,我们都说宏将语法(asts)映射到语法,而不是像函数那样应用(非同一性)解释。如果你想说宏是(空的)表示的w.r.t身份/琐碎的表示,好的。
- 我修改了我的答案,增加了宏是非表示的,除了关于身份/琐碎的表示(其中"表示"的概念变得空虚)。
- 当然,宏有非常重要的表示。它们只是函数(用纯语言编写时)。碰巧它们是表示语法的值的函数。但这并不意味着它们没有有用的表示。
- 山姆:我说的不是宏有简单的表示,而是宏的参数(asts/语法)。与函数的参数不同。
- @康纳:现在我想我明白你的意思了。有些宏系统是由内向外扩展的,而不是由外向外扩展的,尽管我认为这几乎肯定是一个坏主意,但它(可能)会被您的标准所表示。
懒惰的计算可以代替宏的某些使用(那些延迟计算以创建控件构造的宏),但反过来并不是真的。您可以使用宏使延迟的评估构造更加透明--请参阅srfi 41(流),以了解如何执行以下操作的示例:http://download.plt-scheme.org/doc/4.1.5/html/srfi-std/srfi-41/srfi-41.html
除此之外,您还可以编写自己的懒惰IO原语。
然而,根据我的经验,与运行时中普遍懒惰的代码相比,严格语言中普遍懒惰的代码往往会引入开销,而运行时设计的从一开始就有效地支持它——记住,这确实是一个实现问题。
- 回复:"你说的不是真的",我认为你的其他回答显然与此相矛盾。懒惰的效用和宏之间存在交叉点,但两者都不是另一个的子集。
- 为了制造一些控制结构,我想。如何使用Lazy Evaluation编写案例(不是多条件的,而是跳转表)?
- 我看到了一个完整的80年代基本写在哈斯克尔DSL,有Goto,if,和一切。当然,宏也可以做到这一点。但是,如果Haskell的懒惰评估可以生成一个完整的编程语言,我想它可以做开关语句等事情。
- @Louis:Afaik,Augustuss的基本DSL(augustss.blogspot.com/2009/02/…)实际上并不是关于懒惰,而是关于haskell的轻量级语法、重载的num文本和单绑定语法支持。
Lisp始于上个千年的50年代末。参见符号表达式的递归函数及其计算机计算。宏不是该Lisp的一部分。其思想是用符号表达式来计算,符号表达式可以表示各种公式和程序:数学表达式、逻辑表达式、自然语言句子、计算机程序……
后来,Lisp宏被发明了,它们是上述思想在Lisp本身中的应用:宏使用完整的Lisp语言作为转换语言,将Lisp(或Lisp-like)表达式转换为其他Lisp表达式。
您可以想象,使用宏,您可以作为Lisp的用户实现强大的预处理器和编译器。
典型的Lisp方言使用严格的参数评估:函数的所有参数都在函数执行之前进行评估。Lisp还有几个内置的表单,它们具有不同的评估规则。IF就是这样一个例子。通常,lisp IF是一个所谓的特殊运算符。
但是我们可以定义一种新的类似于Lisp的(子)语言,它使用懒惰的计算,我们可以编写宏来将该语言转换为Lisp。这是一个宏应用程序,但迄今为止还不是唯一一个。
使用宏来实现代码转换器的此类Lisp扩展的一个示例(相对较旧),该代码转换器为数据结构提供延迟评估,它是通用Lisp的系列扩展。
宏可以用来处理懒惰的计算,但它只是其中的一部分。宏的要点是,由于它们,语言中基本上没有固定的内容。
如果编程就像玩乐高积木,使用宏还可以更改积木的形状或使用的材料。
宏不仅仅是延迟计算。这是可用的fexpr(Lisp历史上的一个宏观前兆)。宏是关于程序重写的,其中fexpr只是一个特例…
举个例子,我在业余时间编写了一个很小的lisp-to-javascript编译器,最初(在javascript内核中)我只有lambda支持&rest参数。现在有了对关键字参数的支持,这是因为我重新定义了lambda在Lisp本身中的含义。
我现在可以写:
1
| (defun foo (x y &key (z 12) w) ...) |
调用函数时
执行该调用时,函数体中的w参数将绑定到56,而z参数将绑定到12,因为它没有被传递。如果向函数传递了不支持的关键字参数,我还将得到一个运行时错误。我甚至可以通过重新定义编译表达式的含义来添加一些编译时检查支持(即,如果"静态"函数调用窗体正在向函数传递正确的参数,则添加检查)。
中心点是原始(内核)语言根本不支持关键字参数,我可以使用该语言本身添加它。结果就像它从一开始就存在;它只是语言的一部分。
语法很重要(即使技术上可以只使用图灵机)。句法塑造了你的思想。宏(和读取宏)使您可以完全控制语法。
关键的一点是代码重写代码不使用残缺的Buff-Real-Primk*k类语言作为C++模板元编程(仅制作EDCOX1×5)是一个惊人的成就,或者用一个偶数笨拙小于ReXEP替代引擎如C预处理器。
代码重写代码使用相同的完整(和可扩展)语言。一路上都是Lisp;-)
当然,编写宏比编写常规代码更困难;但这是问题的"本质复杂性",而不是人为的复杂性,因为您被迫使用类似于C++元编程的哑半语言。
编写宏比较困难,因为代码是一件复杂的事情,当编写宏时,编写的是复杂的东西,这些东西本身就是复杂的东西。更高一个级别并编写生成宏的宏(这是Lisp的老笑话"我正在编写编写编写编写代码的代码,而这些代码是为我付费的")的由来),这一点也不少见。
但宏观力量是无边无际的。
- 难道不提供一种简单的方法来为一种语言添加新的语法,而只是在大型项目中询问可维护性问题吗?我的意思是,当DSL像正则表达式和SQL那样定义良好、分布广泛时,它们就很好了。但我不太确定在每个项目中弹出的用户定义语言是一个特别好的主意。毕竟,我们使用语言是因为它们擅长某些事情——而不是因为我们可以实现其他语言。不过,我可能错了。你知道,对于一个单独的问题,这可能是一场有趣的辩论…
- 我把评论中的问题转给了一个新问题:stackoverflow.com/questions/7052963/…
- 我不认为那个问题会继续存在。所以不是新闻组,而是一个有点互动的问答网站。我的观点是,如知道Java语言不足以在Java中编程。您必须了解框架才能以正确的方式进行正确的调用。在大型软件中,同样的知识需求也出现在更高的层次上(例如,如果您触摸该表的该列,您也应该触摸该表中的另一列,这类数据就在这里,这类数据就在那里)。如果可以用不同的语法更好地表达这些知识或代码,为什么不这样做呢?
- 我真的不明白那句话跟那个新问题有什么关系。我的意思是,如果有东西需要不同的语法,人们会转向不同的语言或者尝试实现DSL。这就是我在这个问题上所说的。
- @路易斯:我们可以把宏或懒惰作为一种用户定义的语言来描述,事实上,你自己也可以在对SCLV答案的评论中这样做。您所描述的问题与宏或懒惰几乎没有关系,甚至与比较两者之间的关系更小。这是一个高层次抽象的问题,也就是说,一个程序员的合理抽象对于另一个程序员来说可能是不可理解的。这是值得讨论的,但正如6502所说,也许不是这样。
- @路易斯,不,这本身不是一个可维护性问题。如果有一种真正轻松地扩展语言的方法,那么您的维护成本就会下降,因为您可以摆脱大量的预处理器和每个相当大的工业代码库都很典型的弯曲的即席代码生成器。
- 实际上,编写宏并不比使用高阶函数困难。大多数人做错事,但这不是借口。
- @sk-logic:我认为宏比高阶函数更难,因为存在语法问题(例如不需要捕获,正确的代码遍历)。您也有与"时间级别"的高阶函数相同的问题。显然,它们都比普通代码更复杂,因为输出是一个复杂的对象(代码),而不仅仅是数字。数字可能是错误的,但不能是"马车"…在我看来,复杂性的飞跃就在这里。
- @事实上,有很多方法可以将代码重写宏转换为简化代码的好工具。当然,它需要相当多的规则(除非有适当的类型系统来执行它),但是用一种比"用数字"更简单的方式编码是可能的。
- 谷歌搜索"它的口齿不清一路向下"给了一个很多有趣的东西阅读……:)