为什么 IO 不是 State 的实例化?

Why isn't IO an instantiation of State?

提问人:Dannyu NDos 提问时间:2/28/2023 最后编辑:Dannyu NDos 更新时间:2/28/2023 访问量:116

问:

为什么当我们有 时,没有(严格)单子的实例化,如 中所述?我以为它应该是一种代表现实本身的神奇类型。IOStateRealWorldControl.Monad.STRealWorld

我的意思是,回想一下 monad 的“运行”函数:State

runState :: (s -> (a, s)) -> s -> a

将其实例化为 ,我们得到:RealWorld

runIO :: (RealWorld -> (a, RealWorld)) -> RealWorld -> a

由于我们无法构造 值,因此这不应该充当 这样的后门。RealWorldunsafePerformIO

原因是因为这种解释将启用单子变压器,定义为?IOTStateT RealWorld

Haskell IO 状态 子单 子变压器

评论

4赞 leftaroundabout 2/28/2023
人们不应该以任何方式明确地处理。这是一个实现细节。RealWorld
0赞 Daniel Wagner 2/28/2023
还记得我们提供.那么,应该怎么做呢?从表面上看,它可能会让您再次使用 bind 重新运行,这似乎很难实现。state :: ((s -> (a, s)) -> State s astate (\realWorld -> (runState getLine realWorld, realWorld))getLinerealWorld
0赞 chi 2/28/2023
至少,这需要一些线性类型来防止值重复。例如,这样做可以允许一个人在执行副作用后回到旧世界状态,这是不可能实现的。RealWorld

答:

6赞 Li-yao Xia 2/28/2023 #1
  1. 暴露 in 仍然会给你带来和 一样糟糕的结果。RealWorldIOgetUnsafePerformIO :: IO (IO a -> a)unsafePerformIO

  2. IO使用 unlifted 类型 (, ) 来避免不必要的分配,因此无论如何它都不完全匹配。RealWorld#(#,#)State

  3. 它确实匹配,但是每当您查看 Core 或需要进行一些低级 hack 时,直接定义 的 Noise 会最小化(这比也涉及的用途更常见)。STIOSTIO

  4. 我以为它应该是一种代表现实本身的神奇类型。RealWorld

    RealWorld是一个可爱的名字,但主要是混淆的根源。它真的不代表任何事情。最好忘记任何含义,只从操作语义的角度来考虑这个定义。RealWorldIO

4赞 Ben 2/28/2023 #2

其目的是在Haskell值的纯宇宙中仔细定义一个模型,以便您可以使用该模型执行的所有操作都与真实宇宙的实际行为相匹配。然后,我们可以将模型中的特定 API 与实际的真实观察结果和效果联系起来。IO

如果只是这样,它就不会做这项工作。即使我们不能构造一个 ,我们仍然可以使用这个接口做一些对真实宇宙的行为没有任何意义的事情。IOtype IO a = State RealWorldRealWorld

main = do
  backup <- get
  fireMissiles
  tears <- checkForRegrets
  if tears
    then put backup
    else pure ()

因此,Haskell 的纯 IO 模型的一个关键部分是类型是抽象的。它没有直接可观察的属性,除了它实现了类和提供的原始操作。IOMonad

你当然可以在脑海中建模为“它就像一个状态单子,其中贯穿的状态代表整个宇宙”。根据这种理解,组合行动就是安排“宇宙”以正确的顺序贯穿其中。IOIO

许多哈斯克勒主义者(包括我自己)更喜欢这样一种心智模式,即 an 是对不纯洁行为的纯粹描述;类似“声称运行它的程序将产生一个 ”。根据这种理解,组合操作是将后面的程序与前面的程序产生的结果联系起来,作为延续运行。IO aaIO

但这些模型都不是引擎盖下的“真正工作”方式。 就像这两件事一样,但主要是因为一个未知的抽象单子的 API 就像这两件事一样,而“它是一个单子”实际上是公共 API 唯一具有的声明性信息。你可以使用任何一种对你最有帮助的思考方式(我有一个宠物理论,即来自“传统”命令式编程的人发现状态单子类比更容易,而对延续传递风格感到满意的人发现“动作的描述”更有吸引力)。但不管你怎么想,类型检查器和模块系统都会强制执行重要的实际属性(除非你不遗余力地导入和使用不安全的东西)。IOIOIO

实施过程中,有一些看起来像是世界传递的东西正在发生;这是使用您注意到的类型的地方。它本质上是一种实现技术,有助于确保编译器不会意外地决定重新排序 IO 操作;它的工作原理是使所需的操作顺序(对编译器而言)显示为具有需要该顺序的数据依赖关系。但是,如果您能够访问该级别,则仅限于编写实际对应于 IO 操作在现实世界中的行为方式的合理代码,因此这种世界传递不是 的公共 API 的一部分。事实上,它作为一个实现细节存在,并不意味着你必须甚至应该把它看作是一个状态单子,就像你需要把一个或一个看作是一棵树一样。IORealWorldIOIOMapSet

其他一些语言(据我所知,Clean 和 Mercury 语言)确实更明确地使用了世界传递,并且实际上提供了对世界代币的直接访问。要做到这一点,并保持你在纯IO模型中可以做的任何事情实际上对应于我们单一真实宇宙中可以做的事情的属性,需要一种不同的限制。Clean 和 Mercury 使用唯一性类型;从本质上讲,你得到的是一个类型级标记,确保它必须使用一次(请注意,在 Clean 和 Mercury 中,世界令牌本身仍然是完全抽象的;世界令牌的存在是 API 的一部分,但你仍然无法查看它们内部或对它们做任何事情,除非将它们传递给下一个 IO 操作)。这防止了我之前写的那种“备份宇宙”废话。如果 Haskell 真的想用世界传递来模拟 IO,它可以,但它也需要使用类似唯一性类型的东西,所以它仍然不能使用普通的单子。WorldState