Dan*_*Dan 565 monads haskell functional-programming large-scale
设计/构建大型功能程序的好方法是什么,特别是在Haskell中?
我已经阅读了很多教程(自己写一个方案是我最喜欢的,真实世界Haskell紧随其后) - 但大多数程序都相对较小,而且是单一目的.另外,我不认为它们中的一些特别优雅(例如,WYAS中的大量查找表).
我现在想要编写更大的程序,包含更多移动部件 - 从各种不同来源获取数据,清理数据,以各种方式处理数据,在用户界面中显示,持久化,通过网络进行通信等.一个最好的结构,这样的代码是易读,可维护,适应不断变化的要求?
有大量文献针对大型面向对象的命令式程序解决这些问题.像MVC,设计模式等的想法是实现广泛目标的理想规定,例如在OO风格中分离关注点和可重用性.此外,较新的命令式语言适合于"随着您的成长而设计"的重构风格,在我的新手看来,Haskell似乎不太适合.
Haskell有相同的文献吗?如何在功能性编程(单子,箭头,应用等)中使用异域控制结构的动物园最好地用于此目的?你能推荐什么最佳实践?
谢谢!
编辑(这是Don Stewart回答的后续行动):
@dons提到:"Monads在类型中捕获关键的建筑设计."
我想我的问题是:如何在纯函数式语言中考虑关键的架构设计?
考虑几个数据流的示例和几个处理步骤.我可以将数据流的模块化解析器编写为一组数据结构,我可以将每个处理步骤实现为纯函数.一个数据所需的处理步骤将取决于其值和其他数据.一些步骤之后应该是GUI更新或数据库查询等副作用.
什么是以正确方式绑定数据和解析步骤的"正确"方法?人们可以编写一个大功能,为各种数据类型做正确的事情.或者可以使用monad来跟踪到目前为止已处理的内容,并让每个处理步骤从monad状态获得接下来需要的任何内容.或者可以写很多单独的程序并发送消息(我不太喜欢这个选项).
他链接的幻灯片有一个我们需要的东西子弹:"将设计映射到类型/函数/类/ monad上的成语".什么是成语?:)
Don*_*art 519
我在Haskell的工程大项目以及XMonad的设计和实现中谈到了这一点.大型工程是关于管理复杂性.Haskell中用于管理复杂性的主要代码结构机制是:
类型系统
剖析器
纯度
测试
Monads用于结构化
键入类和存在类型
并发和并行
par
您的计划,通过简单,可组合的并行性来击败竞争对手.重构
明智地使用FFI
元编程
包装和分销
警告
-Wall
让您的代码更干净的气味.您还可以查看Agda,Isabelle或Catch以获得更多保证.对于类似lint的检查,请参阅伟大的hlint,它将提出改进建议.使用所有这些工具,您可以处理复杂性,尽可能多地删除组件之间的交互.理想情况下,你有一个非常大的纯代码基础,它很容易维护,因为它是组合的.这并非总是可行,但值得瞄准.
通常:将系统的逻辑单元分解为可能的最小参考透明组件,然后在模块中实现它们.组件集(或组件内部)的全局或本地环境可能会映射到monad.使用代数数据类型来描述核心数据结构.广泛分享这些定义.
use*_*653 118
Don给出了上面的大部分细节,但这是我在Haskell中执行系统守护进程等非常实用的有状态程序时的两分钱.
最后,你住在monad变换器堆栈中.最底层是IO.在此之上,每个主要模块(在抽象意义上,而不是文件中的模块意义)将其必要状态映射到该堆栈中的层.因此,如果您将数据库连接代码隐藏在模块中,则将其全部写入MonadReader类型连接m => ... - > m ...然后您的数据库函数始终可以获得其连接而无需其他函数模块必须意识到它的存在.您可能最终得到一个承载数据库连接的层,另一个配置,第三个用于解决并行和同步的各种信号量和mvars,另一个用于日志文件处理等.
首先找出你的错误处理.Haskell在大型系统中目前最大的弱点是过多的错误处理方法,包括像Maybe这样糟糕的错误处理方法(这是错误的,因为你不能返回任何关于出错的信息;总是使用Either而不是Maybe除非你真的只是意味着缺失值).弄清楚如何首先完成它,并从库和其他代码使用的各种错误处理机制中设置适配器到最后一个.这将为您节省一个悲伤的世界.
附录(摘自评论;感谢Lii和liminalisht) -
更多关于将大型程序分成堆栈中的monad的不同方法的讨论:
Ben Kolera为这个主题提供了一个很好的实用介绍,Brian Hurt讨论了将lift
monadic动作问题解决到你的自定义monad中的问题.George Wilson展示了如何使用mtl
编写适用于任何实现所需类型类的monad的代码,而不是自定义monad类.Carlo Hamalainen撰写了一些简短有用的笔记,总结了乔治的演讲.
小智 43
在Haskell中设计大型程序与在其他语言中进行设计没有什么不同.大型编程是将您的问题分解为可管理的部分,以及如何将这些部分组合在一起; 实现语言不太重要.
也就是说,在大型设计中,尝试利用类型系统以确保您只能以正确的方式将各个部分组合在一起是一件好事.这可能涉及newtype或phantom类型,以使看起来具有相同类型的东西不同.
当你进行重构代码时,纯度是一个很大的好处,所以尽量保持尽可能多的纯代码.纯代码很容易重构,因为它与程序的其他部分没有隐藏的交互.
com*_*nad 16
我本书第一次学习了结构化函数式编程.它可能不是您正在寻找的,但对于函数式编程的初学者来说,这可能是学习构建函数式程序的最佳第一步 - 与规模无关.在所有抽象级别上,设计应始终具有明确排列的结构.
功能编程工艺
http://www.cs.kent.ac.uk/people/staff/sjt/craft2e/
gra*_*nas 11
我目前正在写一本名为"功能设计与架构"的书.它为您提供了一套完整的技术,如何使用纯函数方法构建大型应用程序.它描述了许多功能模式和想法,同时构建了类似SCADA的应用程序'Andromeda',用于从头开始控制太空飞船.我的主要语言是Haskell.这本书包括:
我希望在2017年年底完成这本书在此之前,你可以阅读我的文章"函数式编程设计与建筑"(RUS)这里.
UPDATE
我在网上分享了我的书(前5章).请参阅Reddit上的帖子
Gabriel的博客文章可扩展程序架构可能值得一提.
Haskell设计模式与主流设计模式的区别在于一个重要方面:
传统架构:将A类的几个组件组合在一起,生成B类"网络"或"拓扑"
Haskell架构:将A类的几个组件组合在一起,生成相同类型A的新组件,其特征与其取代部分无法区分
通常情况下,一种看似优雅的建筑往往会从图书馆中脱颖而出,这种图书馆以自下而上的方式展现出这种良好的同质感.在Haskell中,这一点尤其明显 - 传统上被认为是"自上而下的架构"的模式往往会被捕获在像mvc,Netwire和Cloud Haskell这样的库中.也就是说,我希望这个答案不会被解释为尝试取代这个线程中的任何其他人,只是结构选择可以并且应该理想地由域专家在库中抽象出来.在我看来,构建大型系统的真正困难在于评估这些图书馆的建筑"善"与所有实际问题.
正如liminalisht在评论中提到的那样,类别设计模式是Gabriel关于该主题的另一篇文章,类似地.