Search code examples
haskelltypesgeneric-programming

Writing a Hashable instance for a large sum type


I have 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

I need a Hashable instance for this datatype. I could of course just type the instance by hand, but fortunately there is a default implementation for hashWithSalt based on generics.

Unfortunately - as far as I understand - this requires any type that can be "packed" inside the Value type to have a Hashable instance. Well, UTCTime does not have one.

So it looks like I can choose between two "suboptimal" solutions:

  1. Type the Hashable instance by hand.
  2. Write an orphan instance of Hashable UTCTime

I think there should be a third, "optimal" way: to only write an implementation for value constructors where there are not possible to do it automatically, i.e do something like this:

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

The question of course could be asked more generally: how can I re-use existing instance implementation in case of certain value constructors while having my own implementation in specific cases without having to write boilerplate for each of the value constructors.


Solution

  • For this particular situation, you should just use the hashable-time package, which defines the orphan instance in a standardised place.

    In general for this kind of situation, I would either:

    • Wrap the problematic type in a newtype, so you can define the instance locally without risking orphan-instance trouble.
    • Just write the orphan instance. If it's unlikely that somebody else will provide a conflicting instance (i.e. when both class and type belong to obscure packages that are unlikely to be used in conjuction by anybody else), then this isn't something one really needs to worry about (even if duplicate-instance errors will happen at some point, this is very easy to fix and that'll actually be a good thing, removing the redundancy that a newtype would give).
    • Add the instance to the library where it originally came from. If either the class or type come from a very common library, then it would probably make sense to define the instance in the less-common library. If that's open source, add the instance there and send the author a pull request.