stackoverflowuser
stackoverflowuser

Reputation: 1197

Why does "(1 + 1.0)" have the type "Fractional a => a" and not "Num a => a"?

Since:

Why does 1 + 1.0 have the type Fractional a => a?

This seems strange to me because 1 is not fractional. Only 1.0 is fractional. So, how did 1 turn into fractional and get combined with 1.0 to form a fractional?

Since only Num has the + operator, it would seem more natural to me if 1.0 turned into Num, got combined with 1 to produce a final Num, although that would be strange too, because we would lose information going from 1.0 to 1.

Upvotes: 2

Views: 188

Answers (3)

sepp2k
sepp2k

Reputation: 370357

Every Fractional is a Num, but not every Num is a Fractional. So if we have a Num like 1, it could either be a Fractional (because some Nums are Fractionals) or it could not be. However 1.0 can only be a Fractional, it definitely can't be some other Num like Integer.

So when the compiler sees that you add 1 to a Fractional, it realizes that 1 must be a Fractional as well in this case - otherwise you'd not be allowed to add it to a Fractional.


Here's a similar example that only involves user-defined type classes instead of Nums. Maybe this makes things clearer for you:

class Foo a where
  foo :: a

class Foo a => Bar a where
  bar :: a
  combine :: a -> a -> a

Through the above type classes we now have the following methods:

foo :: Foo a => a
bar :: Bar a => a
combine :: Bar a => a -> a -> a

So now let's try to combine foo and bar like this:

combine foo bar

This is roughly equivalent to you trying to add 1 (of type Num a => a) and 1.0 (of type Fractional a => a) in your example. And just like your example, this works fine and has the type Bar a => a.

Upvotes: 10

chi
chi

Reputation: 116174

  • Number 1 (which technically stands for fromInteger applied to the Integer value 1) belongs to all the types in class Num.
  • All the types in class Fractional belong to class Num as well.

Ergo,

Number 1 belongs to all the types in class Fractional.

Upvotes: 2

leftaroundabout
leftaroundabout

Reputation: 120731

Type classes are very much not like OO classes, this can't be overemphasized.

In particular, “if 1.0 turned into Num” doesn't make any sense. Num is a type class, not a type, so nothing can ever “turn into a Num”. In fact, nothing ever turns into something else at all in Haskell – everything has a concrete type, that is fixed.

Now you ask, how do polymorphic functions work then? Well, it's called parametric polymorphism for a reason: what seems to be an “arbitrary type a” is really a type parameter. Like a function parameter, these aren't variables in the sense that they can ever change their value after the fact, but they are variable in the sense that the caller of the function is allowed to choose any particular “type value” for a – provided it fulfills the type-class constraint.

So in a sense, the literal 1 is a function: it accepts a type argument a, and returns a value 1 :: a. What it requires is that a is in class Num.

Then we have (+) and 1.0, both of which also need that same a argument. (+) again requires Num, nothing new; but 1.0 requires Fractional a. So all in all, 1 + 1.0 is a function that accepts “three copies of” the type argument a, and requires

  • Num a
  • Num a
  • Fractional a – which also requires Num a because Num is superclass of Fractional.

It would be pretty awkward if we actually had to write the type out as

(1 + 1.0) :: (Num a, Num a, Fractional a, Num a) => a

so it is allowed to leave out the redundant constraints, only leaving Fractional a, which implies all the rest. What we can't do is only leave one of the Num constraints, because that would not imply Fractional.

Upvotes: 4

Related Questions