Вы можете думать о 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
Вы также можете использовать эквациональные рассуждения, чтобы развернуть 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
буквально выделяет счетчик и увеличивает его три раза, печатая каждое промежуточное значение.
Один ключевой шаг - это этап подстановки, когда счетная программа дублируется, поэтому она запускается три раза.let x = p; ...
отличается от x <- p; ...
, который запускает p
и привязывает x
к результату, а не к самой программе p
.