So I have a typeclass somewhat like this
class Stringify x where
stringify :: x -> String
and I have two other typeclasses somewhat like those
class LTextify x where
ltextify :: x -> L.Text
class Textify x where
textify :: x -> T.Text
and I want to define dependent typeclasses
class (LTextify x) => Stringify x where
stringify = L.unpack . ltextify
class (Textify x) => Stringify x where
stringify = T.unpack . textify
But of course haskell complains about "duplicate instance declarations" which of course is true. I've tried playing around with OVERLAPPABLE and OVERLAPPING, but to no avail. What I want is, that if a class has an instance of LTextify, then that should be used. If not, then if it has an instance of Textify than that should be used to implement Stringify. And I want a class to also be able to explicitely define an instance for Stringify which then overlaps a possibly existing instance of Textify or LTextify. Of course in a deterministic way.
I'm assuming those last two should be instance declarations and not class declarations?
Having different behaviour depending on if an instance exists or not really supported in Haskell, since orphan instances means that the list of available instances can change in unpredictable ways depending on imports. I recommend (if possible) to instead make
Stringify
a superclass of bothTextify
andLTextify
.Additionally, these particular instances would be impossible to make, because the first step of GHC's instance resolution is to discard all constraints and check which instance would match structurally, so it would look like this:
There is no way to distinguish between these two at all, so ghc would have to arbitrary chose between one of them and hope it's the right one.
In order to use the
OVERLAPPING
pragmas, there needs to be one instance that is strictly more specific than the other, likeSee this section in the user manual for a more in depth explanation of how it works. Here's a relevant quote:
There is also the deprecated extension
IncoherentInstances
which allows multiple matching instances even if one isn't strictly more specific than another, but not even that can be used here, since both instances are structurally identical.IncoherentInstances
will arbitrarily choose one of the instances, so it should be avoided as much as possible and only ever be used if it doesn't matter which instance is matched.Edit: One way to reduce boilerplate when defining the instances is to use
DerivingVia
together with a newtype wrapper:And then you can use it to derive instances like this
If you do not have control over the data types and don't want to add orphan instances (which you should avoid if possible), these newtypes can also be used directly by wrapping them around your data to give it the relevant instances.