StableNames 是专门为解决像您这样的问题而设计的。
请注意,StableNames 只能在IO
monad 中创建。所以你有两个选择:要么在monad中创建你的对象,要么在你的实现中使用(在这种情况下或多或少很好)。IO
unsafePerformIO
(==)
但我应该强调,可以以一种完全安全的方式(没有unsafe*
函数)来做到这一点:只有稳定名称的创建应该发生在IO
; 之后,您可以以完全纯粹的方式比较它们。
例如
data SNWrapper a = SNW !a !(StableName a)
snwrap :: a -> IO (SNWrapper a)
snwrap a = SNW a <$> makeStableName a
instance Eq a => Eq (SNWrapper a) where
(SNW a sna) (SNW b snb) = sna == snb || a == b
请注意,如果稳定名称比较说“否”,您仍然需要执行全值比较以获得明确的答案。
根据我的经验,当你有很多共享并且由于某种原因不愿意使用其他方法来表示共享时,效果很好。
(说到其他方法,例如,您可以将IO
monad替换为State Integer
monad 并在该 monad 中生成唯一整数作为“稳定名称”的等价物。)
另一个技巧是,如果你有一个递归数据结构,让递归通过SNWrapper
. 例如,而不是
data Tree a = Bin (Tree a) (Tree a) | Leaf a
type WrappedTree a = SNWrapper (Tree a)
采用
data Tree a = Bin (WrappedTree a) (WrappedTree a) | Leaf a
type WrappedTree a = SNWrapper (Tree a)
这样,即使短路不会在最顶层触发,它也可能会在中间的某个地方触发,并且仍然可以为您节省一些工作。