哈斯克尔的痛点:实践总结

2020-06-08 15:47:21

在这篇文章的开头,我想说哈斯克尔很棒。几年来,我一直将它作为本机应用程序的首选。接下来的一切都是我对这门语言的热爱和喜爱的产物,也是我希望看到它成功的愿望的产物。所有这些都不是为了贬低人们改善哈斯克尔生态系统的努力。这是一项艰苦的工作,而且工作量很大,我感谢你们所有人到目前为止所做的一切。

到目前为止,在使用Haskell时有许多经常出现的痛点。这些都是我自己和我的一些好朋友在哈斯克尔接受不同程度的教育和实践的经历的产物。如果你是一个无畏的哈斯克勒,你可能已经知道了其中的一些或全部。我想把它们列在这里,原因有几个:

首先,明确痛点有望鼓励哈斯凯勒考虑如何克服它们。已经尝试过了(我们稍后会讲到这一点!)。但考虑到问题仍然存在,还有工作要做。

其次,如果您是Haskell的学习者,我希望它能帮助您看到这些都是社区中已知的问题,而不仅仅是“做错了”。人们意识到这些东西需要改进,在你之前的很多人都遇到过完全相同的问题。

如果你只想要皮包骨的文章,请查看文章的最后一页,以获得该文章的速记版本。

与人们普遍认为的相反,有多个Haskell编译器。即便如此,绝大多数应用程序代码的编写都牢牢记住了GHC编译器。这是有充分理由的。最关键的是,GHC投入了最多的资金和时间,因此比其他编译器更快地适应新概念。由于包含了TupleSections、RecordWildCards、OverloadedStrings等语言杂注,几乎所有最初为GHC编写的产品代码都不能与任何其他编译器一起使用。不过,这本身并不是一个痛点;Haskell.org只是建议下载GHC。(除了在Arch Linux上,这完全是另一回事)。

下载GHC后开始出现问题。在撰写本文时,Haskell 1.0已经过去了30年,还没有定制的Haskell IDE。没有针对任何主流文本编辑器的功能强大的Haskell插件可以在没有命令行干预的情况下安装1。单独使用这两个插件都会有一些问题;加在一起就足以让人们完全远离这门语言了。很难责怪他们。

在这方面是有希望的。工具社区似乎一致同意将Haskell语言服务器作为标准。卢克·劳写了一个很棒的总结,我在这里不公平地总结。但是告诉人们“嘿,在我们解决整个问题之前不要工具就行了”,或者“坚持使用这个旧版本的GHC,它才是有效的”,这让人感觉相当粗暴。

目前,我建议人们寻找一个只与GCI集成的扩展,比如Visual Studio代码的简单GHC(Haskell)集成。没有活动部件是一个优点。我还强烈推荐非常棒的工具ghCID,它提供开箱即用的实时重新加载和测试,同样也是围绕GHCI构建的,但在相关的情况下与堆栈集成在一起。

阴谋集团太酷了。它的功能非常强大,而且每一次发布都会变得更加强大。

你知道还有什么超级酷,功能丰富,可以帮助人们编写计算机程序吗?吉特。作为一个杂乱无章的工具,即使是很小的任务也有多个界面,难怪Git有100万零1本书致力于以最实用的方式使用它。相比之下,卡巴尔有“卡巴尔用户指南”(Cabal User‘s Guide)。不幸的是,这本指南在指导方面做得并不多。例如,它没有告诉您2020年使用Cabal的首选方式是使用v2前缀的命令,这些命令直到第七章的第五章才被介绍,指南2中大约有4万字。

在我看来,许多人推荐堆栈而不是阴谋的主要原因有两个。首先,违约是明智的。如果您键入STACK BUILD,则说明您的操作是正确的。其次,你不需要知道它的一切,才能让它做你想做的事情。作为一名有五年经验的Haskell程序员,我仍然太害怕阴谋集团,不敢把它作为我的日常驱动程序,即使是小任务,我也会使用堆栈。

哦,这也无济于事,haskell.org上的文档(这是我的第一个搜索结果)比Cabal.readthedocs.io上的文档落后三个次要版本,后者本身就比通常最前沿的Arch Linux存储库3.harumph上的默认版本早一个次要版本。

如果您是Haskell编程新手,堆栈仍然是阻力最小的途径。我期待着有一天,我可以说,阴谋就是我们所需要的一切。

Monad的发明可能是上四分之一世纪编程语言理论中最重要的一部分。它们是如此强大,以至于每当另一种语言甚至模仿它们在Haskell中的实现时,都会响起一片虚拟的欢呼声。尽管尝试了很多次,但我认为我们还没有找到最好的方法来解释它们到底是什么。维基百科的文章有一个很好的不解释的例子。

在华威大学,我们为一年级学生设计并讲授了一个很好的函数式编程模块。在该模块中,引入了Monad作为抽象顺序组合的机制。这似乎是一种相当有效的激励,但它也隐藏了相当多的复杂性。例如,List Monad不会以预期的方式按顺序组成。此外,效果合成顺序并不总是由(>;>;=)方向定义(例如,考虑反转状态单体)。

