Search code examples
haskellexistential-typestrictness

Existential data types with a single strict field


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?


Solution

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