所以,如果我想创建一个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
有更好的方法吗?
答案 0 :(得分:5)
我不清楚你在寻找什么(它可能不适合你)。以下是如何为您的存在数据类型实现Eq
和Hashable
。
您需要向存在主体添加Hashable
,Typeable
和Eq
约束(除了您首先想要的其他限制之外 - 我将使用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
作为最后的评论:请注意,您对SomeId
,Typeable
和Eq
的初始约束都是可导出的,因此满足这些界限并不像它那么困难似乎最初。
如果不清楚,我会向您展示unsafeCoerce
的替代方案。这种方法是......不可取的。特别是,它
Eq
和Ord
简而言之 - 它不会起作用,即使这样做也会造成一大堆难以复制和混淆的错误。
答案 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
,它实现了一个类型安全的异构地图,更像是一个艺术作品,关于这个想法是多么轻浮而不是一个有用的模块。但是,嘿,如果你发现它有用,请告诉我。 ; - )
也许你应退后一步,描述你想要解决的更大问题,以便你觉得你需要这个。我保证你有更好的方法。