Trying to print a partially-evaluated list in Haskell (with partial success)
Working on exercises from Okasaki’s Purely Functional Data Structures involving lazy evaluation and wondered if it would be possible to introspect on the structures in memory and see what’s actually going on at runtime.
Note: Scala’s lazy Stream
does this, for the cells but not their contents:
scala> val fibs: Stream[Int] = 0 #:: 1 #:: fibs.zip(fibs.tail).map { case (a, b) => a + b }
fibs: Stream[Int] = Stream(0, ?)
scala> fibs(6)
res6: Int = 8
scala> fibs
res7: Stream[Int] = Stream(0, 1, 1, 2, 3, 5, 8, ?)
I found this evaluated
function on Stack Overflow (with a whole list of caveats), and slapped together a simple hack. It works for me with GHC 8.6.5 and -O0
, but not in ghci or with optimization turned on.
An example test:
it "shows a list with several cells evaluated" $ do
let lst = [1..10] :: [Int]
putStrLn $ tshow $ take 3 lst -- actually demand the first three values
showLazyList lst `shouldBe` "1 : 2 : 3 : ..."
And the code:
import GHC.HeapView
import System.IO.Unsafe (unsafePerformIO)
-- [stackoverflow.com/a/2870168...](https://stackoverflow.com/a/28701687/101287)
evaluated :: a -> IO Bool
evaluated = go . asBox
where
go box = do
c <- getBoxedClosureData box
case c of
ThunkClosure {} -> return False
SelectorClosure {} -> return False
APClosure {} -> return False
APStackClosure {} -> return False
IndClosure {indirectee = b'} -> go b'
BlackholeClosure {indirectee = b'} -> go b'
_ -> return True
showLazyList_ :: (Show a) => String -> String -> [a] -> IO String
showLazyList_ elemThunkStr tailThunkStr lst = do
evaluated lst >>= \ case
True -> case lst of
h : t -> do
hStr <- evaluated h >>= \ case
True -> pure (show h)
False -> pure elemThunkStr
tStr <- showLazyList_ elemThunkStr tailThunkStr t
pure $ hStr <> " : " <> tStr
[] -> pure "[]"
False -> pure tailThunkStr
-- |Show the contents of a list, only so far as have already been evaluated. Handles unevaluated
-- elements and cells. Uses unsafePerformIO shamelessly.
showLazyList :: (Show a) => [a] -> String
showLazyList = unsafePerformIO . showLazyList_ "?" "..."