TL;DR: it is not the case that Num a => a
is a Num
value, but rather it is a definition that can be a value of any type of Num
, whatever that type is, specifically, as determined by each specific place where it is used.
We define it first, and we use it, later.
And if we've defined it generally, so that it can be used at many different specific types, we can use it later at many different use sites, which will each demand a specific type of value to be provided for it by our definition. As long as that specific type conforms to the type constraints as per the definition and the use site.
That's what being a polymorphic definition is all about.
It is not a polymorphic value. That is a concept from the dynamic world, but ours is a static one. The types in Haskell are not decided at run time. They are known upfront.
Here's what happens:
> numfunc :: Num a => a -> a; numfunc = undefined
> fraval :: Fractional a => a; fraval = undefined
> :t numfunc fraval
numfunc fraval :: Fractional a => a
numfunc
demands its argument to be in Num
. fraval
is a polymorphic definition, able to provide a datum of any type which is in Fractional
as might be demanded of it by a particular use. Whatever it will be, since it's in Fractional
, it's guaranteed to also be in Num
, so it is acceptable by numfunc
.
Since we now know that a
is in Fractional
(because of fraval
), the type of the whole application is now known to be in Fractional
as well (because of the type of numfunc
).
Technically,
fraval :: Fractional a => a -- can provide any Fractional
numfunc :: Num a => a -> a -- is able to process a Num
-------------------------------------------------
numfunc fraval :: (Num a, Fractional a) => a -- can provide a Fractional
And (Num a, Fractional a)
is simplified to the intersection of the type classes, i.e. just Fractional a
.
This of course means that if there's nothing else in the rest of the code somewhere, further specifying the types, we'll get an ambiguous type error (unless some type defaulting kicks in). But there might be. For now this is acceptable, and has a type -- a polymorphic type, meaning, something else will have to further specify it somewhere in the rest of the code, at any particular use site where it appears. So for now, as a general polymorphic definition, this is perfectly acceptable.
Next,
> frafunc :: Fractional a => a -> a; frafunc = undefined
> numval :: Num a => a; numval = undefined
> :t frafunc numval
frafunc numval :: Fractional a => a
frafunc
demands its type to be in Fractional
. numval
is able to provide a datum of whatever type is demanded of it as long as that type is in Num
. So it's perfectly happy to oblige any demand for a Fractional
type of value. Of course something else in the code will have to further specialize the types, but whatever. For now it's all good.
Technically,
numval :: Num a => a -- can provide any Num
frafunc :: Fractional a => a -> a -- is able to process a Fractional
-------------------------------------------------
frafunc numval :: (Num a, Fractional a) => a -- can provide any Fractional
(I post this answer because I think the simplest things can be a stumbling block for beginners, and these simplest things can be taken for granted without even noticing, by the experts. As the saying goes, we don't know who discovered water, but it sure wasn't a fish.)
undefined :: a
, why doesundefined :: Num a => a
work? There are certainlya
s that are notNum
s. – TwoplyPrelude> :info Fractional
which results in:class Num a => Fractional a where […omitted…]
. – Breedlove