【问题标题】:What are the definitions for >>= and return for the IO monad?IO monad 的 >>= 和 return 的定义是什么?
【发布时间】:2012-03-03 21:52:32
【问题描述】:

看到 List 和 Maybe monad 是怎么定义的,我自然好奇 为 IO monad 定义了操作 >>=return

【问题讨论】:

    标签: haskell monads


    【解决方案1】:

    你会失望的,但是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 的实例, 并且它的>>= 在那里定义(并且它的实现很容易猜到) ).

    有关 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
    【解决方案2】:

    他们没有做任何特别的事情,只是为了排序操作。如果你用不同的名字来思考它们会有所帮助:

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

    >> 变成“然后”

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

    【讨论】:

    • 抱歉,我投了反对票。当某件事看起来很神奇时,仅仅说它不神奇是不够的——你必须拉开窗帘,展示魔术是如何起作用的。
    • IO 没有什么神奇之处。它就像 C 中的分号一样神奇。
    • 但这根本不是真的。分号是 C 基本语法的一个特例。(>>=)(>>) 运算符和函数return 没有这种特殊状态——它们是在库中定义的!如果你不知道发生了什么,那么至少乍一看,你可以将语言的不纯片段定义为一个库,否则它肯定看起来有点神秘。
    • IO 比 C 中的分号更神奇。如果它像分号,您将无法在列表中包含 IO 操作而不执行它们。
    • @leftroundabout 但是在从语句切换到函数时,您添加了使 IO 比分号更神奇的魔法。我并不是说 Haskell 比 C 更神奇,只是 IO 比分号更重要。 (尽管 Haskell 比 C 有一点魔力;你不能为你的函数指针表示定义 (>>=)。好吧,至少不能使用超过一定次数的函数。)
    【解决方案3】:

    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 的类型非常相似,因此会造成混淆。

    【讨论】:

    • +1 很好地解释了为什么使用 State# 来保持语句顺序!
    • 我不同意 IO 不是 State Monad。只是它携带的状态(通常称为RealWorld)是完全抽象的。这一事实不再使它失去作为真正的状态 Monad 的资格,然后是 ST Monad 中 s-thread 的抽象性质。
    • @JohnF.Miller:IO 的状态 monad 模型可以对顺序计算进行建模(尽管 IO 在实践中并未以这种方式实现),但它不能合理地表示并发性。无论如何,考虑到IO 这样的模型比状态单子更多受到限制(或者你可以做一些事情,比如把世界的状态在执行一些副作用后返回,将它们反转)。
    • Mercury 中明确使用了 IO 的世界模型,即使涉及并发,它也能正常工作。您只需将 IO 动作视为在动作发生之前将世界映射到该动作发生以及其他效果的世界。但这在顺序计算中无论如何都是必要的,因为无论如何,由于用户、其他程序、网络、硬件故障等原因,其他并发影响一直在发生。
    • 不完全。它的工作原理是线性类型没有办法创建io 类型的值(除了最终从main 接收它)。这些点与 Haskell 中无法检查 IO 操作的一元上下文或实际执行 IO 操作(除了隐式执行 main IO 操作)完全对应。但我认为我们大部分时间都在同一页上。我只是不同意将 Haskell 的 IO monad 解释为在“世界”上运行的状态 monad 是无效的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-09-29
    • 1970-01-01
    • 2017-04-27
    • 1970-01-01
    • 1970-01-01
    • 2011-07-01
    • 1970-01-01
    相关资源
    最近更新 更多