In Haskell, is it possible to test if a value has been evaluated to weak head normal form? If a function already exists, I would expect it to have a signature like
evaluated :: a -> IO Bool
There are a few places that similar functionality lives.
A previous answer introduced me to the :sprint
ghci command, which will print only the portion of a value that has already been forced to weak head normal form. :sprint
can observe whether or not a value has been evaluated:
> let l = ['a'..]
> :sprint l
l = _
> head l
'a'
> :sprint l
l = 'a' : _
It's possible in IO
to examine properties that would otherwise be off-limits. For example, it's possible to compare in IO
to see if two values came from the same declaration. This is provided by the StableName
s in System.Mem.StableName
and used famously to solve the observable sharing problem in data-reify. The related StablePtr
does not provide a mechanism to check if the referenced value is in weak head normal form.
A negative answer, for the record: It does not appear to be feasible to reuse the mechanism of
sprint
, because it is tightly tied to interpreted interactive evaluation as opposed to primitive runtime structures — as far as I can tell; I've never looked at GHC internals before.I started by searching for “sprint” in the GHC source on GitHub, which turns out to share an implementation with the “print” command but for a
Bool
flag calledforce
, and followed definitions until I got to RtClosureInspect.cvObtainTerm which appears to be a specialized evaluator.There has been a proposal recently, maybe it's somewhere implemented already https://mail.haskell.org/pipermail/libraries/2015-February/024917.html
I'm not sure that there's anything pre-packaged for this. However, one can code it up:
Here's an example usage in ghci:
Of course, this will be tracking whether the wrapped write-and-then-return-the-original-value thunk is evaluated to WHNF, not whether the thing passed to
track
is evaluated to WHNF, so you'll want to put this as close to the thunk you're interested in as possible -- e.g. it will not be able to tell you whether a thunk made by somebody else has already been evaluated by somebody else before the tracking started. And of course consider usingMVar
instead ofIORef
if you need thread-safety.The ghci implementation for
:sprint
ultimately usesunpackClosure#
from ghc-prim to inspect a closure. This can be combined with knowledge of the format of heap objects to determine if a closure has been evaluated all the way to weak head normal form.There are a few ways to reproduce the inspection done by the ghci implementation for
:sprint
. The GHC api exposesgetClosureData :: DynFlags -> a -> IO Closure
inRtClosureInspect
. The vacuum package, which depends only on ghc-prim, reproduces the code fromRtClosureInspect
and exposesgetClosure :: a -> IO Closure
. It's not immediately obvious how to examine either of theseClosure
representations to, for example, follow an indirection. The ghc-heap-view package inspects closures and exposes both agetClosureData :: a -> IO Closure
and a detailed view of theClosure
. ghc-heap-view depends on the GHC api.We can write
evaluated
in terms ofgetBoxedClosureData
from ghc-heap-view.This handling of blackhole closures may be incorrect while the blackhole is being evaluated. The handling of selector closures may be incorrect. The assumption that AP closures aren't in weak head normal form may be incorrect. The assumption that all other closures are in WHNF is almost certainly incorrect.
Example
Our example will require two concurrent threads to observe in one thread that the other is evaluating expressions.
We can communicate information sideways out of a function without resorting to anything
unsafe
by selectively forcing evaluation. The following builds a stream of pairs of thunks in which we can choose to force one or the other of the pair.zero
forces the first one andone
forces the second one.copy
is an evil identity function that has the side effect of forcing bits in a stream based on inspecting the data.readBs
reads our bit stream by checking if each of the thunks in a pair has beenevaluated
.Forcing
copy
when printing it has the side effect of printing the information observed about the read string.If we run the program and provide the input
abc123
we observe the side effect corresponding to checking if each of the charactersisAlpha