在我最初尝试创建一个不相交的集数据结构时,我创建了一个Point
数据类型,其中parent
指针指向另一个Point
:
data Point a = Point
{ _value :: a
, _parent :: Point a
, _rank :: Int
}
要创建一个单例集,创建一个Point
,其自身为其父级(我相信这称为绑结):
makeSet' :: a -> Point a
makeSet' x = let p = Point x p 0 in p
现在,当我想写findSet
时(即按照父指针,直到找到父本身为Point
)我遇到了问题:是否可以检查是否是这种情况?一个天真的Eq
实例当然会无限循环 - 但是这个检查在概念上是否可以写?
(我最后使用Maybe Point
作为父字段,请参阅my other question。)
答案 0 :(得分:5)
不,你所要求的在Haskell世界中被称为引用标识:对于某种类型的两个值,你可以检查它们在内存中是否是相同的值或者两个单独的值碰巧具有完全相同的属性。
对于您的示例,您可以问自己是否会考虑以下两个值相同或不同:
pl1 :: Point Int
pl1 = Point 0 (Point 0 pl1 1) 1
pl2 :: Point Int
pl2 = Point 0 pl2 1
Haskell认为两个值完全相同。即Haskell 不支持引用标识。其中一个原因是它会违反Haskell支持的其他功能。例如,在Haskell中的情况是,我们总是可以通过该函数的实现替换对函数的引用而不改变含义(等式推理)。例如,如果我们执行pl2
:Point 0 pl2 1
并按其定义替换pl2
,我们会得到Point 0 (Point 0 pl2 1) 1
,使pl2
的定义等同于{ {1}}的。这表明Haskell不允许您在不违反等式推理所隐含的属性的情况下观察pl1
和pl1
之间的差异。
你可以使用像pl2
这样的不安全功能(如上所述)来解决Haskell中缺少引用标识的问题,但你应该知道你正在破坏Haskell的核心原则,你可能会发现奇怪的错误。 GHC开始优化(例如内联)您的代码。最好使用不同的数据表示形式,例如您使用unsafePerformIO
值提到的那个。
答案 1 :(得分:4)
您可以尝试使用StableName
(或StablePtr
)和unsafePerformIO
执行此操作,但对于此情况,这似乎比Maybe Point
更糟糕。
答案 2 :(得分:2)
为了观察你最感兴趣的效果 - 指针相等而不是值相等 - 你很可能想要在ST
monad中编写你的算法。 ST
monad可以被认为类似于“本地不纯的IO,全局纯API”,但是,根据Union Find的性质,你可能不得不将整个查找过程放入代码的不纯部分
幸运的是,monad仍然很好地含有这种杂质。
There is also already an implementation of Union Find in Haskell使用三种方法来实现您正在寻找的身份:使用IO
monad,使用IntSet
和使用ST
monad
答案 3 :(得分:0)
在Haskell中,数据是不可变的(IO a
,IORef a
,ST a
,STRef a
,...除外,数据是函数。
所以,任何数据都是“单身”
键入
时data C = C {a, b :: Int}
changeCa :: C -> Int -> C
changeCa c newa = c {a = newa}
您不会更改变量,而是销毁旧数据并创建新数据。 您可以尝试使用链接和指针,但它是无用且复杂的
最后,
data Point a = Point {p :: Point a}
是一个无限列表数据= Point { p=Point { p=Point { p=Point { ....