Haskell的大规模devise?

devise/构造大型函数程序的好方法是什么,特别是在Haskell中?

我已经经历了一些教程(写自己的计划是我最喜欢的,真正的世界哈斯克尔秒) – 但大多数的程序是相对较小,单一目的。 此外,我不认为其中的一些是特别优雅的(例如,WYAS中的大量查找表)。

我现在想编写更大的程序,更多的移动部分 – 从各种不同的来源获取数据,清理,以各种方式处理数据,在用户界面中显示数据,坚持使用数据,通过networking进行通信等等。一个最好的结构,这样的代码是清晰的,可维护的,适应不断变化的需求?

有相当多的文献针对大面向对象的命令式程序来解决这些问题。 像MVC,devise模式等理念是实现广泛目标的体面处方,如OO风格的分离关注和可重用性。 另外,较新的命令式语言适合于“devise成长”的重构风格,在我的新手观点中,Haskell显得不太合适。

Haskell有没有相同的文献? function性程序devise(单子,箭头,应用等)中的外来控制结构动物园如何最好地用于此目的? 你可以推荐哪些最佳做法?

谢谢!

编辑(这是Don Stewart的回答):

@dons提到:“Monads可以捕获types中的关键架构devise”。

我猜我的问题是:如何以纯粹的function语言思考关键的build筑devise?

考虑几个数据stream的例子,以及几个处理步骤。 我可以将数据stream的模块化parsing器编写成一组数据结构,并且可以将每个处理步骤作为纯函数来实现。 一个数据所需的处理步骤取决于其值和其他值。 一些步骤应该跟随像GUI更新或数据库查询的副作用。

什么是“正确”的方式来绑定数据和parsing步骤,以一个很好的方式? 人们可以写一个很大的函数来为各种数据types做正确的事情。 或者可以使用monad来跟踪到目前为止已经处理了什么,并且每个处理步骤从monad状态接下来需要的任何东西。 或者可以写很大程度上单独的程序和发送消息(我不太喜欢这个选项)。

他链接的幻灯片有一个东西我们需要的项目符号:“映射devise到types/function/类/单子的习语”。 成语是什么? 🙂

我在Haskell的工程大型项目 以及XMonad的devise和实现方面谈了一些这方面的内容。 大型工程是关于pipe理复杂性的。 Haskell中用于pipe理复杂性的主要代码结构化机制是:

types系统

  • 使用types系统来强制抽象,简化交互。
  • 通过types强化关键不variables
    • (例如,某些值不能逃避某些范围)
    • 某些代码没有IO,不接触磁盘
  • 强化安全性:检查exception(可能/任一),避免混合概念(Word,Int,Address)
  • 良好的数据结构(如拉链)可以使某些类的testing不必要,因为它们可以静态排除出界错误。

分析器

  • 提供您的程序堆和时间configuration文件的客观证据。
  • 特别是堆分析是确保不必要的内存使用的最佳方法。

纯度

  • 通过消除状态显着降低复杂性。 纯粹的function代码缩放,因为它是组成。 所有你需要的是确定如何使用一些代码的types – 当你改变程序的其他部分时,它不会神秘地破坏。
  • 使用大量的“模型/视图/控制器”风格的编程:尽可能快地将外部数据parsing成纯粹function性的数据结构,对这些结构进行操作,然后一旦所有工作完成,就进行渲染/刷新/序列化。 保持大部分代码纯净

testing

  • QuickCheck + Haskell代码覆盖率,以确保您正在testing的东西,你不能检查types。
  • GHC + RTS非常适合用来观察GC是否花费太多时间。
  • QuickCheck还可以帮助您识别模块的干净,正交的API。 如果代码的属性很难说明,那么它们可能太复杂了。 保持重构,直到你有一套干净的属性可以testing你的代码,组成良好。 那么代码也可能devise的很好。

