这个答案是针对 illissius 提出的问题逐点回答的:
- 用起来很丑。$(fooBar ''Asdf) 看起来不太好。肤浅的,当然,但它有所贡献。
我同意。我觉得 $( ) 被选择让它看起来像是语言的一部分——使用熟悉的 Haskell 符号托盘。但是,这正是您/不/想要的用于宏拼接的符号。它们肯定融合得太多了,而这个美容方面非常重要。我喜欢 {{ }} 拼接的外观,因为它们在视觉上非常不同。
- 写的更难看。引用有时有效,但很多时候你必须手动进行 AST 嫁接和管道。[API][1] 大而笨重,总有很多你不关心但仍需要调度的案例,而你关心的案例往往以多种相似但不相同的形式存在(数据与新类型,记录样式与普通构造函数,等等)。写起来很无聊和重复,而且复杂到不能机械化。[改革提案][2] 解决了其中的一些问题(使引用更广泛适用)。
然而,我也同意这一点,正如“TH 的新方向”中的一些评论所观察到的,缺乏良好的开箱即用 AST 引用并不是一个严重的缺陷。在这个 WIP 包中,我试图以库的形式解决这些问题:https ://github.com/mgsloan/quasi-extras 。到目前为止,我允许在比平常更多的地方进行拼接,并且可以在 AST 上进行模式匹配。
- 舞台限制是地狱。不能拼接同一个模块中定义的函数是其中较小的一部分:另一个后果是,如果你有一个顶级拼接,模块中它之后的所有内容都将超出它之前的任何内容的范围。具有此属性的其他语言(C、C++)通过允许您转发声明事物使其变得可行,但 Haskell 不这样做。如果您需要拼接声明或其依赖项和依赖项之间的循环引用,那么您通常会被搞砸。
我之前遇到过循环 TH 定义不可能的问题……这很烦人。有一个解决方案,但它很丑 - 将循环依赖中涉及的内容包装在一个 TH 表达式中,该表达式结合了所有生成的声明。这些声明生成器之一可能只是一个接受 Haskell 代码的准引用器。
- 这是没有原则的。我的意思是,大多数时候当你表达一个抽象时,这个抽象背后都有某种原则或概念。对于许多抽象来说,它们背后的原理可以用它们的类型来表达。当你定义一个类型类时,你通常可以制定实例应该遵守和客户可以假设的规律。如果您使用 GHC 的 [新泛型特性][3] 对任何数据类型(在范围内)抽象实例声明的形式,您可以说“对于 sum 类型,它的工作方式是这样的,对于产品类型,它的工作方式是这样的”。但是 Template Haskell 只是愚蠢的宏。这不是思想层面的抽象,而是 AST 层面的抽象,它比纯文本层面的抽象更好,但只是适度的。
只有当你用它做无原则的事情时,它才是无原则的。唯一的区别是,通过编译器实现的抽象机制,您对抽象没有泄漏更有信心。也许使语言设计民主化听起来确实有点吓人!TH 库的创建者需要很好地记录并清楚地定义他们提供的工具的含义和结果。原则性 TH 的一个很好的例子是派生包:http ://hackage.haskell.org/package/derive - 它使用 DSL,因此许多派生 / 指定 / 实际派生的例子。
- 它将您与 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 是一个强大的工具,它可以让你/做/几乎任何事情。无政府状态!嗯,我认为这种能力可以让你克服它的大部分限制,并构建能够采用非常有原则的元编程方法的系统。值得使用丑陋的黑客来模拟“正确”实现,因为这样“正确”实现的设计将逐渐变得清晰。
在我个人理想的涅槃版本中,大部分语言实际上会从编译器中移出,进入这些种类的库中。功能作为库实现的事实不会严重影响它们忠实抽象的能力。
Haskell 对样板代码的典型回答是什么?抽象。我们最喜欢的抽象是什么?函数和类型类!
类型类让我们定义一组方法,然后可以在该类的所有泛型函数中使用这些方法。然而,除此之外,类帮助避免样板的唯一方法是提供“默认定义”。现在这里是一个无原则的特性的例子!
我认为拒绝 TH 和类似 lisp 的元编程导致偏爱方法默认值之类的东西,而不是更灵活、宏扩展之类的实例声明。避免可能导致不可预见结果的事情的原则是明智的,但是,我们不应忽视 Haskell 强大的类型系统允许比许多其他环境更可靠的元编程(通过检查生成的代码)。