So I have an existential data type with a single strict field:
data Uncurry (a :: i -> j -> *) (z :: (i,j)) =
forall x y. z ~ '(x,y) => Uncurry !(a x y)
Experimentation using unsafeSizeof
(stolen from this answer) leads me to believe that it can be zero memory-overhead:
λ p = (0, '\0') :: (Int, Char)
λ q = Uncurry p
λ unsafeSizeof p
10
λ unsafeSizeof q
10
So it seems like Uncurry
is sort of acting like a newtype
, being used only at compile time.
This makes sense to me, as the equality assertion doesn't require a dictionary to be carted about.
Is that a valid interpretation? Do I have any guarantees of that from GHC (or the Haskell report), or did I just luck out?
data
is never transformed to newtype
. Uncurry
does add a new closure, and a pointer for the ~
dictionary is actually also carted around, as of GHC 8.0.2. Hence, Uncurry
has a closure with three words.
unsafeSizeof
is incorrect, since Array#
stores its size in words, while ByteArray#
stores its size in bytes, so sizeofByteArray# (unsafeCoerce# ptrs)
returns the number of words rather than the intended number of bytes. A correct version would look like this on 64 bit systems:
unsafeSizeof :: a -> Int
unsafeSizeof !a =
case unpackClosure# a of
(# x, ptrs, nptrs #) ->
I# (8# +# sizeofArray# nptrs *# 8# +# sizeofByteArray# ptrs)
But note that unsafeSizeof
only gives us the size of the topmost closure. So, the closure size of any boxed tuple will be 24
, which coincides with the closure size of Uncurry t
, since Uncurry
has an info pointer, a useless pointer for ~
, and a pointer for the tuple field. This coincidence also holds with the previous buggy unsafeSizeof
implementation. But the total size of Uncurry t
is greater than that of t
.