Haskell : understanding "No instance for" error messages in ghci
Asked Answered
C

2

14

Question 1

Hi, if in WinGHCi I intentionally do the following wrong piece of code :

3 4

Then the error message I get is

<interactive>:1:1:
    No instance for (Num (a0 -> t0))
      arising from the literal `3'
    Possible fix: add an instance declaration for (Num (a0 -> t0))
    In the expression: 3
    In the expression: 3 4
    In an equation for `it': it = 3 4

What exactly does No instance for (Num (a0 -> t0)) mean?

Question 2

Why does the following piece of code :

(+) 2 3 4
<interactive>:1:7:
    No instance for (Num (a0 -> t0))
      arising from the literal `3'
    Possible fix: add an instance declaration for (Num (a0 -> t0))
    In the second argument of `(+)', namely `3'
    In the expression: (+) 2 3 4
    In an equation for `it': it = (+) 2 3 4

yield a slightly different error from the second piece of code :

2+3 4
<interactive>:1:3:
    No instance for (Num (a1 -> a0))
      arising from the literal `3'
    Possible fix: add an instance declaration for (Num (a1 -> a0))
    In the expression: 3
    In the second argument of `(+)', namely `3 4'
    In the expression: 2 + 3 4

Namely in the first piece of code we have No instance for (Num (a0 -> t0)) where as in the second piece of code we have No instance for (Num (a1 -> a0)).


[Response to ehird]

(Questions moved from answer comments) :

1) I appreciate the latter two expressions are different, but are you saying that I should not try to understand why the interpreter chooses (Num (a0 -> t0)) for the former and (Num(a1 -> a0)) for the latter, besides the fact that they are different?

2)Hi, and with the former when you say "But there's no Num instance for functions" what do you mean? Sorry I am not clear on what the concept of an instance is. Furthermore, just out of curiosity, could you use your instance Num (a -> b) method to somehow tell the interpreter to interpret 3 4 as 4 modulo 3?

Cabalism answered 29/4, 2012 at 20:6 Comment(0)
J
16

My intention is to complement ehird's answer with a little bit more explanation. When you wrote the expression

3 4

Then the Haskell interpreter thinks that you are trying to apply the function 3 to whatever 4 is. In order for Haskell to interpret 3 as a function, it needs to make a call to the function

fromInteger :: Integer -> (a -> b)

in order to get a function (i.e. something of type a -> b) from the integer 3. Now, fromInteger is defined in the Num typeclass to have the signature

instance Num x where
    fromInteger :: Integer -> x

i.e. when you make the type x an instance of the Num class, you give an implementation of fromInteger which tells Haskell how to convert an integer literal into an x. In your case, x is the function type a -> b. So let's do it!


First, some boilerplate. To make x an instance of Num Haskell requires that we also make it an instance of Show and Eq:

instance Show (a -> b) where show f = "<function>"
instance Eq (a -> b) where f == g = False

Now let's say we want to interpret 3 4 as "4 modulo 3". Then we need to tell Haskell how to interpret any integer as a function that calls mod. Moreover, since mod only accepts integral types (it has the signature mod :: Integral a => a -> a -> a) then we need to restrict the types of a and b to be integral as well:

instance (Integral a, Integral b) => Num (a -> b) where

To make an instance of Num we need to give implementations of (+), (-), (*) and fromIntegral (actually we should define a couple of other functions too, but let's not worry about that now).

There's a fairly natural way to define addition, subtraction and multiplication (all code from here forms part of the Num instance and should be indented relative to the instance declaration)

    f + g = \x -> f x + g x
    f - g = \x -> f x - g x
    f * g = \x -> f x * g x

i.e. when you add two functions f and g, you get a new function that applies both f and g to its argument, and then adds them together. Since we required that the result of applying f and g was of integral type, we know that it makes sense to add up their outputs.

To interpret an integer as a function we can write

    fromInteger n = \m -> fromIntegral m `mod` fromIntegral n

i.e. when we have an integer n, we return a function of a parameter m that, when called, ensures that both arguments are of the same type (by calling fromIntegral on both of them) and then uses them as arguments to the function mod.

Finally, a bit more boilerplate to stop Haskell complaining:

    abs f = undefined
    signum f = undefined

We can test this out. I have my code in a file called numfun.hs. I boot up the Haskell interpreter and load my file:

Prelude> :l numfun.hs
[1 of 1] Compiling Main             ( numfun.hs, interpreted )
Ok, modules loaded: Main.

Now I can define some functions:

*Main> let f = (+ 1)
*Main> let g = (* 2)

I can add them or subtract them:

*Main> (f + g) 3   -- (3+1) + (3*2)
10
*Main> (f - g) 3   -- (3+1) - (3*2)
-2

And I can call numbers as functions:

*Main> 3 4         -- 4 `mod` 3
1
Jordanna answered 30/4, 2012 at 9:13 Comment(1)
Wow thank you very much for this detailed and well outlined explanation; I really appreciate it. I think I will need to hit some of the books specified on the Haskell website and return to your post a few more times before I digest everything that you have written. Thank you.Cabalism
S
14

The first error happens because an integer literal like 4 can be of any type with a Num instance. That is, 4 has the type (Num a) => a, so it can serve as an Integer, a Double, a Rational, etc. Since you applied 3 to an argument (4), it knows that, in context, 3 must be a function type (i.e. a0 -> t0 for some a0 and t0). But there's no Num instance for functions, so your usage of 3 as a function is invalid. If you added an instance Num (a -> b), it would work, but you probably don't want to.

As for the latter, the two error messages are equivalent; the names generated by GHC have no particular meaning. The letters are usually derived from type variables in the types of functions you're using, and the numbers are appended to keep things unambiguous. In this case, the second expression is equivalent to (+) 2 (3 4) (because function application binds tighter than any infix operator), which isn't quite the same as your first piece of code.

Stephanestephani answered 29/4, 2012 at 20:14 Comment(2)
Hi, I appreciate the latter two expressions are different, but are you saying that I should not try to understand why the interpreter chooses (Num (a0 -> t0)) for the former and (Num(a1 -> a0)) for the latter, besides the fact that they are different? Thanks.Cabalism
Hi, and with the former when you say "But there's no Num instance for functions" what do you mean? Sorry I am not clear on what the concept of an instance is. Furthermore, just out of curiosity, could you use your instance Num (a -> b) method to somehow tell the interpreter to interpret 3 4 as '4 modulo 3'? ThanksCabalism

© 2022 - 2024 — McMap. All rights reserved.