Monads的结构

  • Monads以types捕获关键的架构devise(这个代码访问硬件,这个代码是一个单用户会话等)。
  • 例如xmonad中的X monad,可以准确捕获系统中哪些组件可见的状态的devise。

input类和存在types

  • 使用types类来提供抽象:隐藏多态接口背后的实现。

并发性和并行性

  • 潜入你的程序,以简单,可组合的并行性击败竞争对手。

重构

  • 你可以在Haskell中重构很多东西 。 如果您明智地使用types,这些types可以确保您的大规模更改是安全的。 这将有助于您的代码库的规模。 确保重构将导致types错误,直到完成。

明智地使用FFI

  • FFI使得使用外国代码变得更容易,但是外国代码可能是危险的。
  • 对返回的数据的形状进行假设时要非常小心。

元编程

  • 一点模板Haskell或generics可以删除样板。

包装和分销

  • 使用Cabal。 不要推出自己的构build系统。 (编辑:其实你可能想要现在使用堆栈入门。)。
  • 使用Haddock获得良好的API文档
  • 像graphmod这样的工具可以显示你的模块结构。
  • 依靠Haskell平台版本的库和工具,如果可能的话。 这是一个稳定的基地。 (编辑:同样,现在你可能想用Stack来获得一个稳定的基础并运行。)

警告

  • 使用-Wall保持你的代码清洁的气味。 你也可以看看Agda,Isabelle或Catch寻求更多的保证。 对于类似皮毛的检查,请参阅伟大的hlint ,这将提示改进。

利用所有这些工具,您可以保持复杂性,尽可能多地移除组件之间的交互。 理想情况下,你有一个非常大的纯代码基础,这是很容易维护,因为它是组成。 这并不总是可能的,但这是值得的。

通常: 将系统的逻辑单元分解成可能的最小的透明元件,然后在模块中实现它们。 组件(或内部组件)的全局或本地环境可能会映射到monad。 使用代数数据types来描述核心数据结构。 广泛分享这些定义。

Don给了你上面大部分的细节,但是在Haskell中,我做了两件事,一件是真正的有状态程序,比如系统守护进程。

  1. 最后,你住在monad变压器堆栈中。 最后是IO。 在此之上,每个主要模块(抽象意义上,而不是一个模块中的意义上的)将其必要的状态映射到该堆栈中的一个层。 所以,如果你的数据库连接代码隐藏在一个模块中,你可以把它全部写入MonadReader Connection m => … – > m …types,然后你的数据库函数总是可以不用其他函数模块必须意识到它的存在。 你可能会得到一个图层携带你的数据库连接,另一个你的configuration,第三个你的各种信号量和mvars解决并行和同步,另一个你的日志文件处理等。

  2. 找出你的error handling第一 。 Haskell在大型系统中最大的弱点是大量的error handling方法,包括糟糕的error handling方法,比如Maybe(这是错误的,因为你不能返回任何错误的信息;总是使用Either而不是Maybe,除非你真的只是意味着缺less的价值)。 弄清楚你将如何去做,并从你的库和其他代码使用的各种error handling机制中设置适配器到最后一个。 这会在以后拯救你一个悲伤的世界。

附录 (摘自评论;感谢Lii & liminalisht ) –
更多的关于不同的方法来将一个大的程序切割成一个堆栈单元的讨论:

Ben Kolera为这个话题提供了一个很好的实用介绍, Brian Hurt讨论了解决Monadic行为问题的解决scheme。 乔治·威尔逊展示了如何使用mtl编写与任何实现所需的types类的monad(而不是您自定义的monad类)配合使用的代码。 Carlo Hamalainen写了一些总结乔治谈话的简短有用的笔记。

在Haskell中devise大型程序与在其他语言中完成程序并没有什么不同。 大规模的编程是把你的问题分解成可pipe理的部分,以及如何把它们融合在一起; 实现语言不那么重要。

也就是说,在一个大型devise中,尝试和利用types系统来确保你只能以正确的方式把你的作品assembly在一起。 这可能涉及新types或幻像types,使看起来具有相同types的东西不同。

