This is a follow-up to a question I asked earlier. I am wondering if the way list is updated in IORef
below in the accepted solution is O(1)
or not, in every call of fetch
. I suspect it is because IORef
is likely just keeping a pointer to head of the list (instead of traversing and copying entire list which will be O(n) every time. Just changing the pointer to new head should be O(1), and should prevent eager evaluation of entire list). But, ghc-core
won't show that low-level code. So, asking here:
mklstream :: L.ByteString -> (IO S.ByteString -> IO r) -> IO r
mklstream lbs sink = do
ref <- newIORef (L.toChunks lbs)
let fetch :: IO S.ByteString
fetch = do chunks <- readIORef ref
case chunks of
[] -> return S.empty
(c:cs) -> do writeIORef ref cs
return c
sink fetch
Yes, in GHC it is O(1). The things that are read and written from IORef
s are exactly the same pointers that everything else in the implementation uses as data representation. Indeed, you can know just from the type of writeIORef
that it does nothing special to its data:
writeIORef :: IORef a -> a -> IO ()
Because the a
is completely unconstrained, writeIORef
cannot inspect the data and in particular cannot traverse any list you hand it. (This is not quite convincing -- the runtime can do anything it likes even with unconstrained types, and you might believe that writeIORef
is a runtime primitive -- but it turns out to be true in this case anyway.)