为大和类型编写Hashable实例

时间:2018-06-29 11:02:46

标签: haskell types generic-programming

我的总和类型很大

data Value
= VNull
| VDouble !Double
| VSci !Scientific
| VInt !Int
| VText !Text
| VTexts ![Text]
| VByteString !BS.ByteString
| VUTCTime !UTCTime
-- This goes on for quite a few more lines

我需要此数据类型的Hashable实例。我当然可以手动键入实例,但是幸运的是,基于泛型的hashWithSalt有一个默认实现。

不幸的是,据我所知,这要求可以“打包”在Value类型内的任何类型都具有Hashable实例。好吧,UTCTime没有一个。

所以看起来我可以在两个“次优”解决方案之间进行选择:

  1. 手动键入Hashable实例。
  2. 写一个Hashable UTCTime的孤立实例

我认为应该是第三种“最佳”方法:只为不可能自动执行的值构造函数编写实现,即执行以下操作:

instance Hashable Value where
    hashWithSalt (VUTCTime t) = ... -- custom implementation
    hashWithSalt _ = ... -- use the default implementation

当然可以更普遍地问这个问题:在某些值构造函数的情况下,如何重用现有的实例实现,而在特定情况下具有自己的实现,而不必为每个值构造函数编写样板。

4 个答案:

答案 0 :(得分:6)

对于这种特殊情况,您应该只使用hashable-time package,它在标准化的位置定义了孤立实例。

通常对于这种情况,我会:

  • 将有问题的类型包装在newtype中,这样您就可以在本地定义实例,而不必担心出现孤立实例麻烦。
  • 只需编写孤立实例。如果其他人不太可能提供有冲突的实例(即,当类和类型都属于不太可能被其他人使用的模糊软件包时),那么这并不是人们真正需要担心的事情(即使重复实例错误会在某个时刻发生,这很容易解决,并且实际上是一件好事,消除了newtype会带来的冗余)。
  • 将该实例添加到其最初来自的库中。如果 某个类或类型来自一个非常通用的库,那么在不那么常见的库中定义实例可能是有意义的。如果是开源的,则在其中添加实例,然后向作者发送拉取请求。

答案 1 :(得分:3)

我希望添加一个孤立实例。无论如何,您可以避免以下情况。

定义此辅助类型

data ValueNotTime
= VNull
| VDouble !Double
| VSci !Scientific
| VInt !Int
| VText !Text
| VTexts ![Text]
| VByteString !BS.ByteString

并自动派生Hashable。然后,写一个同构

iso :: Value -> Either ValueNotTime UTCTime
osi :: Either ValueNotTime UTCTime -> Value

以明显的方式。然后,

instance Hashable Value where
    hashWithSalt v = case iso v of
       Left valueNoTime -> use derived implementation (hashWithSalt valueNoTime)
       Right utcTime    -> use custom implementation

答案 2 :(得分:2)

这似乎是从以下位置获取孤立实例的好地方:https://hackage.haskell.org/package/hashable-time


如果导出了一个通用实现,例如genericHashWithSalt(但目前还没有https://github.com/tibbe/hashable/issues/148),则可以这样做

data Value_ utctime
  = ...
  | VUTCTime utctime
  deriving (Generic, Functor)
type Value = Value_ UtcTime

instance Hashable Value where
  hashWithSalt s (VUTCTime t) = (my custom implementation) s t
  hashWithSalt s v = genericHashWithSalt s (fmap (\_ -> ()) v)

如果您不想破坏类型,也应该可以修改Value的通用表示形式,作为在调用VUTCTime之前隐藏genericHashWithSalt的另一种方式。 / p>

 data Value = ...  -- the original one

 instance Hashable Value where
   hashWithSalt s (VUTCTime t) = (my custom implementation) s t
   hashWithSalt s t = genericHashWithSalt s (genericHideLastConstructor t)
   -- something like that...

答案 3 :(得分:2)

您可以创建带有“孔”的类型,然后在hashWithSalt中填充孔。所以:

{-# LANGUAGE DeriveFunctor, DeriveGeneric, DeriveAnyClass #-}
import Data.Hashable
import Data.Text (Text)
import Data.Time
import GHC.Generics
import qualified Data.ByteString as BS
data ValueF a
    = VNull
    | VDouble !Double
    | VInt !Int
    | VText !Text
    | VTexts ![Text]
    | VByteString !BS.ByteString
    | VUTCTime !a
    deriving (Hashable, Functor, Generic)

newtype Value = Value (ValueF UTCTime)

instance Hashable Value where
    hashWithSalt s (Value (VUTCTime t)) = {- whatever you're going to do here -}
    hashWithSalt s (Value v) = hashWithSalt s (() <$ v)
    -- OR
    -- hashWithSalt s (Value v) = hashWithSalt s (unsafeCoerce v :: Value ())