I'd prefer something slightly more specific, such as instead of

        No instance for (Num String)
          arising from use of `+' at test/error.hs:2:8-17
        Possible fix: add an instance declaration for (Num String)
        In the expression: x + (show s)
        In the definition of `f': f x s = x + (show s)

Maybe
        (+) is defined as Num a => a -> a -> a
The second argument of (+) is of type String at test/ error.hs:2:8-17
        String is not an instance of Num
        In the expression: x + (show s)
        In the definition of `f': f x s = x + (show s)

It seems to me that anybody who gets declaring classes and instance declarations already will be able to figure out pretty quickly what to do if they wanted, e.g., Complex to be an instance of Real with a message like that. The problem is there's too much special-casing otherwise, since its not just Strings but other standard numeric types. For example, if I take "mod $ sqrt n $ m" then I probably don't want to declare an instance of "Floating Int" but just want to use a conversion operator like ceiling. Here's another related thing I ran into below (example simplified):

testErr :: Integral a => a -> [a]
testErr n =
    ceiling $ (exp $ sqrt $ log n) ** (1/2)

testMe.hs:8:14:
    Could not deduce (Floating a) from the context (Integral a)
      arising from use of `**' at testMe.hs:8:14-42
    Possible fix:
      add (Floating a) to the type signature(s) for `testErr'
    In the second argument of `($)', namely
        `(exp $ (sqrt $ (log n))) ** (1 / 2)'
    In the expression: ceiling $ ((exp $ (sqrt $ (log n))) ** (1 / 2))
    In the definition of `testErr':
        testErr n = ceiling $ ((exp $ (sqrt $ (log n))) ** (1 / 2))

What I needed to do here was cast n using realToFrac (or at least I did that and it seemed to be the right decision). But, again, the compiler is suggesting that I declare something that's already an Integral as a Floating, which is conceptually similar to declaring an instance of "Floating Integral" (after all, it implies that such an instance can be/has been declared). Here the possible fix is a great deal more likely to be right, however, so I'm not sure it should be changed, except that a beginner might go and change Integral to Floating when they really *wanted* an Integral for other reasons. The real problem seems to be that the top level expression it returns is pretty huge.

If I remove the " ** (1/2)" then I get a message closer to the one I'd like:
    Could not deduce (Floating a) from the context (Integral a)
      arising from use of `log' at testMe.hs:8:28-32
    Possible fix:
      add (Floating a) to the type signature(s) for `testErr'
    In the second argument of `($)', namely `log n'
    In the second argument of `($)', namely `sqrt $ (log n)'
    In the second argument of `($)', namely `(exp $ (sqrt $ (log n)))'

This seems like something more complicated with how the type- inference system works, and may not be as easily soluble, however. Alternately, it might lead to huge error-stack blowups in more complicated expressions?

Again, relatedly, and now I'm *really* digressing, if I don't fix a type signature for testErr but write it so that it needs conflicting types of n then I get (calling the function from main):

testErr n =
    mod n $ ceiling $ (exp $ sqrt $ log n)

    Ambiguous type variable `a' in the constraints:
      `Integral a' arising from use of `testErr' at testMe.hs:20:26-35
      `RealFrac a' arising from use of `testErr' at testMe.hs:20:26-35
      `Floating a' arising from use of `testErr' at testMe.hs:20:26-35
Probable fix: add a type signature that fixes these type variable (s)

Again, its probably too much to ask of the type-inference system that it catch this type error in parsing testErr itself. And the error message is pretty helpful because if I set a type signature, then it forces me to figure out the conflict. Still, if it could expand with which elements of testErr caused it to infer each type (if there is no explicit signature, there is), then maybe that could be useful?

--S

On Sep 5, 2007, at 4:56 AM, Simon Peyton-Jones wrote:

Is your suggestion specific to String?  E.g. if I wrote

        data Complex = MkC Float Float

        real :: Float -> Complex
        real f = MkC f 0

        f x s = x + real 1

then I really might have intended to use Complex as a Num type, and the suggestion is precisely on target. I'd be interested to know this particular "helpful suggestion" on GHC's part is more misleading than useful. What do others think?

| rephrase to something like "String is not an instance of Num"? For a
| newbie, it may not be clear that Num is the class and String is the
| type.

Good point. Not so easy for multi-parameter type classes! E.g. No instance for (Bar String Int). So we could have

        String is not an instance of class Foo  -- single param
        No instance for (Bar String Int)                -- multi-param

Would that be better (single-param case is easier), or worse (inconsistent)?

Simon

_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Reply via email to