Reputation: 3737
What is the reason of error "Overlapping instances for ToJSON [XYZ]" in this example:
data XYZ = XYZ Int
instance ToJSON [XYZ] where
toJSON xs = ...
and GHC shows as overlapping instance this:
instance ToJSON a => ToJSON [a] ...
which is not a suitable case: XYZ
has not ToJSON
instance yet. I can fix it with {-# OVERLAPS #-}
but I don't understand why GHC does not understand that [a]
where a
has not ToJSON
needs explicit definition for ToJSON
. What do I miss?
Upvotes: 3
Views: 94
Reputation: 120711
XYZ
has notToJSON
instance yet
you say it yourself – no instance yet. There is no way to be sure somebody doesn't add that instance later on. What's supposed to happen in this case? In particular, if one piece of code doesn't see the instance yet and therefore picks instance ToJSON a => ToJSON [a]
, and another piece later on has the instance in scope, you'd get two possibly incompatible instances being in use simultaneously. Havoc.
I reckon you probably want to express instance ToJSON XYZ
should never be defined, but really Haskell has no notion of a type being not an instance of a class. The assumption is that every type is potentially an instance of every class – only perhaps it's not possible to see the instance yet.
Ok, actually it is possible to disable an instance from being really defined – by defining it yourself, with an unfulfillable superclass:
instance Bottom => ToJSON XYZ where
toJSON = no
...which will cause a type error if you try to invoke toJSON
for XYZ
anywhere. But still, as far as the language is concerned this is an instance, it just happens to be a “un-typecheckeable instance”.
So, the instances are overlapping, even if the overlap may not actually be something that could compile – but the instance resolution doesn't attempt to check that, i.e. to try compile it, that would get extremely inefficient quickly and in general lead to quite unpredictable program behaviour. (If you've ever worked with advanced C++ templates, you know what I mean.)
Instead, instance lookup always just matches the instance head, i.e. [a]
vs. [XYZ]
. Which, now clearly, do overlap.
The proper thing to do for your application is probably to make a
newtype XYZs = XYZs { getXYZs :: [XYZ] }
instance ToJSON XYZs where
...
If you find that syntactically awkward, note you can use the OverloadedLists
extension
import GHC.Exts (IsList(..))
instance IsList XYZs where
type Item XYZs = XYZ
fromList = XYZs
toList = getXYZs
and then
{-# LANGUAGE OverloadedLists #-}
test :: XYZs
test = [XYZ 1, XYZ 2]
is legal.
Upvotes: 5