Haskell可以对不同类型的IO进行区分

免责声明:我对Haskell的无知几乎是完美的。 对不起,如果这是非常基本的,但我找不到答案,或者甚至是这样的问题。 我的英语也不太好。

据我所知,如果我在系统中有某种功能与文件系统交互,这个函数必须使用IO monad,并且会有一个类似IO ()

在我的(仅面向业务)体验中,系统通常与文件系统进行交互,以便用业务数据读取/写入文件,并进行日志记录。

而在商业应用中,日志无处不在。 所以如果我在Haskell中编写一个系统(我不会很久),几乎每个函数都会使用IO monad。

这是常见的做法或以某种方式记录不需要IO()? 或者,也许Haskell商业应用程序不会记录那么多?

另外,其他类型的I / O如何? 如果我需要从函数访问数据库或Web服务,该函数还使用IO monad,或Haskell也有WS和DB单元? 我几乎可以肯定,只有一个IO monad能够知道IO的类型,从我的角度来看,这种类型看起来很神奇,但我确信我的观点不是客观的衡量标准用处...


是。

一种方法是通过自定义的限制IO Monad 。 您创建一个newtype为(转化)包装IO像这样:

newtype MyIO a = My { _runMy :: IO a }

runMy :: MyIO a -> IO a
runMy = _runMy

但是,您不公开/导出My数据构造函数。 相反,你公开你想要的操作的“包装”版本。 你也不公开一个MonadIO实例(例如); 允许不受限制的提升。 您可能会或可能不会公开其他实例来匹配IO 。 基本上,外部用户必须将MyIO视为与IO中内置的不透明一样,只对MyIO有限(即受限制)的转换。 你会公开一个Monad实例,也许是GeneralizedNewtypeDeriving生成的实例。

你会暴露runMy函数,这将允许在通用IO操作中嵌入任意的MyIO操作,比如在main中的“top-level”。 你不会直接暴露_runMy字段,该字段(与return )实际上会提供一个“后门” _runMy

backdoor :: IO a -> MyIO a
backdoor io = (return () :: MyIO ()) { _runMy = io }
-- polymorphic record update syntax for the win!

也就是说,我的大部分纯粹的总功能都不需要进行日志记录,所以我只是记录了我已经有权访问IO的地方。


组织Haskell程序的典型方法是构建一个特定于应用程序的monad,用于管理应用程序域所需的效果。 这可以通过将我们称之为“monad变压器堆栈”的所需功能分层来完成。 如果IO在应用程序中普遍存在,那么可以将IO具体指定为堆栈的基础(这是唯一适合的地方,因为它是唯一不能被用户级代码解构的monad),但基本monad通常可以保持抽象,这意味着您可以使用非IO monad进行实例化以进行测试。

更具体地说,monad变压器堆栈通常用一组称为读写器,写入器和状态的标准变压器来构建。 每个提供了一个不同的“效果”,它隐式地通过编写在该monad中的代码进行线程化。 为了您的日志记录目的,经常使用Writer monad(以其变换形式WriterT); 它基本上是一个Monoid,你提供的基于对其tell方法的调用来构建一些输出。 如果您基于tell实现了一个日志函数,那么应用程序monad中的任何函数都可以将日志消息mappend到日志输出。 Reader功能通常用于通过ask方法提供一组固定的环境参数; 国家相当明显; 它通过应用程序对一些可转换的数据类型进行线程化,并允许应用程序通过getput方法对其进行转换。 其他单体变压器也由图书馆提供; E可以为您的应用程序提供异常类功能,ListT可以通过List monad提供非确定性等。

对于这样的变换器堆栈,您通常希望将其限制为程序的“应用程序逻辑”层,以便您不需要任何函数在您的应用程序monad中不需要该效果。 普通模块化编程实践适用; 保持抽象的松散耦合和高度一致性,并通过普通的纯函数提供它们的功能,以便应用程序逻辑可以在高级抽象上对其进行操作。 例如,如果您在业务逻辑中具有Person的概念,则可以在Person模块中实现关于Person的数据和函数,该模块不了解任何关于您的应用程序monad的信息。 只是使其可能失败的函数返回一个具有足够信息的Either值来创建日志条目; 当您的应用程序逻辑使用这些函数处理Person值时,如果您需要组合几个可能失败的函数,它可以对结果进行模式匹配或在即时EitherT monad中工作。 然后,您可以在应用程序层使用基于Writer的日志记录功能。

每个Haskell程序的最高级别始终位于IO monad中。 如果您已经将堆栈抽象为基础Monad,或者完全将其设为纯粹的,那么您需要一个小的顶层来提供应用程序所需的IO功能。 如果它是交互式的,您可以一次运行应用程序monad,在这种情况下,您可以解压Writer以获取日志条目以及有关应用程序逻辑请求的其他IO操作的信息。 结果可以通过Reader或State层反馈到应用程序环境中。 如果您的应用程序只是一个批处理器,您可能只需通过IO操作的结果提供必要的输入,运行应用程序monad,然后通过IO从Writer中转储日志。

所有这一切的目的在于说明monads和monad变换器允许您以一种非常干净的方式分离真实应用程序的各个部分,以便您可以在大多数地方充分利用纯粹的简单函数进行数据转换,并让您的代码非常干净和可测试。 您可以在一个小的“运行时支持”层中封装IO操作,在类似但更大的层中建立专用逻辑(围绕(可能是纯粹的)monad变换器堆栈构建),以及在一组不依赖使用它们的应用程序逻辑的任何功能。 这使您可以稍后将这些模块轻松地重新用于类似域中的应用程序。

在Haskell中执行程序结构需要练习(就像它在任何语言中那样),但我想你会发现在阅读了几个Haskell应用程序并编写了一些自己的应用程序之后,它提供的功能允许您构建非常结构良好的应用程序非常容易扩展和重构。 祝你好运!

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

上一篇: Can Haskell make distinctions for different kinds of IO

下一篇: Top level mutable variables in haskell