在IO的基础上构建monad transformer堆栈是否有有效的理由?

5

IO 在 Haskell 中很棘手。线程、FFI、异步异常、惰性等方面都需要考虑。

然后,我们有了 MonadIO,它允许在底层使用 IO 来建立单调堆栈。由于 IO 动作可以执行任意操作,因此在这样不稳定的基础上构建单调堆栈的价值何在?

为什么要存在它?如果确实需要执行任意副作用,为什么不构建自定义单调堆栈 IO 呢?


4
“但这就是我们所做的事情……?” MonadIO 只是一个类型类,它将所有这样的“自定义 monad”分组在一起。恰好许多有用的 monad 可以表示为 monad transformer 堆栈。您确定您在此处的问题中表述得清楚吗? - AJF
1
我真的不明白为什么它被标记为一个糟糕的问题。 - sevo
但是都在MonadIO中”这句话的意思是什么?你是说我们无法知道IO操作中发生了什么吗?使用单子变换器可以帮助我们理解,而不是阻碍。我仍然不清楚你的问题是什么。我建议提供一个具体的例子。 - AJF
3
@AJFarmar似乎在询问为什么我们不在所有地方都使用一种细粒度的效果系统。参见像Delimiting the IO monad和 https://chrispenner.ca/posts/monadio-considered-harmful这样的建议。 - duplode
1
相关链接:https://github.com/fpco/unliftio/tree/master/unliftio#readme - sevo
显示剩余2条评论
3个回答

1

MonadIO 可以在实现一个 MonadIO 实例类型的函数时非常有用。

newtype FooMonad a
  = FooMonad (StateT Int IO a)
  deriving (Functor, Applicative, Monad)

doFoo :: FooMonad String
doFoo = FooMonad $ liftIO getLine

你可以使用 StateT IntIOMonadIO 实例方便地定义“原始”FooMonad 操作。使用你的模块的其他人只能使用你选择导出的基本操作。

1

现在使用ReaderT设计模式是一种常见的方法。

你对在IO之上放置其他转换器持谨慎态度是正确的,上述链接的博客文章解释了其中的一些原因。

然而,当涉及到向所有函数传递“公共应用程序配置”时,ReaderT提供了一个不错的小便利。


1
在这种不稳定的基础上构建单子堆栈的价值是什么?
IO之上的转换器可能让您更方便地谈论重复操作序列(因此有各种流式处理)。
它们还可能有助于管理外部分配的资源所需的簿记工作。 IO的"野性"并非对在其之上叠加变换器的一般反对意见,因为它们可以帮助您避免重复代码并使关键逻辑更清晰。
相反,论点IO 已经提供了一些处理错误 (异常) 和可变引用 (IORef, MVars...) 的内置功能,因此为已经存在的功能添加转换器可能过于复杂。

特别针对可变引用的一个论点是,通过“纯”的方式维护的状态在异常发生时会消失,而这可能并不是你想要的。您还可以从多个线程访问可变引用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接