Haskell IORef - 答案与获得答案的函数
Haskell IORef - an answer vs. a function to get an answer
我正在尝试了解 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
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
分配一个新的计数器,returns 一个程序,当 运行 时,增加计数器并 returns 它的旧值。 newCounter
不执行它 returns 的程序。如果您改写:
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
您还可以使用等式推理将 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
在最终版本中,您可以看到 printCounts
确实分配了一个计数器并将其递增 3 次,打印每个中间值。
一个关键步骤是 let-substitution 步骤,其中计数器程序被复制,这就是它到达 运行 三次的原因。 let x = p; ...
不同于 x <- p; ...
,后者 运行 是 p
,并且将 x
绑定到结果而不是程序 p
本身。
我正在尝试了解 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
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
分配一个新的计数器,returns 一个程序,当 运行 时,增加计数器并 returns 它的旧值。 newCounter
不执行它 returns 的程序。如果您改写:
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
您还可以使用等式推理将 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
在最终版本中,您可以看到 printCounts
确实分配了一个计数器并将其递增 3 次,打印每个中间值。
一个关键步骤是 let-substitution 步骤,其中计数器程序被复制,这就是它到达 运行 三次的原因。 let x = p; ...
不同于 x <- p; ...
,后者 运行 是 p
,并且将 x
绑定到结果而不是程序 p
本身。