为什么Haskell中没有IO转换器?

59

每个单子都有一个变换器版本,据我所知,变换器的概念是单子的一种通用扩展。根据其他变换器的构建方式,IOT应该是这样的:

newtype IOT m a = IOT { runIOT :: m (IO a) }

我可以即时开发有用的应用:IOT Maybe 可以执行 IO 操作或什么都不做,IOT [] 可以构建一个列表,稍后可以对其进行 sequence

那么为什么Haskell中没有IO transformer呢?

(注:我看过这篇Haskell Cafe帖子,但无法理解太多。此外,ST变换器的Hackage页面在其描述中提到了可能相关的问题,但没有提供任何详细信息。)


5
出于同样的原因,没有 runIO 函数(当然不包括 unsafePerformIO)... - stephen tetley
8
  1. 那并没有解释任何事情。
  2. 在单子接口中没有 m a -> a 这个函数,所以我不明白它是如何相关的。 (bind 的内部实现可以是不安全的,只要接口是纯的即可。)
- David
1
你的意思是说,如果没有(合理地)每次使用bind时进行内部解包,就不可能有IOT,这会导致不可预测的行为吗?(如果是的话,也许可以将其作为完整答案) - David
6
runIOT (launchMissiles >> lift []) 应该求值为什么? - is7s
5
如果我可以多次点赞这个问题,我会这样做。考虑如何给IO提供替代语义,如何仅基于数据类型解释它。是什么让人们认为IO代码具有“副作用”?只有一种特定的运行时解释。 - pigworker
显示剩余6条评论
1个回答

39

考虑一个具体的示例: IOT Maybe。你如何为它编写 Monad 实例?你可以从以下内容开始:

instance Monad (IOT Maybe) where
    return x = IOT (Just (return x))
    IOT Nothing >>= _ = IOT Nothing
    IOT (Just m) >>= k = IOT $ error "what now?"
      where m' = liftM (runIOT . k) m
现在您有一个类型为m' :: IO (Maybe (IO b))的值,但您需要一个Maybe (IO b)类型的值,最重要的是,JustNothing之间的选择应该由m'确定。如何实现?
当然,答案是不可能实现,因为它无法实现。您也不能通过纯接口隐藏背后的unsafePerformIO来进行自圆其说,因为从根本上讲,您正在请求一个纯值——Maybe构造函数的选择——取决于IO中的某些结果。不可能发生。
在一般情况下,情况甚至更糟,因为任意(普遍量化的)MonadIO更难解开。
顺便说一句,您提到的ST变换器与您建议的IOT的实现方式不同。它使用编译器提供的神奇精灵尘特殊原语将内部实现的ST作为类似于State的单子,并基于此定义了一个类似于StateT的变换器。IO在内部实现为更神奇的ST,因此类似地可以定义一个假设的IOT
这实际上并没有改变什么,只是可能让您更好地控制由IOT引起的不纯边效果的相对顺序。

1
好的回答!关于最后一句话:如果我们去掉奇怪的RealWorld,那么这是否意味着transformer并不具备普适性呢?所有其他常用单子都有(transformer)是不是一种美好的巧合呢? - David
25
@David: 看你怎么看待这个问题。如果IO真正是一个状态单子,其状态值是整个外部宇宙,那么如果定义一个IOT,按照这样的定义,它将能够正确地工作,其中,“正确”意味着在IOT Maybe中的Nothing将放弃宇宙并因此结束所有存在。个人而言,我会坚持当前的情况... - C. A. McCann
3
这是将“作为IO计算”的实际含义与如何“运行”其中一种特定实现混淆的错误。 - pigworker
2
IOT (Just m) >>= k = IOT $ Just $ m >>= maybe (fail "...") id . runIOT . k 这段代码怎么样?它不好是因为 (>>=) 可能会 fail(但实际上不应该)。 - JJJ
3
问题的实际根源在于,我们无法从一个 Nothing :: Maybe a 中创造出一个 a,使我们能够将其 return 然后 join,对吗?显然,这对于 Identity 就可以起作用,但是是否存在其他可能适用的单子? - Sebastian Graf

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