haskellinstanceghci

ghci unexpected behavior instance of class


I've been going through Bryan O'Sullivan's and co's "Real World Haskell," and came across what I would call unexpected 'laxness' on the part of GHCi, version 7.8.3, under Windows. I ":loaded" the following -

module JSONModule   where

data JValue = JNumber Double
           | JBool Bool
             deriving ( Show, Eq, Ord )


class JSON a where
  toJValue :: a -> JValue
  fromJValue :: JValue -> Either String  a


fromJBool (JBool b) = Right b
fromJBool _ = Left "not a JSON boolean"

instance JSON Double where
  toJValue = JNumber
  fromJValue = doubleToJValue id

instance JSON Bool where
  toJValue = JBool
  fromJValue = fromJBool

doubleToJValue :: (Double -> a) -> JValue -> Either String a
doubleToJValue f (JNumber v) = Right (f v)
doubleToJValue _ _ = Left "not a JSON number"

Then, in ghci :

*JSONModule> :r
[1 of 1] Compiling JSONModule       ( JSONModule.hs, interpreted )
Ok, modules loaded: JSONModule.
*JSONModule> toJValue False
JBool False
*JSONModule> fromJValue it
Left "not a JSON number"

While this is true, it is not what would have expected. I think ghci should have told me to fly a kite, as there were 2 instances for fromJValue. Indeed, if I specify

fromJValue it :: Either String Bool

I get Right False. The issue seems to be doubleToJValue. Eliminating the JSON Double instance, and adding a JChar Char constructor to JValue, and a corresponding instance of JSON Char, I get the expected 'ambiguous' response from ghci. So I think there is a bug. Comments? Thanks...


Solution

  • This is not a bug, but a result of the ExtendedDefaultRules extension, which is enabled by default at the GHCi prompt, but not in files.

    Approximately, when a type is otherwise ambiguous and has class constraints of the right form, GHC with this extension will try defaulting it to the first type which fits from (), Integer, Double.

    Without the ExtendedDefaultRules extension, such as in module files by default, defaulting can still happen, but the requirements are stricter (at least one numeric class must be involved, and () isn't tried) and will only apply to a fixed set of classes, not any you define yourself.