本文介绍了Haskell 中的大规模设计?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

设计/构建大型函数式程序的好方法是什么,尤其是在 Haskell 中?

What is a good way to design/structure large functional programs, especially in Haskell?

我已经阅读了很多教程(我最喜欢自己编写一个方案,紧随其后的是 Real World Haskell)——但大多数程序都相对较小,而且用途单一.此外,我不认为其中一些特别优雅(例如,WYAS 中的大量查找表).

I've been through a bunch of the tutorials (Write Yourself a Scheme being my favorite, with Real World Haskell a close second) - but most of the programs are relatively small, and single-purpose. Additionally, I don't consider some of them to be particularly elegant (for example, the vast lookup tables in WYAS).

我现在想要编写更大的程序,有更多的活动部分 - 从各种不同的来源获取数据,清理它,以各种方式处理它,在用户界面中显示它,持久化它,通过网络通信等. 怎样才能最好地构建这样的代码,使其清晰易读、易于维护并适应不断变化的需求?

I'm now wanting to write larger programs, with more moving parts - acquiring data from a variety of different sources, cleaning it, processing it in various ways, displaying it in user interfaces, persisting it, communicating over networks, etc. How could one best structure such code to be legible, maintainable, and adaptable to changing requirements?

有大量文献针对大型面向对象命令式程序解决了这些问题.MVC、设计模式等想法是实现广泛目标(如面向对象风格的关注点分离和可重用性)的不错方法.此外,较新的命令式语言适合随着成长而设计"的重构风格,在我的新手看来,Haskell 似乎不太适合这种风格.

There is quite a large literature addressing these questions for large object-oriented imperative programs. Ideas like MVC, design patterns, etc. are decent prescriptions for realizing broad goals like separation of concerns and reusability in an OO style. Additionally, newer imperative languages lend themselves to a 'design as you grow' style of refactoring to which, in my novice opinion, Haskell appears less well-suited.

是否有 Haskell 的等效文献?函数式编程(monads、arrow、applicative 等)中可用的外来控制结构动物园如何最好地用于此目的?您可以推荐哪些最佳做法?

Is there an equivalent literature for Haskell? How is the zoo of exotic control structures available in functional programming (monads, arrows, applicative, etc.) best employed for this purpose? What best practices could you recommend?

谢谢!

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

