I'm trying to understand how IORefs are really used, and I'm having trouble following the sample code I found on 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
When printCounts executes "c <- newCounter", why doesn't c get the result of doing the work in the newCounter "return $ do" block, which seems like it should get assigned to the constant "IO 0" the first time it is called and then never change? Instead, c seems to get assigned the function defined in that "return $ do" block, which is then executed anew every time printCounts gets to another "print =<< c." It seems that the answer somehow lies in newCounter having the double nested "IO (IO Int)" type, but I can't follow why that makes c a function to be re-executed when called instead of a constant evaluated just once.
You can think of
IOas a type of programs.newCounter :: IO (IO Int)is a program that outputs a program. More precisely,newCounterallocates a new counter, and returns a program that, when run, increments the counter and returns its old value.newCounterdoesn't execute the program it returns. It would if you wrote instead:You can also use equational reasoning to unfold
printCountsinto a sequence of primitives. All versions ofprintCountsbelow are equivalent programs:In the final version, you can see that
printCountsquite literally allocates a counter and increments it three times, printing each intermediate value.One key step is the let-substitution one, where the counter program gets duplicated, which is why it gets to run three times.
let x = p; ...is different fromx <- p; ..., which runsp, and bindsxto the result rather than the programpitself.