在进行代码重构时,纯度是一个很大的好处,所以尽量保持尽可能纯的代码。 纯代码很容易重构,因为它与程序的其他部分没有隐藏的交互。

本书第一次学习了结构化函数式编程。 这可能不是你正在寻找的东西,但是对于函数式编程的初学者来说,这可能是学习构build函数式程序的最好的第一步 – 与规模无关。 在所有的抽象层次上,devise应该总是有清晰的结构。

函数式编程的工艺

函数式编程的工艺

http://www.cs.kent.ac.uk/people/staff/sjt/craft2e/

我目前正在写一本题为“Functional Design and Architecture”的书。 它为您提供了一套完整的技术,如何使用纯function方法构build一个大型应用程序。 它描述了许多function模式和想法,同时构build了一个SCADA类似的应用程序“仙女座”从头开始控制飞船。 我的主要语言是Haskell。 本书涵盖:

  • 使用图表进行build筑模型的方法;
  • 需求分析;
  • embedded式DSL域build模;
  • 外部DSLdevise和实现;
  • Monads作为具有效果的子系统;
  • 免费monads作为function接口;
  • 箭头eDSLs;
  • 使用免费的一次性eDSL进行控制反演;
  • 软件事务内存;
  • 镜头;
  • 国家,读者,作家,RWS,ST monads;
  • 不纯状态:IORef,MVar,STM;
  • multithreading和并行域build模;
  • GUI;
  • 主stream技术和方法(如UML,SOLID,GRASP)的适用性;
  • 与不纯的子系统交互。

您可能熟悉这本书的代码,以及“Andromeda”项目代码。

我希望在2017年底完成本书。在这之前,您可以在这里阅读我的文章“function编程中的devise和架构”(Rus)。

UPDATE

我在网上分享了我的书(前5章)。 在Reddit上查看post

加布里埃尔的博客文章可伸缩程序架构可能值得一提。

Haskell的devise模式与主streamdevise模式有一个重要的区别:

  • 常规体系结构 :将几个组件组合在一起,生成typesB的“networking”或“拓扑”

  • Haskell体系结构 :将几个A类组件组合在一起生成一个相同typesA的新组件,与其替代品无法区分

经常让我觉得,一个显而易见的优雅的build筑往往倾向于从performance出这种同质感的图书馆中以一种自下而上的方式走出来。 在Haskell中,这尤其明显 – 传统上被认为是“自上而下”架构的模式往往会在像mvc , Netwire和Cloud Haskell这样的库中被捕获。 也就是说,我希望这个答案不会被解释为在这个线程中试图取代任何其他的东西,只是结构select可以而且应该被领域专家理想地抽象出去。 在我看来,build立大型系统的真正困难在于评估这些图书馆的架构“善”与你所有的实际问题。

正如评论中提到的那样, 类别devise模式是Gabriel关于这个话题的另一篇文章,也是类似的。

我发现Alejandro Serrano的论文“ 使用Haskell教学软件架构 ”(pdf)对于思考Haskell中的大规模结构很有用。

也许你必须退一步思考如何把问题的描述转化为devise。 由于Haskell是如此之高,它可以以数据结构的forms捕获问题的描述,作为过程的行为和作为函数的纯粹的转换。 那么你有一个devise。 编译这段代码时,开始开始,find代码中遗漏字段,缺less实例和缺lessmonadic变换器的具体错误,因为例如在IO过程中需要某个状态monad的库来执行数据库访问。 瞧,这个节目。 编译器提供您的心理素描,并使devise和开发保持一致。

以这样的方式,您从一开始就从Haskell的帮助中获益,编码是自然的。 如果你想到的是一个具体的普通问题,我不会在乎做一些“function性”,“纯粹的”或者一般的东西。 我认为过度工程是IT中最危险的事情。 当问题是创build一个抽象一系列相关问题的库时,情况就不同了。