[Haskell-cafe] Extending the idea of a general Num to other types?

Sterling Clover s.clover at gmail.com
Wed Sep 5 13:43:14 EDT 2007


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



More information about the Haskell-Cafe mailing list