【发布时间】:2012-03-03 21:52:32
【问题描述】:
看到 List 和 Maybe monad 是怎么定义的,我自然好奇
为 IO monad 定义了操作 >>= 和 return。
【问题讨论】:
看到 List 和 Maybe monad 是怎么定义的,我自然好奇
为 IO monad 定义了操作 >>= 和 return。
【问题讨论】:
你会失望的,但是IO monad 中的>>= 并没有那么有趣。引用 GHC 来源:
{- |
A value of type @'IO' a@ is a computation which, when performed,
does some I\/O before returning a value of type @a@.
There is really only one way to \"perform\" an I\/O action: bind it to
@Main.main@ in your program. When your program is run, the I\/O will
be performed. It isn't possible to perform I\/O from an arbitrary
function, unless that function is itself in the 'IO' monad and called
at some point, directly or indirectly, from @Main.main@.
'IO' is a monad, so 'IO' actions can be combined using either the do-notation
or the '>>' and '>>=' operations from the 'Monad' class.
-}
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
这意味着IO monad 被声明为 StateState# monad 的实例, 并且它的.>>= 在那里定义(并且它的实现很容易猜到) )
有关 IO monad 的更多详细信息,请参阅 Haskell wiki 上的 IO inside 文章。
查看Haskell docs 也很有帮助,其中每个位置的右侧都有小的“来源”链接。
更新:还有另一个令人失望的地方,这是我的回答,因为我没有注意到 State# 中的“#”。
但是 IO 的行为类似于 State monad 携带抽象 RealWorld 状态
正如@ehird 所写,State# 是编译器的内部代码,>>= 的 IO monad 在 GHC.Base 模块中定义:
instance Monad IO where
{-# INLINE return #-}
{-# INLINE (>>) #-}
{-# INLINE (>>=) #-}
m >> k = m >>= \ _ -> k
return = returnIO
(>>=) = bindIO
fail s = failIO s
returnIO :: a -> IO a
returnIO x = IO $ \ s -> (# s, x #)
bindIO :: IO a -> (a -> IO b) -> IO b
bindIO (IO m) k = IO $ \ s -> case m s of (# new_s, a #) -> unIO (k a) new_s
【讨论】:
State# 而不是State,正如其他地方所讨论的,IO monad 中发生的真正魔力并没有被它对状态令牌的操作所捕获。
GHC.IOBase 已在 this commit 中删除。提到的定义现在似乎在GHC.Base。
他们没有做任何特别的事情,只是为了排序操作。如果你用不同的名字来思考它们会有所帮助:
>>= 变为“然后,使用上一个操作的结果,”
>> 变成“然后”
return 变成“什么都不做,但是什么都不做的结果是”
这会变成这个函数:
main :: IO ()
main = putStr "hello"
>> return " world"
>>= putStrLn
变成:
main :: IO ()
main = putStr "hello" and then,
do nothing, but the result of doing nothing is " world"
and then, using the result of the previous action, putStrLn
说到底,IO 并没有什么神奇之处。它就像 C 语言中的分号一样神奇。
【讨论】:
(>>=) 和(>>) 运算符和函数return 没有这种特殊状态——它们是在库中定义的!如果你不知道发生了什么,那么至少乍一看,你可以将语言的不纯片段定义为一个库,否则它肯定看起来有点神秘。
IO没有具体实现;它是一种抽象类型,Haskell 报告未定义确切的实现。实际上,没有什么可以阻止将 IO 及其 Monad 实例作为编译器原语实现的实现,而根本没有 Haskell 实现。
基本上,Monad 用作IO 的接口,它本身不能在纯 Haskell 中实现。这可能是您现阶段需要了解的全部内容,深入研究实施细节可能只会让人感到困惑,而不是提供洞察力。
也就是说,如果您查看 GHC 的源代码,您会发现它将 IO a 表示为一个看起来像 State# RealWorld -> (# State# RealWorld, a #) 的函数(使用 unboxed tuple 作为返回类型),但这是一种误导;这是一个实现细节,这些State# RealWorld 值在运行时实际上并不存在。 IO 不是状态单子,1在理论上或在实践中。
相反,GHC 使用 impure 原语来实现这些 IO 操作; State# RealWorld“值”只是通过引入从一个语句到下一个语句的数据依赖关系来停止编译器重新排序语句。
但如果你真的想看看 GHC 对 return 和 (>>=) 的实现,这里是:
returnIO :: a -> IO a
returnIO x = IO $ \ s -> (# s, x #)
bindIO :: IO a -> (a -> IO b) -> IO b
bindIO (IO m) k = IO $ \ s -> case m s of (# new_s, a #) -> unIO (k a) new_s
其中unIO 只是从IO 构造函数内部展开函数。
请务必注意,IO a 表示不纯计算的描述,可以运行该计算以生成 a 类型的值。有一种方法可以从 GHC 的 IO 的内部表示中获取值这一事实并不意味着这通常适用,或者您可以对所有单子做这样的事情。这纯粹是 GHC 的一个实现细节。
1state monad 是一个单子,用于在一系列计算中访问和改变状态;它表示为s -> (a, s)(其中s 是状态类型),看起来与GHC 用于IO 的类型非常相似,因此会造成混淆。
【讨论】:
RealWorld)是完全抽象的。这一事实不再使它失去作为真正的状态 Monad 的资格,然后是 ST Monad 中 s-thread 的抽象性质。
IO 的状态 monad 模型可以对顺序计算进行建模(尽管 IO 在实践中并未以这种方式实现),但它不能合理地表示并发性。无论如何,考虑到IO 这样的模型比状态单子更多受到限制(或者你可以做一些事情,比如把世界的状态在执行一些副作用后返回,将它们反转)。
io 类型的值(除了最终从main 接收它)。这些点与 Haskell 中无法检查 IO 操作的一元上下文或实际执行 IO 操作(除了隐式执行 main IO 操作)完全对应。但我认为我们大部分时间都在同一页上。我只是不同意将 Haskell 的 IO monad 解释为在“世界”上运行的状态 monad 是无效的。