生产中的Haskell详细指南

903 阅读12分钟

生产中的Haskell

在我们的Haskell in Production系列中,我们采访了在实际工作中使用 Haskell 的公司的开发人员和技术领导。我们涵盖了好处、坏处、常见的陷阱,以及构建有用的Haskell产品的技巧。

我们今天的嘉宾是Max Tagher。他是Mercury的联合创始人和CTO,该公司为初创企业提供银行产品。进一步阅读,了解Mercury在哪些方面使用了Haskell,他们为什么选择它,以及他们喜欢它的原因。

采访Max Tagher

你能给我们的读者简单介绍一下Mercury和你在那里的角色吗?

Mercury提供银行*产品,使创始人能够建立伟大的初创企业。我们提供免月费的支票和储蓄账户、借记卡、ACH支付、支票以及免费的国内和美元国际电汇。作为补充,还有一些高级功能,如财务管理、用户权限和风险债务贷款。Mercury的主要竞争优势包括拥有一个优秀的网站、在线注册、几乎没有费用,以及一个了解初创企业运作的合规部门。你可以在我们的演示网站上看到Mercury的每个功能。

我是Mercury的联合创始人之一,我还作为CTO管理工程部门。

Card - Max_Tagher.png

*Mercury是一家金融技术公司,不是银行。银行服务由Choice Financial Group和Evolve Bank & Trust®提供;FDIC成员。

在你们的堆栈中,哪里使用了Haskell?

Mercury的后端完全采用Haskell。为了完整起见,我们的堆栈在前端使用TypeScript和React,基础设施使用Nix和AWS,数据库使用Postgres,iOS使用Swift,Android使用Kotlin。

你为什么决定为这个项目选择Haskell?

在我和我的联合创始人工作的前一家公司,我们用Ruby on Rails做后端。我发现这种语言和框架的动态性质意味着我们有很多不必要的运行时错误,比如相当于空指针的错误,甚至是打字错误。我们知道我们想为Mercury使用静态类型的东西,因为客户对他们的银行有更高的正确性期望。

我看了一些不同的语言,但我并不满意。共同的主题是类型系统的限制(例如Go),对某些基础语言的依赖(例如Scala继承了很多Java的缺陷,可能需要学习这两种语言),或者语言的不成熟性。Haskell有一个非常强大的类型系统,而且从一开始就是这样建立的,所以图书馆很可能会使用这些功能。

我曾经在开源背景下使用过Haskell,为Yesod网络框架和Persistent数据库库做出过贡献,所以我对它的好处很熟悉。我还认识一些Haskell社区的人,我可以向他们寻求建议和推荐。

最后,我真的很喜欢写Haskell。

Haskell有什么特别的品质使它对金融技术项目有价值吗?

Haskell在金融和加密货币背景下有一个更常用的声誉。我认为这主要是作为一种通用的编程语言,具有很好的正确性属性,也许还有很好的解析库--我不认为有相当于Erlang的OTP或Python的统计库,使Haskell成为这些领域的明显选择。

为了详细说明该语言的一般优点,最简单的Haskell特性使数据建模准确成语化,毫不费力。

  • Newtypes使得创建像RoutingNumberAccountNumber 这样的类型只需一行,这些类型在运行时的功能是Text ,但在编译时不能互换。你可以额外定制这些类型的功能,例如,不支持ToJSON 操作的HashedPassword ,而继承Text的默认ToJSON 实现的RoutingNumber

  • 代数数据类型允许对不同形状的数据进行建模,例如data TransferOrigin = ManualTransfer UserId | AutomatedTransfer

  • MaybeEither 这样的数据类型(以及相应的缺乏null )有助于对可选数据进行建模和检查错误情况。

我要特别强调的是,所有这些在Haskell中是完全正常和无聊的。许多这些东西在其他语言中也是可以实现的,但往往感觉很笨拙、不规范,而且在库中也不是默认的。

除了这些核心功能外,我们还受益于:

  • 类型安全的SQL连接,确保两个表通过每个表上的UserId 进行连接。

  • 编译时的元编程,消除脆弱的模板。

  • 一个绿色线程并发模型,不需要额外的思考。

