Написание хэшируемого экземпляра для типа большой суммы - PullRequest
0 голосов
/ 29 июня 2018

У меня большая сумма типа

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 голосов
/ 29 июня 2018

Вы можете сделать тип с "отверстием" и заполнить отверстие в 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 ())
0 голосов
/ 29 июня 2018

Похоже, это хорошее место для получения экземпляра-сироты: 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.

 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...
0 голосов
/ 29 июня 2018

В этой конкретной ситуации вам просто нужно использовать пакет с хэшируемым временем , который определяет экземпляр-сироту в стандартизированном месте.

В общем, для такой ситуации я бы либо:

  • Оберните проблемный тип в newtype, чтобы вы могли определить экземпляр локально, не рискуя столкнуться с проблемой-сиротой.
  • Просто напишите экземпляр-сироту. Если маловероятно, что кто-то другой предоставит конфликтующий экземпляр (т. Е. Когда и класс, и тип принадлежат непонятным пакетам, которые вряд ли будут использоваться совместно кем-либо еще), то об этом действительно не стоит беспокоиться (даже если В некоторый момент произойдут ошибки дублирующегося экземпляра, это очень легко исправить, и это действительно будет хорошо, удалив избыточность, которую даст newtype).
  • Добавить экземпляр в библиотеку, откуда он изначально был. Если или класс или тип происходят из очень распространенной библиотеки, то, вероятно, имеет смысл определить экземпляр в менее распространенной библиотеке. Если это открытый исходный код, добавьте туда экземпляр и отправьте автору запрос на извлечение.
0 голосов
/ 29 июня 2018

Я бы предпочел добавить экземпляр-сироту. Во всяком случае, вы можете избежать этого следующим образом.

Определите этот вспомогательный тип

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
...