我想我只需要看看bind
for的定义,IO
然后一切就清楚了。
-- ghc-8.6.5/libraries/base/GHC/Base.hs; line 1381
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)
-- ghc-8.6.5/libraries/base/GHC/Base.hs; line 1387
unIO :: IO a -> (State# RealWorld -> (# State# RealWorld, a #))
unIO (IO a) = a
该IO
类型在单独的模块中声明:
-- ghc-8.6.5/libraries/ghc-prim/GHC/Types.hs; line 169
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
IO
(更多关于and操作符的例子bind
可以在Philip Wadler的How to Declare an Imperative中找到。)
如果有人可以帮助我逐步了解程序的实际评估方式并确定副作用发生的确切时刻,那么其他会很有帮助的事情。
让我们重写bindIO
:
使用let
-expressions 和bang-patterns而不是case
:
使用以下方法从其第一个参数中提取操作unIO
:
bindIO :: IO a -> (a -> IO b) -> IO b
bindIO m k = IO (\ s -> let !(# new_s, a #) = unIO m s in unIO (k a) new_s)
现在对于那个Learn You A Haskell示例的扩展版本:
main = putStr "Hey, " >>=
(\_ -> putStr "I'm " >>=
(\_ -> putStrLn "Andy!"))
替换(>>=)
为bindIO
, 沿途切换到前缀表示法:
main = bindIO (putStr "Hey, ")
(\_ -> bindIO (putStr "I'm ")
(\_ -> putStrLn "Andy!"))
现在是挑剔的部分 - 将所有调用扩展到bindIO
; 一切顺利,程序最终将类似于:
main = IO (\s0 -> let !(# s1, _ #) = unIO (putStr "Hey, ") s0 in
let !(# s2, _ #) = unIO (putStr "I'm ") s1 in
unIO (putStrLn "Andy!") s2)
另一项更改 - 它是可选的,但它有助于澄清这里发生的事情:
main = IO (\s0 -> let !(# s1, _ #) = unIO (putStr "Hey, ") s0 in
let !(# s2, _ #) = unIO (putStr "I'm ") s1 in
let !(# s3, a3 #) = unIO (putStrLn "Andy!") s2) in
(# s3, a3 #))
-
其中,据我所知,它可以解释为:为了putStrLn "Andy!"
我首先需要putStr "I'm "
,为了做到这一点,我首先需要putStr "Hey, "
。
正确:因为程序中如何使用 、 和s0
(s1
一次),从而建立了评估顺序。该排序允许并直接使用效果来打印出它们各自的参数。s2
s3
putStr
putStrLn
因此,与例如标准 ML(使用语法排序)不同,Haskell 依赖于数据依赖性来确保 I/O 以所需的顺序发生 -do
符号只是一种方便。
我遇到的问题是 lambda 在惰性评估期间忽略了他们的论点 - 这种事情不应该被识别和短路吗?
如果我们还“过度扩展”您的另一个示例:
IO (\ s0 -> let !(# s1, _ #) = unIO (putStrLn "What is your name?") s0 in
let !(# s2, name #) = unIO getLine s1 in
let !(# s3, a3 #) = unIO (putStrLn ("Nice to meet you, " ++ name ++ "!")) s2 in
(# s3, a3 #))
我们可以清楚地看到,实际上被忽略的是输出- 即() :: ()
来自使用putStr
and putStrLn
- 但不是states,它们保持顺序。
类型的 IO 操作如何IO ()
携带足够的信息以允许运行时系统打印"Hey, I'm Andy!"
- 这IO ()
与IO ()
打印"Hello World!"
或写入文件有什么不同?
相同的方式(+)
,(-)
并且(*)
被区别对待,即使它们具有相同的类型 ( Num a => a -> a -> a
) - 通过具有不同的名称。