当人们考虑建立初创公司时,Haskell通常不是他们想到的第一种语言。你能谈一谈你用这种语言建立创业公司的经验吗?

我们的经验很好。Mercury特别适合使用Haskell,因为这家初创公司有明确的目标(建立银行的核心功能),并且重视正确性。雇用可能是最强大的好处,正如我在下面描述的那样。

在开发项目时,你是否遇到了Haskell的缺点?如果有,你能描述一下这些吗?

  • Mercury开始时,IDE的支持很弱,目前还不能处理我们代码库的规模。我们正在签约解决这个问题。

  • 无论好坏,Haskell有很多做事的方法。错误处理尤其缺乏既定的模式。

  • 我们是使用Haskell的较大的公司之一,遇到了MacOS链接器和编译器性能的问题,我们把它承包出去解决。

  • 文档在功能层面上往往很好,但在集成层面上却很差。特别是开始使用一个库可能会很棘手。

你能列举一些你的团队在开发Mercury时发现非常有用的Haskell库,并且你想把它们作为特色吗?

Haskell包的生态系统非常庞大,我们直接依赖于194个开源包。当我看到这些开放源码作者对赞助持开放态度时,我就会尽力支持他们。

我想特别感谢Michael Snoyman创造了YesodPersistent,也感谢他对软件开发的务实态度。如果不是他的工作,Mercury很可能永远不会使用Haskell。

你使用什么样的效果系统:RIO, mtl, fused-effects, Polysemy, 还是别的什么?

我们基本上使用 "ReaderT over IO "模式,所以类似于RIO。mtl ,但我们没有复杂的单体转换栈。

我对更高级的效果系统不是很熟悉,但我一般会不鼓励人们用它们来建立一个公司。总的来说,我认为好处是有限的,复杂性更高,而且有很多未知的未知数,你会用它们来处理。

据我所知,你把一些GHC工作承包给了Haskell软件开发公司。你能谈谈这对Mercury的好处吗?是由于潜在的收益还是你遇到的编译器的缺陷?

我们已经把工作承包给了几家公司,目前是Tweag、Well-Typeed和Foxhound。我们主要承包了GHC(编译器)和HLS(IDE工具)的改进,特别是在性能方面。

签约是很有帮助的,因为它让我们专注于建立我们的产品,并征集GHC专家进行编译器性能方面的工作。这特别有帮助,因为这些人通常对如何开始工作有很好的想法,或者已经有了正在进行的工作,而新的开发者不会知道这些。

也就是说,我们现在已经大到可以开始建立自己的内部团队了--我们刚刚雇用了我们的第一个GHC开发人员。

你们的团队在招聘Haskell开发人员时遇到过什么困难吗?

没有,事实上,我们的人事副总裁曾经说过,后端Haskell工程师是整个公司中 "最容易招聘的角色",在我看来,招聘是Haskell的 "杀手级应用"。我认为有几件事促成了这一点:

  1. 对Haskell工作的需求比市场提供的更多。

  2. 太多的公司为普通语言招聘。我们的招聘人员必须发送10倍于TypeScript工作的外联活动才能得到回应。

  3. 许多优秀的开发人员对Haskell感兴趣。

  4. 对Haskell的兴趣可以作为开发人员质量基线的一个适当的代理。

(参见Paul Graham的"Python悖论")

招聘对于初创企业来说是绝对关键的。如果你在招聘方面没有优势(自动驾驶汽车、治疗癌症、现实扭曲场等),你可能很难吸引和留住人才。早期,在我们有任何名气之前,Haskell是Mercury的一大亮点。早期拥有优秀的开发人员可以建立一个强大的基础,并吸引那些希望与其他优秀工程师一起工作的人。

我们现在有超过100名开发人员,我们继续招聘有丰富Haskell经验的人,以及完全没有Haskell经验的人。如果你想成为这些人中的一员,请查看mercury.com/jobs。

你们是否有任何内部培训计划来教授或提高Haskell开发人员的技能?

