GHC中派生的Eq实例的效率如何?

时间:2012-10-09 10:36:58

标签: haskell lazy-evaluation

GHC(以及一般Haskell)派生的Eq实例中是否存在短路,当我比较同一个数据类型的实例时会触发该实例?

-- will this fire?
let same = complex == complex

我的计划是读取一个懒惰的数据结构(比如一棵树),更改一些值然后比较旧版本和新版本以创建一个差异,然后将其写回文件。

如果内置短路,那么一旦发现新结构引用旧值,比较步骤就会中断。同时,这首先不会从文件中读取。

我知道我不应该担心Haskell中的引用,但这似乎是处理延迟文件更改的好方法。如果内置没有短路,是否有办法实现这一点?对不同方案的建议表示欢迎。

2 个答案:

答案 0 :(得分:8)

StableNames专门用于解决像你这样的问题。

请注意,StableNames只能在IO monad中创建。所以你有两个选择:在<{1}} monad中创建你的对象,或者在你的IO实现中使用unsafePerformIO(这或多或少都很好)情况)。

但我要强调,可以以一种完全安全的方式(没有(==)函数)这样做:只在unsafe*中创建稳定名称;之后,你可以用完全纯粹的方式比较它们。

E.g。

IO

请注意,如果稳定名称比较显示为“no”,则仍需要执行完整值比较以获得明确的答案。

根据我的经验,当你有很多分享并且出于某种原因不愿意使用其他方法来表示分享时,这种方法非常有效。

(说到其他方法,你可以,例如,用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 monad替换IO monad,并在该monad中生成唯一的整数,作为“稳定名称”的等价物。)

另一个技巧是,如果你有一个递归数据结构,让递归通过State Integer。例如。而不是

SNWrapper

使用

data Tree a = Bin (Tree a) (Tree a) | Leaf a
type WrappedTree a = SNWrapper (Tree a)

这样,即使短路不会在最顶层发射,它也可能会在中间的某处发生,但仍可以节省一些工作。

答案 1 :(得分:4)

(==)的两个参数都是同一个对象时,没有短路。派生的Eq实例将进行结构比较,并且在相等的情况下,当然需要遍历整个结构。您可以使用

自行构建可能的快捷方式
GHC.Prim.reallyUnsafePtrEquality# :: a -> a -> GHC.Prim.Int#

但事实上这很少会发生:

Prelude GHC.Base> let x = "foo"
Prelude GHC.Base> I# (reallyUnsafePtrEquality# x x)
1
Prelude GHC.Base> I# (reallyUnsafePtrEquality# True True)
1
Prelude GHC.Base> I# (reallyUnsafePtrEquality# 3 3)
0
Prelude GHC.Base> I# (reallyUnsafePtrEquality# (3 :: Int) 3)
0

如果您从文件中读取结构,它肯定不会找到与已经在内存中的对象相同的对象。

您可以使用重写规则来避免对词法相同的对象进行比较

module Equal where

{-# RULES
"==/same"  forall x. x == x = True
  #-}

main :: IO ()
main = let x = [1 :: Int .. 10] in print (x == x)

导致

$ ghc -O -ddump-rule-firings Equal.hs 
[1 of 1] Compiling Equal            ( Equal.hs, Equal.o )
Rule fired: Class op enumFromTo
Rule fired: ==/same
Rule fired: Class op show

规则触发(注意:它没有触发let x = "foo",但是使用用户定义的类型,它应该)。