为大和类型编写一个 Hashable 实例
Writing a Hashable instance for a large sum type
我有大额型
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 有一个基于泛型的默认实现。
不幸的是——据我所知——这需要值类型中可以是 "packed" 的任何类型都具有 Hashable 实例。嗯,UTCTime 没有。
看来我可以在两个 "suboptimal" 解决方案之间进行选择:
- 手动键入 Hashable 实例。
- 编写 Hashable UTCTime 的孤立实例
我认为应该第三种,"optimal"方式:只为值构造函数编写一个不可能自动完成的实现,即做像这样:
instance Hashable Value where
hashWithSalt (VUTCTime t) = ... -- custom implementation
hashWithSalt _ = ... -- use the default implementation
这个问题当然可以问得更笼统:如何在某些值构造函数的情况下重用现有的实例实现,同时在特定情况下拥有自己的实现 而不必为每个构造函数编写样板值构造函数.
我希望添加一个孤立实例。无论如何,您可以通过以下方式避免这种情况。
定义这个辅助类型
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
这似乎是从以下位置获取孤立实例的好地方: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
的通用表示作为在调用 genericHashWithSalt
之前隐藏 VUTCTime
的另一种方式.
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...
对于这种特殊情况,您应该只使用 hashable-time package,它在标准化位置定义了孤立实例。
一般情况下,我会:
- 将有问题的类型包装在
newtype
中,这样您就可以在本地定义实例,而不会冒孤儿实例问题的风险。
- 只写孤立实例。如果其他人不太可能提供冲突的实例(即当 class 和 type 都属于不太可能被其他人联合使用的晦涩包),那么这不是真正需要担心的事情大约(即使在某些时候会发生重复实例错误,这也很容易修复,这实际上是一件好事,消除了
newtype
会提供的冗余)。
- 将实例添加到它最初来自的库中。如果 class 或类型来自一个非常常见的库,那么在不太常见的库中定义实例可能是有意义的。如果那是开源的,请在此处添加实例并向作者发送拉取请求。
你可以打一个"hole"的字,填上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 ())
我有大额型
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 有一个基于泛型的默认实现。
不幸的是——据我所知——这需要值类型中可以是 "packed" 的任何类型都具有 Hashable 实例。嗯,UTCTime 没有。
看来我可以在两个 "suboptimal" 解决方案之间进行选择:
- 手动键入 Hashable 实例。
- 编写 Hashable UTCTime 的孤立实例
我认为应该第三种,"optimal"方式:只为值构造函数编写一个不可能自动完成的实现,即做像这样:
instance Hashable Value where
hashWithSalt (VUTCTime t) = ... -- custom implementation
hashWithSalt _ = ... -- use the default implementation
这个问题当然可以问得更笼统:如何在某些值构造函数的情况下重用现有的实例实现,同时在特定情况下拥有自己的实现 而不必为每个构造函数编写样板值构造函数.
我希望添加一个孤立实例。无论如何,您可以通过以下方式避免这种情况。
定义这个辅助类型
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
这似乎是从以下位置获取孤立实例的好地方: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
的通用表示作为在调用 genericHashWithSalt
之前隐藏 VUTCTime
的另一种方式.
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...
对于这种特殊情况,您应该只使用 hashable-time package,它在标准化位置定义了孤立实例。
一般情况下,我会:
- 将有问题的类型包装在
newtype
中,这样您就可以在本地定义实例,而不会冒孤儿实例问题的风险。 - 只写孤立实例。如果其他人不太可能提供冲突的实例(即当 class 和 type 都属于不太可能被其他人联合使用的晦涩包),那么这不是真正需要担心的事情大约(即使在某些时候会发生重复实例错误,这也很容易修复,这实际上是一件好事,消除了
newtype
会提供的冗余)。 - 将实例添加到它最初来自的库中。如果 class 或类型来自一个非常常见的库,那么在不太常见的库中定义实例可能是有意义的。如果那是开源的,请在此处添加实例并向作者发送拉取请求。
你可以打一个"hole"的字,填上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 ())