unsafeCoerce shenanigans

David Feuer david.feuer at gmail.com
Mon Feb 12 23:45:07 UTC 2018


Suppose I have a function of type

unionWith# :: (Hashable k, Eq k) => (a -> a -> (# a #)) -> HashMap k a
-> HashMap k a -> HashMap k a

I can use this to implement strict and lazy unions with practically no
code duplication either in source or in generated code:

S.unionWith, L.unionWith ::
  (Hashable k, Eq k)
  =>  (a -> a -> a) -> HashMap k a -> HashMap k a -> HashMap k
S.unionWith f = unionWith# (\a b -> let !r = f a b in (# r #))
L.unionWith f = unionWith# (\a b -> (# f a b #))

Semantically, this gets the job done, but there's a potential
performance problem. If the function passed to either version of
unionWith fails to inline, then we'll build a closure to interface
with the underlying function. This is not so great, because it adds
indirection in a likely hot spot. I'm not too terribly worried about
the lazy version, because it's prone to leaking memory anyway, but I
do care about the strict version. It seems to me that for the *strict*
version, I can probably use unsafeCoerce to do the job instead:

S.unionWith f = unionWith# (unsafeCoerce f)

When unionWith# applies f to values, it will get back something that
it *thinks* might be a thunk, but actually will never be a thunk. We
don't really care, especially because we just stick it into a lazy
constructor field.

My main concern is that this is obviously not at all an officially
supported way to use unsafeCoerce. What are the chances that GHC will
do something that breaks it? If I can't do this reliably, I should
still be able to use unionWith# to reduce *source* size, relying on
lots of inlining to avoid closures, but the generated code would stay
large.

David


More information about the Glasgow-haskell-users mailing list