EDIT (this is a follow-up to Don Stewart's answer):

@dons 提到:Monads 在类型中捕获关键的架构设计."

@dons mentioned: "Monads capture key architectural designs in types."

我想我的问题是:人们应该如何看待纯函数式语言中的关键架构设计?

I guess my question is: how should one think about key architectural designs in a pure functional language?

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

Consider the example of several data streams, and several processing steps. I can write modular parsers for the data streams to a set of data structures, and I can implement each processing step as a pure function. The processing steps required for one piece of data will depend on its value and others'. Some of the steps should be followed by side-effects like GUI updates or database queries.

以一种很好的方式将数据和解析步骤联系起来的正确"方式是什么?可以编写一个大函数来为各种数据类型做正确的事情.或者可以使用 monad 来跟踪到目前为止已处理的内容,并让每个处理步骤从 monad 状态中获取接下来需要的任何内容.或者可以编写大量独立的程序并四处发送消息(我不太喜欢这个选项).

What's the 'Right' way to tie the data and the parsing steps in a nice way? One could write a big function which does the right thing for the various data types. Or one could use a monad to keep track of what's been processed so far and have each processing step get whatever it needs next from the monad state. Or one could write largely separate programs and send messages around (I don't much like this option).

他链接的幻灯片有一个我们需要的东西:将设计映射到的习语类型/函数/类/单子".什么是习语?:)

The slides he linked have a Things we Need bullet: "Idioms for mapping design ontotypes/functions/classes/monads". What are the idioms? :)

推荐答案

我在 在 Haskell 中设计大型项目XMonad 的设计和实现. 大型工程是关于管理复杂性.Haskell 中用于管理复杂性的主要代码结构机制是:

I talk a bit about this in Engineering Large Projects in Haskell and in the Design and Implementation of XMonad. Engineering in the large is about managing complexity. The primary code structuring mechanisms in Haskell for managing complexity are:

类型系统

  • 使用类型系统来强制抽象,简化交互.
  • 通过类型强制执行关键不变量
    • (例如,某些值不能逃脱某些范围)
    • 那个代码不做IO,不接触磁盘

    分析器

    • 为您的程序的堆和时间配置文件提供客观证据.
    • 尤其是堆分析,是确保不使用不必要的内存的最佳方法.

    纯度

    • 通过移除状态显着降低复杂性.纯函数式代码可以扩展,因为它是组合式的.您所需要的只是确定如何使用某些代码的类型——当您更改程序的其他部分时,它不会神秘地中断.
    • 使用大量模型/视图/控制器"风格的编程:尽快将外部数据解析为纯函数式数据结构,对这些结构进行操作,然后在所有工作完成后,渲染/刷新/序列化.保持大部分代码纯净

    测试

    • QuickCheck + Haskell 代码覆盖率,以确保您正在测试无法通过类型检查的内容.
    • GHC + RTS 非常适合查看您是否在 GC 上花费了太多时间.
    • QuickCheck 还可以帮助您识别模块的干净、正交的 API.如果代码的属性难以说明,则它们可能太复杂了.继续重构,直到你有一组干净的属性来测试你的代码,这些属性组合得很好.那么代码也可能设计得很好.

    用于构建的单子

    • Monads 捕获关键的架构设计类型(此代码访问硬件,此代码是单用户会话等)
    • 例如xmonad 中的 X monad 精确地捕获了系统的哪些组件可见的状态的设计.

    类型类和存在类型

    • 使用类型类提供抽象:将实现隐藏在多态接口后面.

    并发和并行

    • par 加入到您的程序中,通过简单、可组合的并行性在竞争中脱颖而出.
    • Sneak par into your program to beat the competition with easy, composable parallelism.

    重构

    • 您可以在 Haskell 中重构很多.如果您明智地使用类型,这些类型可确保您的大规模更改是安全的.这将有助于您的代码库扩展.确保您的重构在完成之前会导致类型错误.
    • You can refactor in Haskell a lot. The types ensure your large scale changes will be safe, if you're using types wisely. This will help your codebase scale. Make sure that your refactorings will cause type errors until complete.

    明智地使用 FFI

    • FFI 使使用外来代码更容易,但外来代码可能很危险.
    • 对返回的数据形状的假设要非常小心.

    元编程

    • 一些模板 Haskell 或泛型可以删除样板.

    包装和分发

    • 使用阴谋集团.不要推出自己的构建系统.(实际上,您现在可能想使用 Stack 来开始使用.
    • 使用 Haddock 编写好的 API 文档
    • graphmod 等工具可以显示您的模块结构.
    • 尽可能依赖 Haskell 平台版本的库和工具.这是一个稳定的基地.(同样,这些天您可能想要使用 Stack 来建立和运行稳定的基础.)
    • Use Cabal. Don't roll your own build system. ( Actually you probably want to use Stack now for getting started.).
    • Use Haddock for good API docs
    • Tools like graphmod can show your module structures.
    • Rely on the Haskell Platform versions of libraries and tools, if at all possible. It is a stable base. ( Again, these days you likely want to use Stack for getting a stable base up and running.)

    警告

    • 使用 -Wall 使您的代码没有异味.您也可以查看 Agda、Isabelle 或 Catch 以获得更多保证.对于类似 lint 的检查,请参阅很棒的 hlint,它会提出改进建议.
    • Use -Wall to keep your code clean of smells. You might also look at Agda, Isabelle or Catch for more assurance. For lint-like checking, see the great hlint, which will suggest improvements.

    使用所有这些工具,您可以处理复杂性,尽可能消除组件之间的交互.理想情况下,你有一个非常大的纯代码基础,这真的很容易维护,因为它是组合式的.这并不总是可能的,但值得瞄准.

    With all these tools you can keep a handle on complexity, removing as many interactions between components as possible. Ideally, you have a very large base of pure code, which is really easy to maintain, since it is compositional. That's not always possible, but it is worth aiming for.

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

    In general: decompose the logical units of your system into the smallest referentially transparent components possible, then implement them in modules. Global or local environments for sets of components (or inside components) might be mapped to monads. Use algebraic data types to describe core data structures. Share those definitions widely.

    这篇关于Haskell 中的大规模设计?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-02 15:28