Well, one way to look at it is that Haskell translates the return type polymorphism that you’re thinking of into parametric polymorphism, using something called the dictionary-passing translation for type classes. (Though this is not the only way to implement type classes or reason about them; it’s just the most popular.)
Basically, fromInteger
has this type in Haskell:
fromInteger :: Num a => Integer -> a
That might be translated internally into something like this:
fromInteger# :: NumDictionary# a -> Integer -> a
fromInteger# NumDictionary# { fromInteger = method } x = method x
data NumDictionary# a = NumDictionary# { ...
, fromInteger :: Integer -> a
, ... }
So for each concrete type T
with a Num
instance, there’s a NumDictionary# T
value that contains a function fromInteger :: Integer -> T
, and all code that uses the Num
type class is translated into code that takes a dictionary as the argument.