1

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
Robin Green
  • 32,079
  • 16
  • 104
  • 187
Sal
  • 4,312
  • 1
  • 17
  • 26
  • While you're thinking operationally at this low level you might like to read https://ghc.haskell.org/trac/ghc/wiki/Commentary/Rts/Storage/HeapObjects . And if you're working with mutable variables keep in mind that an `IORef` can easily point to a thunk and you should be mindful of this to avoid space leaks with `write/modifyIOref` – jberryman Jun 05 '16 at 19:13
  • Side question: any specific reason for choosing a continuation-passing function? Wouldn't `lstream :: L.ByteString -> IO S.ByteString` be easier to use, which basically corresponds to the `sink = id` case? – chi Jun 05 '16 at 19:37
  • @chi, that style is due to `GivesPopper` function in the original question that I linked to. – Sal Jun 05 '16 at 21:46

1 Answers1

7

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.)

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380