在看到 List 和 Maybe monad 是如何定义的之后,我自然对 IO monad的操作>>=
和定义产生了好奇。return
3 回答
没有具体的实现IO
;它是一种抽象类型,Haskell 报告未定义确切的实现。事实上,没有什么可以阻止实现实现IO
及其Monad
实例作为编译器原语,根本没有 Haskell 实现。
基本上,Monad
用作 的接口,IO
它本身不能在纯 Haskell 中实现。这可能是您现阶段需要知道的全部内容,深入研究实施细节可能只会让人感到困惑,而不是提供洞察力。
也就是说,如果您查看 GHC 的源代码,您会发现它表示IO a
为一个看起来像的函数State# RealWorld -> (# State# RealWorld, a #)
(使用未装箱的元组作为返回类型),但这是一种误导;这是一个实现细节,这些State# RealWorld
值在运行时实际上并不存在。IO
不是一个状态单子,1在理论上或在实践中。
相反,GHC 使用不纯的原语来实现这些 IO 操作;“State# RealWorld
值”只是通过引入从一个语句到下一个语句的数据依赖关系来停止编译器重新排序语句。
但如果你真的想看看 GHC 对 and 的实现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
whereunIO
只是从IO
构造函数内部解开函数。
重要的是要注意,它IO a
表示可以运行以产生 type 值的不纯计算的描述a
。有一种方法可以从 GHC 的内部表示中获取值这一事实IO
并不意味着这通常成立,或者您可以对所有单子做这样的事情。这纯粹是 GHC 的一个实现细节。
1状态单子是用于在一系列计算中访问和改变状态的单子;它表示为s -> (a, s)
(s
状态类型在哪里),它看起来与 GHC 用于 的类型非常相似IO
,因此会造成混淆。
你会失望的,但>>=
in 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 被声明为monad 的实例,并且它在那里定义(并且它的实现很容易猜到)。State
State#
>>=
有关monad的更多详细信息,请参阅Haskell wiki 上的IO inside文章。查看Haskell 文档IO
也很有帮助,其中每个位置的右侧都有小的“源”链接。
更新:还有另一个失望,这是我的回答,因为我没有注意到State#
. 但是IO
表现得像带有抽象状态的State
monadRealWorld
正如@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
他们没有做任何特别的事情,只是为了排序动作。如果你用不同的名字来思考它们会有所帮助:
>>= 变成“然后,使用前一个动作的结果,”
>> 变成“然后”
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 语言中的分号一样神奇。