haskelltime-complexityioref

IORef handling of list


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

Solution

  • Yes, in GHC it is O(1). The things that are read and written from IORefs 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.)