【问题标题】:Writing a Hashable instance for a large sum type为大总和类型编写 Hashable 实例
【发布时间】:2018-12-08 13:29:09
【问题描述】:

我有一个大额类型

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

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

【问题讨论】:

    标签: haskell types generic-programming


    【解决方案1】:

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

    一般来说,对于这种情况,我会:

    • 将有问题的类型包装在 newtype 中,这样您就可以在本地定义实例,而不会冒孤立实例问题的风险。
    • 只写孤儿实例。如果其他人不太可能提供冲突的实例(即,当类和类型都属于其他人不太可能结合使用的晦涩包时),那么这不是人们真正需要担心的事情(即使重复实例错误会在某个时候发生,这很容易修复,这实际上是一件好事,消除了newtype 会给出的冗余)。
    • 将实例添加到它最初来自的库中。如果任何一个类或类型来自一个非常常见的库,那么在不太常见的库中定义实例可能是有意义的。如果这是开源的,请在此处添加实例并向作者发送拉取请求。

    【讨论】:

    • 嗯,这是最实用的建议,即我将实际使用的建议,所以我将接受这个建议。 Hashable-time 为我做了关于孤儿实例的脏活 :) 老实说,我可以为 UTCTime 创建一个孤儿实例,因为我正在为特定问题编写应用程序,而且几乎不可能有人会使用该代码用于其他任何内容。新型解决方案也是可行的,但需要进行一些重构(模式、棱镜)。在这种情况下,将孤立实例分离到独立包中似乎也是一个很好的通用策略。谢谢你的回答!
    【解决方案2】:

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

    定义这个辅助类型

    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
    

    【讨论】:

    • 此解决方案将使我重写与我的值上的模式匹配并使用透镜/棱镜的代码,这大约是主应用程序中代码行的一半。顺便说一句好技巧,谢谢你的回答!
    【解决方案3】:

    这似乎是获取孤儿实例的好地方: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...
    

    【讨论】:

    • 好的,谢谢!我刚刚针对该问题提出了拉取请求:)
    【解决方案4】:

    你可以制作一个带有“洞”的类型,并在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 ())
    

    【讨论】:

    • 这是一个有趣的技巧,谢谢。 :) 不幸的是,这需要做更多的重构,而不仅仅是为我的所有 Value 构造函数编写 hashWithSalt 案例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-06
    • 2018-11-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多