RandomB
RandomB

Reputation: 3737

Default instance implementations with "overlaps"

I get error:

 Duplicate instance declarations:
   instance [overlap ok] EnumTag a => Read a
     -- Defined at /XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/intero/intero2932Xpa-TEMP.hs:110:27
   instance [overlap ok] StrTag a => Read a
     -- Defined at /XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/intero/intero2932Xpa-TEMP.hs:121:27 (intero)

For this code:

class (Show a, Enum a) => EnumTag a where
  anyEnum :: a

instance {-# OVERLAPS #-} EnumTag a => Read a where
  readPrec = RP.lift P.skipSpaces >> expectEnum
instance {-# OVERLAPS #-} EnumTag a => Eq a where
  a == b | a == anyEnum || b == anyEnum = True
         | otherwise = fromEnum a == fromEnum b

class StrTag a where
  anyStr :: a
  tagPrefix :: a -> String -- ^ should be constant
  toStr :: String -> a

instance {-# OVERLAPS #-} StrTag a => Read a where
  readPrec = parens $ do
    RP.lift P.skipSpaces
    (RP.lift $ expectShown anyStr) <++ RP.lift g
    where g = do
            Just s@(_:_) <- L.stripPrefix tagPrefix <$> expectTag
            return $ toStr s

Why does it happen? Read a in 1st instance is valid only when a is EnumTag, in 2nd one - is valid only when a is StrTag.

How can I fix this error and to create "default" instances for EnumTag and to StrTag, so client code will "inherit" those functionality (Read) simple, only with instantiation of EnumTag or StrTag ?

Upvotes: 0

Views: 248

Answers (1)

chi
chi

Reputation: 116139

Overlapping instances are quite fragile. Arguably, they should never be used.

Anyway, overlapping instances cause GHC to avoid committing to an instance too soon. E.g.

-- overlapping
instance ... => Read [Int]  -- 1
instance ... => Read [a]    -- 2

When GHC has to solve a constraint such as Read [b], it does not commit to instance 2, since it can't rule out the more specific instance 1: after all the type variable b could be later on found to be Int.

If later on, b is found to be Int, instance 1 will be picked. If b is instead found to be String, GHC will pick instance 2.

This however requires that there is a "best choice" in each case. I.e. when b = Int, both instances could be used, but 1 is more specific, hence it is the "best", and GHC chooses that.

In the case you have instances

instance ... => Read [a] -- 3
instance ... => Read [a] -- 4

then no best instance exists, since they use the same head Read [a], making the choice to be inherently ambiguous, and GHC rejects that.

What probably confuses you is that GHC will never consider what's on the left of => (the "context") to pick an instance. It does not matter if the instances will not overlap once you consider their context, since no type can satisfy both contexts of 3 and 4. Detecting that the contexts are mutually exclusive is very hard in general, and GHC does not even attempt to do so. Only the instance head (on the right of =>) is considered.

There is no way to create such two default instances which are turned on automatically unless the client defines a more specific one.

I'd prefer to avoid overlaps in instances, and provide a default implementation outside an instance. E.g.

-- library
readPrec_fromStrTag :: StrTag a => Int -> ReadS a
readPrec_fromStrTag = parens $ do
    RP.lift P.skipSpaces
    (RP.lift $ expectShown anyStr) <++ RP.lift g
    where g = do
            Just s@(_:_) <- L.stripPrefix tagPrefix <$> expectTag
            return $ toStr s

If the client wants to use that, they can enable the default implementation using

instance StrTag T where
   ...
instance Read T where
   readPrec = readPrec_fromStrTag  -- use default from the library

If this and other boilerplate is becoming too much burdensome, Template Haskell might help the user in writing that.

Upvotes: 4

Related Questions