Dav*_*obs 4 io closures haskell do-notation ioref
我正在努力了解如何IORefs真正使用,并且我无法按照我在https://www.seas.upenn.edu/~cis194/spring15/lectures/12-unsafe.html上找到的示例代码进行操作
newCounter :: IO (IO Int)
newCounter = do
r <- newIORef 0
return $ do
v <- readIORef r
writeIORef r (v + 1)
return v
printCounts :: IO ()
printCounts = do
c <- newCounter
print =<< c
print =<< c
print =<< c
Run Code Online (Sandbox Code Playgroud)
当printCounts执行" c <- newCounter"时,为什么不能c在newCounter" return $ do"块中得到完成工作的结果,这似乎应该IO 0在第一次被调用时被分配给常量" "然后永远不会改变?相反,c似乎被赋予了在" return $ do"块中定义的函数,然后每次printCounts到达另一个" print =<< c." 时重新执行.看来,答案在某种程度上就在于newCounter具有双嵌套" IO (IO Int)"类型,但我不能跟着为什么让c一个函数调用,而不是只计算一次恒定时要重新执行.
您可以将其IO视为一种程序.newCounter :: IO (IO Int)是一个输出程序的程序.更确切地说,newCounter分配一个新计数器,并返回一个程序,该程序在运行时递增计数器并返回其旧值.newCounter不执行它返回的程序.如果你写的话会:
newCounter :: IO (IO Int)
newCounter = do
r <- newIORef 0
let p = do -- name the counter program p
v <- readIORef r
writeIORef r (v + 1)
return v
p -- run the counter program once
return p -- you can still return it to run again later
Run Code Online (Sandbox Code Playgroud)
您还可以使用等式推理展开printCounts为一系列基元.以下所有版本printCounts都是等效的程序:
-- original definition
printCounts :: IO ()
printCounts = do
c <- newCounter
print =<< c
print =<< c
print =<< c
-- by definition of newCounter...
printCounts = do
c <- do
r <- newIORef 0
return $ do
v <- readIORef r
writeIORef r (v + 1)
return v
print =<< c
print =<< c
print =<< c
-- by the monad laws (quite hand-wavy for brevity)
-- do
-- c <- do
-- X
-- Y
-- .....
-- =
-- do
-- X
-- c <-
-- Y
-- .....
--
-- (more formally,
-- ((m >>= \x -> k x) >>= h) = (m >>= (\x -> k x >>= h)))
printCounts = do
r <- newIORef 0
c <-
return $ do
v <- readIORef r
writeIORef r (v + 1)
return v
print =<< c
print =<< c
print =<< c
-- c <- return X
-- =
-- let c = X
--
-- (more formally, ((return X) >>= (\c -> k c)) = (k X)
printCounts = do
r <- newIORef 0
let c = do
v <- readIORef r
writeIORef r (v + 1)
return v
print =<< c
print =<< c
print =<< c
-- let-substitution
printCounts = do
r <- newIORef 0
print =<< do
v <- readIORef r
writeIORef r (v + 1)
return v
print =<< do
v <- readIORef r
writeIORef r (v + 1)
return v
print =<< do
v <- readIORef r
writeIORef r (v + 1)
return v
-- after many more applications of monad laws and a bit of renaming to avoid shadowing
-- (in particular, one important step is ((return v >>= print) = (print v)))
printCounts = do
r <- newIORef 0
v1 <- readIORef r
writeIORef r (v1 + 1)
print v1
v2 <- readIORef r
writeIORef r (v2 + 1)
print v2
v3 <- readIORef r
writeIORef r (v3 + 1)
print v3
Run Code Online (Sandbox Code Playgroud)
在最终版本中,您可以看到printCounts完全分配计数器并将其递增三次,打印每个中间值.
一个关键步骤是let-substitution one,其中计数器程序被复制,这就是它运行三次的原因.let x = p; ...不同于x <- p; ...,运行p和绑定x结果而不是程序p本身.