2

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.

Community
  • 1
  • 1
Ad N
  • 7,930
  • 6
  • 36
  • 80
  • 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 Oct 30 '13 at 10:22
  • 2
    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 Oct 30 '13 at 10:41
  • @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 Oct 30 '13 at 10:54

1 Answers1

5

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.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • I totally agree with your remark regarding signatures : only the first signature makes sense. The second signature is here just for my educational purpose, allowing me to illustrate the orthogonal problem I stumbled upon. – Ad N Oct 30 '13 at 10:22