使用 bang 模式时,IO monad 会变得严格吗?
Does IO monad become strict when bang pattern is used?
我期待以下代码片段:
main = do
let !x = [2,3,5,2,3,5,6,7,1,3,0,1]
begin <- getCPUTime
let !rx = reverse x
end <- getCPUTime
putStrLn $ "Calculation time: " ++ show (end - begin) ++ " ps."
putStrLn $ "Result: " ++ show rx
与以下版本相同:
main = do
let x = [2,3,5,2,3,5,6,7,1,3,0,1]
begin <- x `seq` getCPUTime
let rx = reverse x
end <- rx `seq` getCPUTime
putStrLn $ "Calculation time: " ++ show (end - begin) ++ " ps."
putStrLn $ "Result: " ++ show rx
这是真的吗?如果 x
和 rx
在第一个版本中当 "needed" 计算为 WHNF 时,这是错误的。
顺便说一句,我想提出一个用于深度评估的语法糖,命名为"double bang pattern"。
您提供的特定代码示例生成相同的编译代码。如果你参加以下课程:
{-# LANGUAGE BangPatterns #-}
module Bang where
import System.CPUTime
main1 = do
let !x = [2,3,5,2,3,5,6,7,1,3,0,1]
begin <- getCPUTime
let !rx = reverse x
end <- getCPUTime
putStrLn $ "Calculation time: " ++ show (end - begin) ++ " ps."
putStrLn $ "Result: " ++ show rx
main2 = do
let x = [2,3,5,2,3,5,6,7,1,3,0,1]
begin <- x `seq` getCPUTime
let rx = reverse x
end <- rx `seq` getCPUTime
putStrLn $ "Calculation time: " ++ show (end - begin) ++ " ps."
putStrLn $ "Result: " ++ show rx
并编译它(使用 GHC 版本 8.6.5):
stack ghc -- -dsuppress-all -dsuppress-uniques -ddump-simpl -fforce-recomp -O2 Bang.hs
你会在转储的GHC核心中发现main1
和main2
被编译成完全相同的代码,实际上被拉出到一个单独的main4
函数中:
main1
main1 = main4 `cast` <Co:3>
main2
main2 = main4 `cast` <Co:3>
但是,一般来说,let !x = ...
构造并不完全等同于使用 let x = ...
后跟 x `seq` y
。例如下面两个IO动作是不同的:
foo :: IO ()
foo = do
let !x = undefined
return ()
bar :: IO ()
bar = do
let x = undefined
return $ x `seq` ()
第一个立即产生异常:
main = do
print 1
foo -- EXCEPTION!
print 2
第二个在执行时什么都不做,但如果您尝试仔细检查它的结果,则会生成异常:
main = do
print 1
bar -- does nothing
print 2
x <- bar -- also does nothing
print 3
() <- bar -- EXCEPTION!
print 4
我相信在脱糖之后,bar
和foo
等同于:
bar = return (undefined `seq` ())
foo = undefined `seq` return ()
这解释了他们不同的行为。
我期待以下代码片段:
main = do
let !x = [2,3,5,2,3,5,6,7,1,3,0,1]
begin <- getCPUTime
let !rx = reverse x
end <- getCPUTime
putStrLn $ "Calculation time: " ++ show (end - begin) ++ " ps."
putStrLn $ "Result: " ++ show rx
与以下版本相同:
main = do
let x = [2,3,5,2,3,5,6,7,1,3,0,1]
begin <- x `seq` getCPUTime
let rx = reverse x
end <- rx `seq` getCPUTime
putStrLn $ "Calculation time: " ++ show (end - begin) ++ " ps."
putStrLn $ "Result: " ++ show rx
这是真的吗?如果 x
和 rx
在第一个版本中当 "needed" 计算为 WHNF 时,这是错误的。
顺便说一句,我想提出一个用于深度评估的语法糖,命名为"double bang pattern"。
您提供的特定代码示例生成相同的编译代码。如果你参加以下课程:
{-# LANGUAGE BangPatterns #-}
module Bang where
import System.CPUTime
main1 = do
let !x = [2,3,5,2,3,5,6,7,1,3,0,1]
begin <- getCPUTime
let !rx = reverse x
end <- getCPUTime
putStrLn $ "Calculation time: " ++ show (end - begin) ++ " ps."
putStrLn $ "Result: " ++ show rx
main2 = do
let x = [2,3,5,2,3,5,6,7,1,3,0,1]
begin <- x `seq` getCPUTime
let rx = reverse x
end <- rx `seq` getCPUTime
putStrLn $ "Calculation time: " ++ show (end - begin) ++ " ps."
putStrLn $ "Result: " ++ show rx
并编译它(使用 GHC 版本 8.6.5):
stack ghc -- -dsuppress-all -dsuppress-uniques -ddump-simpl -fforce-recomp -O2 Bang.hs
你会在转储的GHC核心中发现main1
和main2
被编译成完全相同的代码,实际上被拉出到一个单独的main4
函数中:
main1
main1 = main4 `cast` <Co:3>
main2
main2 = main4 `cast` <Co:3>
但是,一般来说,let !x = ...
构造并不完全等同于使用 let x = ...
后跟 x `seq` y
。例如下面两个IO动作是不同的:
foo :: IO ()
foo = do
let !x = undefined
return ()
bar :: IO ()
bar = do
let x = undefined
return $ x `seq` ()
第一个立即产生异常:
main = do
print 1
foo -- EXCEPTION!
print 2
第二个在执行时什么都不做,但如果您尝试仔细检查它的结果,则会生成异常:
main = do
print 1
bar -- does nothing
print 2
x <- bar -- also does nothing
print 3
() <- bar -- EXCEPTION!
print 4
我相信在脱糖之后,bar
和foo
等同于:
bar = return (undefined `seq` ())
foo = undefined `seq` return ()
这解释了他们不同的行为。