我可以使用unsafecorce作为存在类型类的hashkey / comaprison键吗?

时间:2017-02-11 04:51:07

标签: haskell dictionary

所以,如果我想创建一个hashmap键,例如使用像

这样的存在类型
data SomeId = forall a. SomeClass a => SomeId a

所以,如果我想创建一个地图,我需要自己实现Ord。有没有办法存储价值?在这种情况下,unsafeCooerce是永久性的,还是有任何警告?

喜欢这个吗?

instance Ord SomeId where
    compare (Id a) (Id b) = compare (unsafeCoerce a)::Int (unsafeCoerce b)::Int

有更好的方法吗?

2 个答案:

答案 0 :(得分:5)

我不清楚你在寻找什么(它可能不适合你)。以下是如何为您的存在数据类型实现EqHashable

您需要向存在主体添加HashableTypeableEq约束(除了您首先想要的其他限制之外 - 我将使用Show)。

import Data.Typeable
import Data.Hashable

data SomeId = forall a. (Show a, Hashable a, Eq a, Typeable a) => SomeId a

-- This instance uses the fact 'SomeId' wraps types that have and 'Eq' and 
-- a 'Typeable' constraint
instance Eq SomeId where
  SomeId a == SomeId b = maybe False (a ==) (cast b)

-- This instance uses the fact 'SomeId' wraps types that have 'Hashable' constraints
instance Hashable SomeId where
  hashWithSalt s (SomeId a) = 0xdeadbeef * hashWithSalt s a

并且,出于调试目的:

instance Show SomeId where
  show (SomeId x) = show x

现在,我可以使用SomeId作为HashMap中的密钥。例如,

ghci> import qualified Data.HashMap.Strict as H
ghci> hashMap = H.fromList [(SomeId 1, "one"), (SomeId (), "unit"), (SomeId "s", "string")]
ghci> H.lookup (SomeId 1) hashMap
Just "one"
ghci> H.lookup (SomeId ()) hashMap
Just "unit"
ghci> H.lookup (SomeId "s") hashMap
Just "string
ghci> H.lookup (SomeId 2) hashMap
Nothing
ghci> H.lookup (SomeId True) hashMap
Nothing

作为最后的评论:请注意,您对SomeIdTypeableEq的初始约束都是可导出的,因此满足这些界限并不像它那么困难似乎最初。

如果不清楚,我会向您展示unsafeCoerce的替代方案。这种方法是......不可取的。特别是,它

  • 违反一般的参照透明度
  • 完全无视EqOrd
  • 的所有法律

简而言之 - 它不会起作用,即使这样做也会造成一大堆难以复制和混淆的错误。

答案 1 :(得分:4)

这太糟糕了,你绝对不应该这样做。 unsafeCoerce这里可以返回不同的值,具体取决于表达式是否已经被评估,如果GC决定移动对象(并且GHC有一个压缩收集器AFAIK,那么这将一直发生),甚至可能依赖于通过读取结束与它无关的对象。可怕,可怕的想法,这个。

参考平等测试,就像你在大多数命令式语言中看到的那样

var foo = Object();
var bar = Object();
foo === foo   // true
foo === bar   // false

在Haskell中是不可能的,因为引用透明度,它表示您始终可以用其定义替换变量,并且不会发生任何变化。如果不应命名的语言具有引用透明性,则可以用以下代码替换该代码:

 Object() === Object()  // true
 Object() === Object()  // false
这显然是一个矛盾。参考透明度是使Haskell很好用的事情之一,但不幸的是,如果我推断你正在尝试做什么,它将使它变得不可能。我建议不要绕过引用透明度(你可以使用unsafePerformIO来做) - 如果没有它,Haskell 非常很难推理 - 整个语言是围绕这个假设构建的。

所以有了这个警告,这里有一些关于游戏的笔记,因为我一直在这个特殊的兔子洞。

在我看来,您正在将地图从Id建模到他们所引用的对象,并且此地图可以是多态的。即也许你想要这样的功能:

insert :: SomeClass a => a -> MyMap -> (SomeId, MyMap)
lookup :: MyMap -> SomeId -> ???

有一些???的合适值,这是不可能的,因为我们忘记了类型信息。你可以使用Data.Dynamic,我想:

lookup :: MyMap -> SomeId -> Maybe Dynamic

取决于您想要做什么。这将涉及大量的投射,你基本上是通过这样做把静态类型的安全性抛到窗外。

也许你想考虑打字标识符?

insert :: SomeClass a => a -> MyMap -> (SomeId a, MyMap)
lookup :: MyMap -> SomeId a -> Maybe a

如果您SomeClass需要一些唯一"哈希"生成函数(并且它确实在各种类型中保证是唯一的)然后您可以使此API工作。如果发生冲突,则会出现错误unsafeCoerce错误(例如,在ghci中尝试unsafeCoerce 42 "foo" :: String)。您可以通过在您的Id类型中添加TypeRep来缓解一些痛苦,但您仍然必须自己决定如何散列值。

使用unsafePerformIO制作密钥时,可以将值与新生成的Unique配对,但如果执行此操作,则会违反参照透明度,因为如果生成两个相同的密钥价值,他们会比较不同的,这在纯语言中是无稽之谈。

基本上,异构地图的整个想法充满了困难。

我写了hetero-map,它实现了一个类型安全的异构地图,更像是一个艺术作品,关于这个想法是多么轻浮而不是一个有用的模块。但是,嘿,如果你发现它有用,请告诉我。 ; - )

也许你应退后一步,描述你想要解决的更大问题,以便你觉得你需要这个。我保证你有更好的方法。