Haskell的规模设计?

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

我经历了一系列的教程(写自己的计划是我最喜欢的,Real World Haskell紧随其后) - 但大多数程序都是相对较小的,而且是单一用途的。 另外,我不认为其中的一些特别优雅(例如,WYAS中的大量查找表)。

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

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

Haskell有相当的文献吗? 功能性程序设计(单子,箭头,应用等)中的外来控制结构的动物园如何最适​​用于此目的? 你可以推荐哪些最佳实践?

谢谢!

编辑(这是唐斯图尔特回答的后续):

@dons提到:“Monads可以捕获类型中的关键架构设计。”

我想我的问题是:如何以纯粹的功能语言来思考关键架构设计?

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

以合适的方式将数据和解析步骤联系起来的“正确”方法是什么? 人们可以编写一个大功能,为各种数据类型做正确的事情。 或者可以使用monad来跟踪到目前为止已处理的内容,并让每个处理步骤从monad状态下一步获得所需内容。 或者可以编写大量单独的程序并发送消息(我不太喜欢这个选项)。

他链接的幻灯片有一个东西我们需要的项目符号:“用于将设计映射到类型/函数/类/ monads上的习语”。 成语是什么? :)


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

类型系统

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

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

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

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

  • Monads以类型捕获关键的体系结构设计(此代码访问硬件,此代码是单用户会话等)
  • 例如xmonad中的X monad,可以精确地捕获系统中哪些组件可见状态的设计。
  • 输入类和存在类型

  • 使用类型类来提供抽象:隐藏多态接口背后的实现。
  • 并发性和并行性

  • 潜行par到你的程序打,方便,可组合并行的竞争。
  • 重构

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

  • FFI使用外国代码玩起来更容易,但是外国代码可能很危险。
  • 在对返回数据的形状进行假设时要非常小心。
  • 元编程

  • 一点模板Haskell或泛型可以删除样板。
  • 包装和分销

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

  • 使用-Wall让你的代码保持清新的气味。 你也可以看看Agda,Isabelle或Catch寻求更多的保证。 对于类似皮棉的检查,请参阅伟大的hlint,这将提出改进建议。
  • 借助所有这些工具,您可以随时掌握复杂性,尽可能多地移除组件之间的交互。 理想情况下,你有一个非常大的纯代码库,这很容易维护,因为它是合成的。 这并非总是可行,但值得一试。

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


    Don给了你上面大部分的细节,但是这是我在做Haskell的系统守护进程等真正有价值的有状态程序时的两分钱。

  • 最后,你住在monad变压器堆栈中。 最后是IO。 在此之上,每个主要模块(抽象意义上而不是单元模块意义上的)将其必要状态映射到该堆栈中的一个层。 因此,如果你的数据库连接代码隐藏在一个模块中,你可以将它全部写入MonadReader Connection m => ... - > m ...类型,然后你的数据库函数总是可以在没有其他函数的情况下获得它们的连接模块必须知道它的存在。 你可能最终会得到一层承载你的数据库连接,另一个你的配置,第三个你的各种信号量和mvars用于解决并行和同步,另一个你的日志文件处理等。

  • 首先找出你的错误处理。 Haskell在大型系统中最大的弱点是大量的错误处理方法,包括糟糕的错误处理方法(这是错误的,因为你无法返回任何错误信息;除非你真的使用Either而不是Maybe只是意味着缺失值)。 弄清楚你将如何首先完成它,并从你的库和其他代码使用的各种错误处理机制中设置适配器到最终的适配器中。 这会在以后拯救你一个悲伤的世界。

  • 附录 (摘自评论;感谢Lii&liminalisht) -
    更多讨论关于将大型程序切割成单片机的不同方式:

    本Kolera给出了一个很大的实用介绍到这个话题,和布赖恩·赫特讨论解决的问题lift荷兰国际集团一元行动统一到您的自定义单子。 乔治威尔逊展示了如何使用mtl编写与任何实现所需类型类的monad(而不是您自定义的monad类)兼容的代码。 Carlo Hamalainen写了一些总结乔治谈话的简短有用的笔记。


    在Haskell中设计大型程序与在其他语言中进行设计没有什么不同。 大规模的编程就是将你的问题分解成易于管理的部分,以及如何将它们融合在一起; 实现语言不那么重要。

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

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

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

    上一篇: scale design in Haskell?

    下一篇: Is there a conditional ternary operator in VB.NET?