12

Is there a GHC-specific "unsafe" extension to ask whether two Haskell references point to the same location?

I'm aware this can break referential transparency if not used properly. But there should be little harm (unless I'm missing something), if it is used very careful, as a means for optimizations by short-cutting recursive (or expensive) data traversal, e.g. for implementing an optimized Eq instance, e.g.:

instance Eq ComplexTree where
   a == b  = (a `unsafeSameRef` b) || (a `deepCompare` b)

providing deepCompare is guaranteed to be true if unsafeSameRef decides true (but not necessarily the other way around).

EDIT/PS: Thanks to the answer pointing to System.Mem.StableName, I was able to also find the paper Stretching the storage manager: weak pointers and stable names in Haskell which happens to have addressed this very problem already over 10 years ago...

hvr
  • 7,775
  • 3
  • 33
  • 47
  • I've often wanted exactly this feature, for exactly this purpose: a faster equality check. – Neil Brown Apr 18 '11 at 12:03
  • 5
    @FUZxxl: if he asks that (and the question indicates quite clearly that he knows what he's talking about), then obviously he needs that. Blaming OP in performing premature optimization without knowing his problem is a bit... premature. – Roman Cheplyaka Apr 18 '11 at 12:38
  • @Roman Cheplyaka: Sorry. I don't wanted the comment to be rude, so I've deleted it. – fuz Apr 18 '11 at 13:04
  • 1
    +1 For perfectly asked question. However, I would trust the compiler to automatically do this optimization for me. Does it not? – Tarrasch Apr 18 '11 at 14:15
  • @Tarrasch: no, there are lots of reasons not to do that. Particularly, equality is not quite reflexive, as Lennart Augustsson pointed out in his response. – Roman Cheplyaka Apr 18 '11 at 20:23

4 Answers4

13

GHC's System.Mem.StableName solves exactly this problem.

Roman Cheplyaka
  • 37,738
  • 7
  • 72
  • 121
9

There's a pitfall to be aware of:

Pointer equality can change strictness. I.e., you might get pointer equality saying True when in fact the real equality test would have looped because of, e.g., a circular structure. So pointer equality ruins the semantics (but you knew that).

augustss
  • 22,884
  • 5
  • 56
  • 93
2

I think StablePointers might be of help here http://www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/Foreign-StablePtr.html Perhaps this is the kind of solution you are looking for:

import Foreign.StablePtr (newStablePtr, freeStablePtr)
import System.IO.Unsafe (unsafePerformIO)

unsafeSameRef :: a -> a -> Bool
unsafeSameRef x y = unsafePerformIO $ do
    a <- newStablePtr x
    b <- newStablePtr y
    let z = a == b
    freeStablePtr a
    freeStablePtr b
    return z;
Jakob Runge
  • 2,287
  • 7
  • 36
  • 47
  • `StableName` would behave the way you suggest. `StablePtr` does not do any deduplication/sharing. It does not go through a hash table or such at all. – Peaker Apr 06 '16 at 11:31
  • Ah, thanks for the update. I see the shortcomings of `StablePtr` for this use. – Jakob Runge Apr 07 '16 at 11:18
2

There's unpackClosure# in GHC.Prim, with the following type:

unpackClosure# :: a -> (# Addr#,Array# b,ByteArray# #)

Using that you could whip up something like:

{-# LANGUAGE MagicHash, UnboxedTuples #-} 
import GHC.Prim

eq a b = case unpackClosure# a of 
    (# a1,a2,a3 #) -> case unpackClosure# b of 
        (# b1,b2,b3 #) -> eqAddr# a1 b1

And in the same package, there's the interestingly named reallyUnsafePtrEquality# of type

reallyUnsafePtrEquality#  :: a -> a -> Int#

But I'm not sure what the return value of that one is - going by the name it will lead to much gnashing of teeth.

yatima2975
  • 6,580
  • 21
  • 42
  • 1
    @yatima2975: I asked a question about what `reallyUnsafePtrEquality#` does in the #ghc channel. The answer was: *FUZxxl: (...) but it takes two objects and tells you whether their addresses in memory are the same (=> they are the same object)*. You can even try in ghci! But then, you see, where the problem is: It only works in some cases. – fuz Apr 18 '11 at 13:16
  • 1
    It returns 0# for inequality and 1# for equality. But notice, that there may be both false negatives and false positives. – fuz Apr 18 '11 at 13:22
  • @FYZxxl false negatives *and* false positives...in other words...it's useless? – Dan Burton Apr 18 '11 at 15:51
  • @FUZxxl - Thanks for finding that out! False negatives aren't so bad (for the question-asker) as he'll just have to do a 'normal' comparison, but the possibility of false positives makes `reallyUnsafePtrEquality` completely useless for this problem. Presumably, the positives arise due to objects being moved by the GC? – yatima2975 Apr 18 '11 at 15:53
  • @Dan They should rename it `reallyUselessPtrEquality#` then :) – yatima2975 Apr 18 '11 at 15:55
  • @yatima2975: AFAIK, false positives are possible when using different types. In IRC, somebody said, that `let x = [] :: [Char]; y = [] : [Int] in reallyUnsafePtrEquality# x y` may yield `1#` under some compiler optimizations. Moving objects shouldn't be an issue, since all pointers are updated while GC. – fuz Apr 18 '11 at 15:58
  • Thus, `reallyUnsafePtrEquality#` should be safe for day-to-day uses, but only when it is backed up by a deep equality test. – fuz Apr 18 '11 at 16:05