该答案是针对illissius提出的问题的逐点回答:
- 使用起来很丑。$(fooBar''Asdf)看起来不太好。肤浅的,当然,但它有所作为。
我同意。我觉得$()被选择为看起来像是语言的一部分-使用了熟悉的Haskell符号托盘。但是,这正是您不希望在用于宏拼接的符号中想要的。它们肯定混合得太多,并且在化妆品方面非常重要。我喜欢{{}}的外观,因为它们在视觉上非常明显。
- 写起来更难看。有时可以进行报价,但是很多时候您必须进行手动AST嫁接和管道连接。[API] [1]大而笨拙,总有很多情况您不关心但仍然需要调度,而您确实关心的情况往往以多种相似但不相同的形式出现(数据vs. newtype,记录样式与常规构造函数等)。写起来很无聊而且重复,而且足够复杂以至于不能机械化。[改革建议] [2]解决了其中一些问题(使引用更为广泛地适用)。
但是,我也同意这一点,正如“ TH的新方向”中的一些评论所指出的那样,缺乏良好的即用型AST报价并不是关键缺陷。在此WIP软件包中,我试图以库形式解决这些问题:https : //github.com/mgsloan/quasi-extras。到目前为止,我允许在比平时更多的地方进行拼接,并且可以在AST上进行模式匹配。
- 舞台限制是地狱。无法拼接在同一模块中定义的功能只是其中的一小部分:另一个结果是,如果您具有顶级拼接,则模块中位于其后的所有内容都将超出其之前的范围。具有此属性的其他语言(C,C ++)通过允许您转发声明的内容来使其可行,而Haskell则不行。如果需要在拼接的声明或它们的依赖项和依赖项之间使用循环引用,那么通常就很麻烦。
我曾经遇到过循环TH定义的问题,以前是不可能的……这很烦人。有一个解决方案,但这很丑陋-将循环依赖关系中涉及的内容包装在TH表达式中,该表达式将所有生成的声明组合在一起。这些声明生成器之一可能只是接受Haskell代码的准引用。
- 这是无原则的。我的意思是,大多数时候表达抽象时,抽象背后都有某种原理或概念。对于许多抽象,它们背后的原理可以用它们的类型表示。定义类型类时,通常可以制定法律,实例应遵循且客户可以承担。如果您使用GHC的[新泛型功能] [3]对任何数据类型(在范围内)抽象实例声明的形式,您会说“对于求和类型,它像这样,对于产品类型,它像那样”。但是Template Haskell只是愚蠢的宏。它不是思想层面的抽象,而是AST层面的抽象,这比纯文本层面的抽象要好,但要适度。
仅当您使用它进行无原则的操作时,它才是无原则的。唯一的区别是,使用编译器实现的抽象机制,您可以更加放心抽象不会泄漏。也许使语言设计民主化听起来确实有些吓人!TH库的创建者需要很好地记录并清楚地定义他们提供的工具的含义和结果。原则上TH的一个很好的例子是派生包:http : //hackage.haskell.org/package/derive-它使用DSL,使得许多派生的示例/ specify /实际派生。
- 它把您与GHC联系起来。理论上,另一个编译器可以实现它,但实际上,我怀疑这种情况是否会发生。(这与各种类型的系统扩展形成了鲜明对比,尽管它们可能目前仅由GHC实现,但我可以轻易地想象它会被其他编译器采用并最终实现标准化。)
这很不错-TH API很大又笨重。重新实施似乎很难。但是,实际上只有几种方法可以解决代表Haskell AST的问题。我想如果复制TH ADT并为内部AST表示形式编写转换器,将会带给您很多帮助。这等同于创建haskell-src-meta的工作(并非无关紧要)。也可以通过漂亮地打印TH AST并使用编译器的内部解析器来简单地重新实现它。
虽然我可能是错的,但从实现的角度来看,我不认为TH是这么复杂的编译器扩展。实际上,这是“保持简单”的好处之一,而使基础层不成为某些具有理论吸引力的,可静态验证的模板系统。
- API不稳定。将新的语言功能添加到GHC并更新template-haskell软件包以支持它们时,这通常涉及TH数据类型的向后不兼容更改。如果您希望TH代码与GHC的多个版本兼容,则需要非常小心并可能使用
CPP
。
这也是一个好点,但是有点戏剧化。尽管最近增加了API,但并没有引起广泛的破坏。另外,我认为通过前面提到的高级AST引用,可以大大减少实际需要使用的API。如果没有构造/匹配需要不同的功能,而是用文字表示,那么大多数API都会消失。此外,对于与Haskell类似的语言,您编写的代码将更容易移植到AST表示形式。
总而言之,我认为TH是一个功能强大的,半被忽视的工具。减少仇恨可能会导致图书馆生态系统更加活跃,从而鼓励实施更多的语言功能原型。据观察,TH是一种功能强大的工具,可以让您/ do /几乎执行任何操作。无政府状态!好吧,我认为这种功能可以使您克服其大多数局限性,并构建能够使用具有相当原则性的元编程方法的系统。值得一提的是,使用丑陋的hack来模拟“适当”的实现,因为这样,“适当”的实现的设计将逐渐变得清晰。
在我个人理想的涅磐版本中,许多语言实际上都会从编译器中移出,进入各种库中。将功能实现为库这一事实并不会严重影响其忠实抽象的能力。
Haskell对样板代码的典型回答是什么?抽象。我们最喜欢的抽象是什么?函数和类型类!
类型类让我们定义了一组方法,然后可以将该类中所有通用的函数使用。但是,除此之外,类提供“默认定义”是帮助避免样板的唯一方法。现在,这是一个无原则功能的示例!
我认为对TH和类似Lisp的元编程的拒绝导致他们倾向于方法默认值之类的东西,而不是更灵活的,宏扩展的实例声明之类的东西。避免可能导致无法预料的结果的原则是明智的,但是,我们不应该忽略Haskell的有能力类型系统比在许多其他环境中(通过检查生成的代码)允许更可靠的元编程。