是的,我们内部有一个教人学习Haskell的团队,由Matt Parsons负责。我认为拥有这样一个项目是非常重要的,因为如果你是从大多数命令式语言过来的,那么Haskell可能是相当陌生的。我们的培训项目通常需要6到8周的时间来完成,混合了书本学习和1对1指导。

据我所知,你们也使用Nix,这似乎是现在Haskell团队的一个流行选择。你能用几个词来描述一下你的团队使用Nix的经验吗?

我的答案是 "混合"。不止是几个字。

Nix是一个软件集合,对包的管理有独特的见解。不是像pythonpython3 那样的单一全局包,而是多个版本的依赖关系可以并存。每个包在文件系统中都是通过其内容的哈希值来定位的。这需要确定性地构建软件包,用Nix语言进行配置。还有一个使用Nix进行配置的Linux发行版(NixOS),一个使用Nix指定的包来创建shell的开发工具(nix-shell ),以及更多。我们使用Nix做了很多事情。

我们在服务器上运行NixOS,使用它进行部署(但不是通过NixOps),并部分用于CI(尽管我们正在从Hydra迁移)。我们在前端、后端和Android开发环境中使用nix-shell

好处是:

  • 简单的开发环境设置。你仍然需要设置Nix,但这是一个持续的成本,而设置却一直在增加复杂性。

  • 稳定的开发环境。你不需要处理一半的团队使用旧的GHC版本,或者因为Debian带有旧的C库而出现奇怪的错误。一个推论是:支持各种各样的Linux发行版更容易。

  • 缓存的依赖性。

  • 整个NixOS操作系统的即时回滚。

缺点是:

  • 修改第三方Haskell包的开发工作流程是很艰巨的。它经常涉及到大量的重新编译,并使迭代一个开源包的经验比Stack等差得多。(据称有一些解决方法,虽然我们还没有开始使用)。

  • Nix的文档相当糟糕。寻找一个包的选项往往需要挖掘源代码。

  • Nix语言本身感觉非常不符合人体工程学。奇怪的语法,缺乏类型,错误信息很差。

  • 你将需要雇用对Nix有经验的人。

  • Stack不支持通过Nix指定包,所以你失去了Stack的功能,如--file-watch

在我们的内部调查中,有些人说这是在Mercury工作的最好的事情,有些人说这是最差的事情。你对Nix的满意程度可能在很大程度上取决于你过去的经历和当前的角色--记忆中的一次痛苦的Ruby系统升级,是在staging中仔细测试过的--但还是破坏了生产--现在马上就回滚了。但反过来说,你使用stack 来修补一个依赖,现在可能需要重新编译所有的自定义依赖,把30秒的迭代周期变成30分钟,因为amazonka 构建。

总的来说,我对Nix很满意,但对它的认可度不高。

对于其他想用Haskell启动的团队,你有什么建议?

在你用Haskell开公司之前,我会先适应Haskell--至少足以用它写一个小的webapp。我主要会写一些命令式的代码,避免写一些看起来 "花哨 "的代码。不要为do符号感到尴尬,也不要觉得需要使用运算符或无点风格。

如果你是一个单独的开发者,我对使用Haskell会比较犹豫(我认为类型带来的好处较少,但还是比你预期的要多)。如果对你的业务来说,开发速度比正确性更重要的话,我同样会更加犹豫不决。

当我开始使用Mercury时,我想通过选择其他可靠的技术来限制我的 "复杂性预算"。Haskell在这方面的成本比我想象的要低,但我仍然建议大多数初创公司使用单一的Postgres数据库(包括用于排队作业),采用单片式架构,并部署在AWS。在不太普遍的情况下,我可能会建议使用React+TypeScript SPA和本地iOS和Android应用程序,使用JSON作为交换格式。

我想提醒大家不要打算从其他东西开始,然后转到Haskell。现在绝不是重写的好时机,你会失去Haskell的一些招聘优势,而且你的团队可能不喜欢这种转换。这绝对是可能的,但 "用动态语言做原型,用静态语言大规模重写 "是很冒险的建议。