这个答案是对illissius提出的问题的逐条回答:
用起来很难看。$(fooBar " Asdf)看起来不太好。当然,这很肤浅,但也有帮助。
我同意。我觉得选择$()是为了让它看起来像语言的一部分——使用熟悉的Haskell符号托盘。然而,这正是你/不希望/用于宏拼接的符号。他们确实融入太多了,这方面的美容是相当重要的。我喜欢拼接的{{}}外观,因为它们在视觉上非常明显。
It's even uglier to write. Quoting works sometimes, but a lot of the time you have to do manual AST grafting and plumbing. The [API][1] is big and unwieldy, there's always a lot of cases you don't care about but still need to dispatch, and the cases you do care about tend to be present in multiple similar but not identical forms (data vs. newtype, record-style vs. normal constructors, and so on). It's boring and repetitive to write and complicated enough to not be mechanical. The [reform proposal][2] addresses some of this (making quotes more widely applicable).
我也同意这一点,然而,正如“TH的新方向”中的一些评论所观察到的那样,缺乏良好的开箱即用的AST引用并不是一个关键的缺陷。在这个WIP包中,我试图以库的形式解决这些问题:https://github.com/mgsloan/quasi-extras。到目前为止,我允许拼接的地方比平时多一些,可以在ast上进行模式匹配。
The stage restriction is hell. Not being able to splice functions defined in the same module is the smaller part of it: the other consequence is that if you have a top-level splice, everything after it in the module will be out of scope to anything before it. Other languages with this property (C, C++) make it workable by allowing you to forward declare things, but Haskell doesn't. If you need cyclic references between spliced declarations or their dependencies and dependents, you're usually just screwed.
我遇到过循环TH定义是不可能的问题…这很烦人。有一个解决方案,但它很难看——将循环依赖关系中涉及的东西包装在一个TH表达式中,该表达式结合了所有生成的声明。其中一个声明生成器可以是接受Haskell代码的准引号。
It's unprincipled. What I mean by this is that most of the time when you express an abstraction, there is some kind of principle or concept behind that abstraction. For many abstractions, the principle behind them can be expressed in their types. When you define a type class, you can often formulate laws which instances should obey and clients can assume. If you use GHC's [new generics feature][3] to abstract the form of an instance declaration over any datatype (within bounds), you get to say "for sum types, it works like this, for product types, it works like that". But Template Haskell is just dumb macros. It's not abstraction at the level of ideas, but abstraction at the level of ASTs, which is better, but only modestly, than abstraction at the level of plain text.
It's only unprincipled if you do unprincipled things with it. The only difference is that with the compiler implemented mechanisms for abstraction, you have more confidence that the abstraction isn't leaky. Perhaps democratizing language design does sound a bit scary! Creators of TH libraries need to document well and clearly define the meaning and results of the tools they provide. A good example of principled TH is the derive package: http://hackage.haskell.org/package/derive - it uses a DSL such that the example of many of the derivations /specifies/ the actual derivation.
它把你和GHC联系在一起。理论上,另一个编译器可以实现它,但在实践中,我怀疑这种情况是否会发生。(这与各种类型系统扩展形成对比,尽管它们目前可能只由GHC实现,但我很容易想象它们会被其他编译器采用,并最终标准化。)
That's a pretty good point - the TH API is pretty big and clunky. Re-implementing it seems like it could be tough. However, there are only really only a few ways to slice the problem of representing Haskell ASTs. I imagine that copying the TH ADTs, and writing a converter to the internal AST representation would get you a good deal of the way there. This would be equivalent to the (not insignificant) effort of creating haskell-src-meta. It could also be simply re-implemented by pretty printing the TH AST and using the compiler's internal parser.
虽然我可能是错的,但从实现的角度来看,我不认为TH是一个复杂的编译器扩展。这实际上是“保持简单”的好处之一,而不是让基础层成为理论上有吸引力的、静态可验证的模板系统。
API不稳定。当新的语言特性被添加到GHC,并且template-haskell包被更新以支持它们时,这通常涉及到对TH数据类型的向后不兼容的更改。如果你想让你的TH代码兼容多个版本的GHC,你需要非常小心,可能要使用CPP。
This is also a good point, but somewhat dramaticized. While there have been API additions lately, they haven't been extensively breakage inducing. Also, I think that with the superior AST quoting I mentioned earlier, the API that actually needs to be used can be very substantially reduced. If no construction / matching needs distinct functions, and are instead expressed as literals, then most of the API disappears. Moreover, the code you write would port more easily to AST representations for languages similar to Haskell.
In summary, I think that TH is a powerful, semi-neglected tool. Less hate could lead to a more lively eco-system of libraries, encouraging the implementation of more language feature prototypes. It's been observed that TH is an overpowered tool, that can let you /do/ almost anything. Anarchy! Well, it's my opinion that this power can allow you to overcome most of its limitations, and construct systems capable of quite principled meta-programming approaches. It's worth the usage of ugly hacks to simulate the "proper" implementation, as this way the design of the "proper" implementation will gradually become clear.
在我个人理想的涅槃版本中,大部分语言实际上会移出编译器,进入这些类型的库中。特性作为库实现的事实并没有严重影响它们忠实抽象的能力。
Haskell对样板代码的典型回答是什么?抽象。我们最喜欢的抽象概念是什么?函数和类型类!
类型类允许我们定义一组方法,然后可以在该类上的所有泛型函数中使用这些方法。然而,除此之外,类帮助避免模式化的唯一方法是提供“默认定义”。这里有一个无原则特征的例子!
Minimal binding sets are not declarable / compiler checkable. This could lead to inadvertent definitions that yield bottom due to mutual recursion.
Despite the great convenience and power this would yield, you cannot specify superclass defaults, due to orphan instances http://lukepalmer.wordpress.com/2009/01/25/a-world-without-orphans/ These would let us fix the numeric hierarchy gracefully!
Going after TH-like capabilities for method defaults led to http://www.haskell.org/haskellwiki/GHC.Generics . While this is cool stuff, my only experience debugging code using these generics was nigh-impossible, due to the size of the type induced for and ADT as complicated as an AST. https://github.com/mgsloan/th-extra/commit/d7784d95d396eb3abdb409a24360beb03731c88c
In other words, this went after the features provided by TH, but it had to lift an entire domain of the language, the construction language, into a type system representation. While I can see it working well for your common problem, for complex ones, it seems prone to yielding a pile of symbols far more terrifying than TH hackery.
TH gives you value-level compile-time computation of the output code, whereas generics forces you to lift the pattern matching / recursion part of the code into the type system. While this does restrict the user in a few fairly useful ways, I don't think the complexity is worth it.
我认为拒绝TH和类似lisp的元编程导致了对方法默认值之类的东西的偏好,而不是更灵活的宏扩展,比如实例声明。避免可能导致不可预见结果的规则是明智的,然而,我们不应该忽视Haskell的强大类型系统允许比许多其他环境中更可靠的元编程(通过检查生成的代码)。