关于模板哈斯克尔有什么不好?

Haskell社区经常认为模板Haskell是一种不幸的方便。 在这方面,我很难将词语完全放在文字上,但请考虑这几个例子

  • 模板Haskell在“The Ugly(but necessary)”中列出,以回答用户使用/避免哪些Haskell(GHC)扩展的问题?
  • 模板Haskell在newtype的值线程(库邮件列表)的Unboxed Vectors中考虑了临时/劣势解决方案,
  • Yesod经常因为过度依赖Template Haskell而受到批评(请参阅博客文章以回应这种观点)
  • 我已经看过各种博客文章,人们用Template Haskell做了非常整洁的事情,使得Haskell中常常不可能使用更漂亮的语法,以及巨大的样板缩减。 那么为什么模板哈斯克尔被这样看待呢? 什么使它不受欢迎? 在什么情况下应该避免模板哈斯克尔,为什么?


    避免模​​板哈斯克尔的一个原因是它作为一个整体不是类型安全的,因此违背了“哈斯克尔精神”的大部分。 这里有一些例子:

  • 你无法控制TH代码生成的Haskell AST的类型,除此之外, 你可以有一个类型为Exp的值,但是你不知道它是一个表示[Char]还是表示(a -> (forall b . b -> c))或者其他。 如果可以表达一个函数只能生成某种类型的表达式,或者只是函数声明,或者只有数据构造函数匹配模式等,那么TH会更可靠。
  • 您可以生成不能编译的表达式。 您是否生成了引用不存在的自由变量foo的表达式? 运气不好,只有在实际使用代码生成器时才会看到,只有在触发生成特定代码的情况下。 单元测试也非常困难。
  • TH也是非常危险的:

  • 在编译时运行的代码可以执行任意IO ,包括发射导弹或窃取信用卡。 你不需要仔细查看你曾经下载过的每一个包裹搜索TH利用。
  • TH可以访问“模块专用”功能和定义,在某些情况下完全打破封装。
  • 然后有一些问题使TH功能作为图书馆开发人员使用起来不那么有趣:

  • TH代码不总是可组合的。 比方说,有人为镜头制造发电机,而且往往不是,发电机的构造方式只能由“最终用户”直接调用,而不能由其他TH代码调用,例如采用生成透镜作为参数的类型构造函数列表。 在代码中生成该列表非常困难,而用户只需编写generateLenses [''Foo, ''Bar]
  • 开发人员甚至不知道可以编写TH代码。 你知道你可以写forM_ [''Foo, ''Bar] generateLens吗? Q只是一个monad,所以你可以使用它的所有常用功能。 有些人不知道这一点,因此,他们创建了多个具有相同功能的基本相同功能的重载版本,这些功能导致了一定的膨胀效应。 而且,大多数人在Q monad中编写他们的生成器,即使他们不需要,就像写bla :: IO Int; bla = return 3 bla :: IO Int; bla = return 3 ; 你正在给一个比它需要更多“环境”的功能,并且这个功能的客户需要提供那个环境作为它的一个效果。
  • 最后,有些东西使TH功能作为最终用户使用起来不那么有趣:

  • 不透明度。 当一个TH函数的类型为Q Dec ,它可以在模块的顶层生成绝对的任何东西,并且完全不能控制生成的内容。
  • 整体性。 除非开发人员允许,否则无法控制TH函数的产生量; 如果你发现一个生成数据库接口 JSON序列化接口的函数,你不能说“不,我只想要数据库接口,谢谢;我会推出我自己的JSON接口”
  • 运行。 TH代码需要相当长的时间才能运行。 每次编译文件时都会重新解释代码,并且通常,运行的TH代码需要大量的包,这些包必须加载。 这大大减慢了编译时间。

  • 这完全是我自己的看法。

  • 这是丑陋的使用。 $(fooBar ''Asdf)只是看起来不错。 肤浅,当然,但它有助于。

  • 写作更加丑陋。 有时引用作品,但很多时候你必须做手动AST移植和管道。 这个API非常笨重,总是有很多你不关心但仍然需要发送的情况,而且你关心的情况往往会以多种类似但不相同的形式出现(数据与新类型,记录风格与普通构造函数等)。 写起来很乏味,重复性很强,不够机械。 改革提案解决了其中的一些问题(使报价更为广泛适用)。

  • 舞台限制是地狱。 不能在同一个模块中定义的拼接功能只是其中的一小部分:另一个结果是,如果您有顶层​​拼接,则在模块之后的所有东西都会超出范围。 具有此属性的其他语言(C,C ++)通过允许您转发声明事物来使其可行,但Haskell不会。 如果您需要拼接声明或其依赖项和依赖项之间的循环引用,那么通常只需要拧紧。

  • 它没有纪律。 我的意思是,当你表达一个抽象时,在抽象背后有一些原则或概念。 对于许多抽象,它们背后的原则可以用它们的类型来表达。 对于类型类,您通常可以制定哪些实例应该服从并且客户可以承担的法律。 如果您使用GHC的新泛型特性来抽象任何数据类型(范围内)的实例声明的形式,您可以说“对于汇总类型,它适用于产品类型,它适用于此类型”。 另一方面,模板Haskell只是宏。 它不是思想层面的抽象概念,而是AST层次上的抽象层次,与纯文本层次上的抽象层次相比,这种抽象更好,但仅仅是温和的。

  • 它将您与GHC联系在一起。 理论上,另一个编译器可以实现它,但实际上我怀疑这是否会发生。 (这与各种类型的系统扩展形成对比,尽管它们现在只能由GHC实现,但我很容易想象它被其他编译器采用并最终标准化。)

  • API不稳定。 当新的语言功能被添加到GHC并且更新了template-haskell软件包以支持它们时,这通常涉及TH数据类型的向后不兼容的更改。 如果您希望您的TH代码与GHC的多个版本兼容,您需要非常小心并可能使用CPP

  • 有一个普遍的原则,你应该使用正确的工具和最小的工具,在这个类比中,模板Haskell就是这样的。 如果有办法做到这一点,不是模板哈斯克尔,它通常是可取的。

  • Haskell模板的优点是你可以用它做任何事情,你无法做任何其他的事情,这是一个很大的问题。 大多数情况下,用于TH的东西可能只能在直接作为编译器功能实现时才能完成。 因为它可以让你做这些事情,并且因为它可以让你以更轻量且可重用的方式为潜在的编译器扩展建立原型(例如参见各种镜头包),所以这两者都是非常有益的。

    总结一下为什么我认为模板Haskell有负面的感受:它解决了很多问题,但是对于解决的任何问题,感觉应该有一个更好,更优雅,更严谨的解决方案,更适合解决这个问题,一个不能通过自动生成样板来解决问题,但是不需要具有样板文件。

    *尽管我经常觉得CPP对于可以解决的问题具有更好的功率重量比。

    编辑23-04-14:我在上面经常试图得到的,最近才得到的是,抽象和重复数据删除之间有一个重要区别。 适当的抽象通常会导致重复数据删除作为一种副作用,而重复往往是抽象数据不足的迹象,但这并不是为什么它很有价值。 正确的抽象是使代码正确,易于理解和可维护的原因。 重复数据删除技术只能缩短它。 模板Haskell就像一般的宏一样,是重复数据删除的工具。


    我想解决dflemstr带来的一些问题。

    我不认为你不能认为TH是令人担忧的事实。 为什么? 因为即使有错误,它仍然是编译时间。 我不确定这是否会加强我的观点,但这与在C ++中使用模板时收到的错误在精神上相似。 我认为这些错误比C ++的错误更容易理解,因为你会得到一个漂亮的生成代码版本。

    如果一个TH表达式/准引用者做的事情如此先进以至于棘手的角落可能隐藏起来,那么这可能是不明智的?

    我最近用最近正在研究的准引用(使用haskell-src-exts / meta)打破了这个规则 - https://github.com/mgsloan/quasi-extras/tree/master/examples。 我知道这引入了一些错误,例如不能在广义列表解析中进行拼接。 但是,我认为http://hackage.haskell.org/trac/ghc/blog/Template%20Haskell%20Proposal中的一些想法很有可能会在编译器中结束。 在此之前,用于将Haskell解析为TH树的库几乎是完美的近似值。

    关于编译速度/依赖性,我们可以使用“零”包来内联生成的代码。 这对于给定图书馆的用户来说至少是不错的,但对于编辑图书馆的情况,我们做不了多少。 TH依赖可以膨胀生成的二进制文件吗? 我认为它遗漏了所有未被编译代码引用的东西。

    Haskell模块的编译步骤的分段限制/分裂确实很糟糕。

    RE不透明度:这对于您调用的任何库函数都是一样的。 你无法控制Data.List.groupBy会做什么。 你只需要一个合理的“保证”/约定,版本号告诉你一些关于兼容性的内容。 当它改变时,它有点不同。

    这是使用第零的代价 - 您已经对生成的文件进行了版本控制 - 所以您将始终知道何时生成的代码的形式发生了变化。 但是,查看差异可能有点粗糙,但对于大量生成的代码而言,这是一个更好的开发人员界面可以方便使用的地方。

    RE Monolithism:您当然可以使用自己的编译时代码来后处理TH表达式的结果。 在顶级声明类型/名称上过滤代码并不是很多。 哎呀,你可以想象写一个这样做的泛型函数。 对于修改/去分解分析者,您可以在“QuasiQuoter”上进行模式匹配,并提取出所使用的转换,或者根据旧的进行转换。

    链接地址: http://www.djcxy.com/p/33177.html

    上一篇: What's so bad about Template Haskell?

    下一篇: Why doesn't Safe Haskell support Template Haskell?