函数可以从容器的角度进行广泛的解释,其中FMAP修改容器中的值,而不修改容器本身。容器可以是结构化的(在数据函数器的情况下),也可以是计算性的(在控制函数器的情况下);这两个概念都不会超出盒子的解释。可以对单子进行类似的类比,但它们都在某些关键方面分崩离析。“单胞胎就像玉米煎饼”因其真实性而出名。

希望我们不久就能找到单曲教程的柏拉图式理想。同时,我们必须坚持以身作则。

然而,向新学习者介绍单体并不是唯一的痛点。朋友和同事指出,每当试图学习一个以Monad为核心的新图书馆时,都会出现类似的问题。

库很少记录组成效果的基本机制。因此,用户只能猜测库的核心实际上是做什么的。即使是一些记录最好的图书馆似乎也遭受了这种痛苦。

我恳请库编写者预先定义其基本类型的功能。在此期间,请将逐个文档的文档视为比逐个博客帖子的文档更重要的优先事项。Haskell社区离不开博客圈(我们身边有很多有才华的博客作者,不包括我自己),但博客确实会随着时间的推移而衰落。内联文档是真理的来源,并且恰好在人们第一眼看到的地方找到。

如果任何人需要一个好的、可读的文档示例,即使是关于更高级的主题,我可以强烈推荐阅读Alexis King的一些库,例如EFF。

哈斯克尔的记录系统已经过时了。在抽象和最小约束类型的世界中,记录的单形性就像一个疼痛的大拇指一样突出。例如,没有内置的方式来表示“给定此形状的记录,我将生成与此额外字段相同的记录”。即使有两个共享字段的记录也很困难。这样的结构在许多现代语言中是很自然的。这种具体化经常被用作打击Haskell程序员的大棒。

已经开发了许多库,它们扩展或取代了内置记录的功能。这些库中的每一个都远远优于默认的事件状态,这让Haskell看起来很糟糕。

如果您是学习者,我建议您简单地认为在Haskell中合理的记录管理是不可能的,直到另行通知。如果您非常需要此功能,请参阅上面的库。

祈祷Haskell社区可以统一到一种方法上,至少足以构建到GHC中作为语言扩展,或者,理想情况下,可以进入Haskell报告。

坦率地说,GHC使用惊人数量的时间和内存来完成它的工作。这是一个众所周知的问题,由于缺乏资源,一些程序和库甚至无法在合格的硬件上编译。此外,对于第一次尝试Haskell的人来说,很长的编译时间是令人不快的。

这部分归因于Haskell库的另一个已知问题,即它们中的一些会将已知的领域作为依赖项拉进来。潘多克就是一个很好的例子。其结果是使用肮脏的变通办法,或者干脆放弃,购买一台功能更强大的机器。

但即便如此,我也不明白为什么编译一个有100个左右文件的程序要使用数十亿字节的内存来编译。我不确定我们对此能做些什么,因为GHC是如此庞大,除了随着时间的推移慢慢改进它的代码库之外。

对于库和应用程序开发人员,请尽可能停止依赖像Lens和Cabal这样庞大的库。我认为几乎任何程序都不需要依赖CABAL,而且镜头几乎总是可以用微镜头取代,这是在考虑到库开发人员的情况下设计的一个最小的重新实现。

在我为这篇文章做研究时,我遗漏了人们告诉我的一些痛点。这不是因为它们是无效的,而是因为它们中的大多数都是它们所处的学习环境的产物,可能不像上面列出的那样普遍。

这些问题都不是不可逾越的。事实上,其中许多目前正在修复过程中。但是,除非我们能直视对方的眼睛,承认他们的存在,否则人们将继续被拒之门外。

为了您的方便,我在下面用方便的抽认卡形式总结了上面的内容。

使用您最喜欢的文本编辑器,以及只依赖GHC和GCI的Haskell工具。

尽可能做到举一反三。四处打听你感兴趣的高级话题的介绍。

向大师学习,并与您的代码一起记录,而不仅仅是在博客帖子中。

如果你知道我在这里没有提到的常规问题,我很想听听。有很多要讨论的。

相信我,我都试过了。我已经试了好几年了。显然,emacs的haskell模式可能会很接近;我在这方面并没有取得成功。↩。

事实证明,这个问题是已知的,并有望很快得到解决。感谢Reddit上的George_t的指针。↩。

这甚至没有提到“单子是内函子范畴上的么半群”或“单子是终端双范畴上的松散半群”的经典例子,这两个例子都是对哈斯克尔社区某些成员的不公平但有趣的描述。↩

一个例子是非常棒的SBV库。它有一种符号类型,很多功能都基于该类型。我已经使用这个库很长时间了,但是如果你问我符号的Monad实例做了什么,我不能告诉你。在文档中,Symbol被描述为“SymbolicT到IO Monad的专门化”;SymbolicT被定义为“Symbol的泛化”。我希望问题清楚了。↩