IO monad 的 >>= 和 return 的定义是什么?

发布于 2025-01-04 12:02:33 字数 109 浏览 3 评论 0原文

在看到 List 和 Maybe monad 是如何定义后,我自然好奇如何 >>=return 操作是为 IO monad 定义的。

After seeing how the List and Maybe monads are defined, I naturally became curious about how
the operations >>= and return are defined for the IO monad.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

拧巴小姐 2025-01-11 12:02:33

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 的 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 内部表示中获取值,但这并不意味着这通常成立,或者您可以对所有 monad 执行这​​样的操作。这纯粹是 GHC 方面的实现细节。

1 状态 monad 是一个用于访问和改变一系列计算的状态;它表示为 s ->; (a, s) (其中 s 是状态类型),它看起来与 GHC 用于 IO 的类型非常相似,因此会造成混淆。

There is no specific implementation for IO; it's an abstract type, with the exact implementation left undefined by the Haskell Report. Indeed, there's nothing stopping an implementation implementing IO and its Monad instance as compiler primitives, with no Haskell implementation at all.

Basically, Monad is used as an interface to IO, which cannot itself be implemented in pure Haskell. That's probably all you need to know at this stage, and diving into implementation details is likely to just confuse, rather than give insight.

That said, if you look at GHC's source code, you'll find that it represents IO a as a function looking like State# RealWorld -> (# State# RealWorld, a #) (using an unboxed tuple as the return type), but this is misleading; it's an implementation detail, and these State# RealWorld values do not actually exist at runtime. IO is not a state monad,1 in theory or in practice.

Instead, GHC uses impure primitives to implement these IO operations; the State# RealWorld "values" are only to stop the compiler reordering statements by introducing data dependencies from one statement to the next.

But if you really want to see GHC's implementation of return and (>>=), here they are:

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

where unIO simply unwraps the function from inside the IO constructor.

It's important to note that IO a represents a description of an impure computation that could be run to produce a value of type a. The fact that there's a way to get values out of GHC's internal representation of IO doesn't mean that this holds in general, or that you can do such a thing for all monads. It's purely an implementation detail on the part of GHC.

1 The state monad is a monad used for accessing and mutating a state across a series of computations; it's represented as s -> (a, s) (where s is the type of state), which looks very similar to the type GHC uses for IO, thus the confusion.

甜味拾荒者 2025-01-11 12:02:33

您会感到失望,但 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 被声明为 State State# monad 的实例,< Strike> 及其 >>= 在那里定义(并且其实现相当容易猜测)。

请参阅 Haskell wiki 上的 IO inside 文章,了解有关 <代码>IO单子。
查看 也很有帮助Haskell 文档,其中每个位置的右侧都有小的“源”链接。

更新:还有另一个令人失望的地方,这就是我的答案,因为我没有注意到 State# 中的“#”。
然而,IO 的行为类似于带有抽象 RealWorld 状态的 State monad,

正如@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

You will be disappointed, but the >>= in IO monad isn't that interesting. To quote the GHC source:

{- |
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 #))

That means IO monad is declared as the instance of State State# monad, and its >>= is defined there (and its implementation is fairly easy to guess).

See IO inside article on Haskell wiki for more details about the IO monad.
It is also helpful to look at the Haskell docs, where every position has small "Source" link on the right.

Update: And there goes another disappointment, which is my answer, because I didn't notice the '#' in State#.
However IO behaves like State monad carrying abstract RealWorld state

As @ehird wrote State# is compiler's internal and >>= for the IO monad is defined in GHC.Base module:

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
弥枳 2025-01-11 12:02:33

它们不做任何特殊的事情,只是用于排序操作。如果您用不同的名称来考虑它们,将会有所帮助:

>>= 变为“然后,使用上一个操作的结果”

>>变成“然后”,

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 语言中的分号一样神奇。

They don't do anything special, and are just there for sequencing actions. It would help if you think of them with different names:

>>= becomes "and then, using the result of the previous action,"

>> becomes "and then,"

return becomes "do nothing, but the result of doing nothing is"

This turns this function:

main :: IO ()
main = putStr "hello"
   >>  return " world"
   >>= putStrLn

becomes:

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

In the end, there's nothing magical about IO. It's exactly as magical as a semicolon is in C.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文