Ghc-vis and ghc-heap-view are awesome. I’ve been playing with the idea of combining ghc-vis or something similar with -finfo-table-map to print thunks and function pointers, and to resolve the relevant core code. It sort of work but only with compiled code because GHCi does not populate the info-table-map and the breakpoint source locations aren’t externally accessible either.
But the core for sieve is a mess so maybe it wouldn’t be helpful anyway
Maybe the GHC-core code for the infinite list might help with intuitions? It’s an intermediate language which GHC uses and which is a bit more explicit.
Rec {
primes_go3
= \ x_srPd ->
let {
sat_srPh
= case x_srPd of wild_srPf {
__DEFAULT ->
case +# wild_srPf 1# of sat_srPg { __DEFAULT ->
primes_go3 sat_srPg
};
9223372036854775807# -> []
} } in
let { sat_srPe = I# x_srPd } in : sat_srPe sat_srPh
end Rec }
Here, the let bindings correspond pretty closely to allocations on the heap. You can translate this pretty directly to imperative code, where thunks are zero-argument functions which cache their results.
function mkList(i) {
console.log("mkList", i);
function thunk() {
if (this.result != null) { return this.result; }
this.result = mkList(i+1);
return this.result;
}
p = i;
return [p, thunk];
}
> a = mkList(2)
mkList 2 debugger eval code:2:13
Array [ 2, thunk() ]
>a[1]()[1]()[0]
mkList 3 debugger eval code:2:13
mkList 4 debugger eval code:2:13
4
>a[1]()[1]()[0]
4