2
votes

I started learning Haskell following the recommandations in this answer. So I am just implementing easy list functions, and I stumble onto a difference in the compiler behavior I cannot explain to myself :

-- Impl 1
elementAt :: (Integral b) => [a] -> b -> a
elementAt xs id = xs !! (fromIntegral(id-1))

-- Impl 2
elementAt' :: (Num b) => [a] -> b -> a
elementAt' xs id = xs !! (id-1)

With following signatures :

fromIntegral :: (Integral a, Num b) => a -> b
(!!) :: [a] -> Int -> a

I get an error only for the second implementation, elementAt'.

Could not deduce (b ~ Int)
from the context (Num b)

If I understand correctly, it means that the operator (!!) is expecting an Int instance as its second argument (as seen from the signature), but we only guarantee that the provided parameter is conforming to the Num typeclass (inferred from elemenAt' signature), which is wider than Int.

With that in mind, I do not understand why the first implementation actually works, knowing that fromIntegral also returns an value that is only conforming to the Num typeclass.

1
Note that id is not a great variable name, being already a standard identifier for the identity function. There's nothing wrong with just i.leftaroundabout
@leftaroundabout : thank, my Haskell being less than 24h old I get to learn a lot ; )Ad N
When a function returns a Num a value, what it's really saying is that "Whatever Num a type you need from me, I can provide it." When a function takes a Num a value as an argument, really it's saying, "You can give me any Num a value and in the most difficult of situations I will know what to do to work it all out." That means that when you take a Num a argument, it is your responsibility to work it out. When you call a function that returns a Num a value, it is its responsibility to work it out.kqr
@kqr Thanks, the first concept (regarding a function returning a Num a that could actually return something different depending on the calling context), is what confused me, probably because of my C and C++ background. It has something "templates with return type deduction" to it !Ad N

1 Answers

5
votes

fromIntegral returns any instance of the Num class. I.e., whatever instance you require, it'll know how to produce it. That's the idea of a type variable, it's basically an extra compile-time argument which the caller of the function gets to choose. That's why elemAt works: the compiler knows we need Int, so it tells fromIntegral, which then knows what to do.

However, consequently, if you define a function with signature Num b => ..., you also need to allow the caller put in any type of their choice for b, provided it's in the Num class. In this case, you do not get to ask for the particular instance Int but need to take whatever the caller gives you. That's the difference.

In fact, Num b => [a] -> b -> a is not a signature for which you could validly define this function. How would you index a list by a complex number, or even an infinitely-dimensional matrix or whatever? What you could do is Integral b => [a] -> b -> a.