Shane Unger
Shane Unger

Reputation: 378

Typeclass behavior in Haskell

As a beginner to Haskell I'm having a hard time understanding why this is failing

-- this works fine of course
f :: Float
f = 1.0

f :: Num a => a
f = 1.0
-- Could not deduce (Fractional a) arising from the literal ‘1.0’
--      from the context: Num a
--        bound by the type signature for:
--                   f :: forall a. Num a => a

My confusion stems from them both having instances of Num. So since Int's, Integers, Doubles, etc all have an instance of the Num typeclass, why can't I stuff any numerical value in f?

For example, negate which has a signature negate :: Num a => a -> a will work with Int's Float's Doubles, etc.

Any insight would be greatly appreciated.

Upvotes: 3

Views: 201

Answers (4)

Will Ness
Will Ness

Reputation: 71119

If you simply write

f = 10.5

you get back f :: Fractional a => a without a problem.

It is when you explicitly claim its type to be Num a => a, Haskell must unify the proclaimed and the actual types, and it can't, since Fractional is Num's subclass:

>> :i Fractional
class Num a => Fractional a where
  --  ^^^                              -- Fractional is Num's subclass
  ...........
  fromRational :: Rational -> a
  ...........

Every Fractional is a Num, but not every Num is a Fractional.


The type of fromRational is fromRational :: Fractional a => Rational -> a.

This suggests that floating point literals like 10.5 are actually read as fromRational (10.5 :: Rational), just like whole number literals like 10 are read as fromInteger (10 :: Integer).

Indeed, section 10.3 in the Haskell tutorial reads:

An integer numeral (without a decimal point) is actually equivalent to an application of fromInteger to the value of the numeral as an Integer. Similarly, a floating numeral (with a decimal point) is regarded as an application of fromRational to the value of the numeral as a Rational. Thus, 7 has the type (Num a) => a, and 7.3 has the type (Fractional a) => a.

Upvotes: 1

chi
chi

Reputation: 116174

why can't I stuff any numerical value in f?

For example, negate which has a signature negate :: Num a => a -> a will work with Int's Float's Doubles, etc.

Precisely because of that!

You can't stuff any numerical value in the definition of f, as you can't stuff any numerical value in the definition of negate.

Suppose we tried to define negate as follows

negate :: Num a => a -> a
negate x = 10.5 - 10.5 - x

Would that work on an Int? That is, on negate 42 :: Int? No, since 10.5 is not an Int.

Since the type of negate promises that it works on any numeric type, including Int, but it does not actually work on Int, then the promise is broken. Static type checking rejects that code because of this.

Similarly, if type checking accepted

f :: Num a => a
f = 10.5

then all of these should work: f + 8 :: Int, f / 2 :: Double, f - 4 :: Integer. But 10.5 does not fit into Int (nor into Integer).

The issue here is that f :: Num a => a allows the caller to choose any numeric type a. Since the type allows the caller to choose, f can not choose itself, but must adapt to any choice made by the caller. So, f can not use code which only works at some numeric types, but not others.

If f only works at Fractional types, a subset of Num types, then the type of f must advertise to the caller that its choice is limited to fractional types. This is done by using f :: Fractional a => a instead.

Upvotes: 2

Lucy Maya Menon
Lucy Maya Menon

Reputation: 1590

The issue here is that when you write f :: Num a => a, this means that f must work for all possible instantiations of a such that a Num a instance exists. In particular, this means that writing (f :: Int) somewhere else should work fine, since instance Num Int certainly exists. However, the value that you wrote for f to return is 1.0, which is not an integer: 1.0 :: Fractional p => p. The error message is basically saying that "Knowing that a is a Num doesn't tell us that a is a Fractional, so there's no way for the fractional literal 1.0 to have type a".

One way to think about it is that the caller gets to choose what a should be: this is why type signatures of this form are called universally quantified.

You may be thinking of existential quantification: f returns some kind of Num, but the "caller" doesn't know what Num was returned. This is often not as useful as universal quantification, and is a little clunky in Haskell, but can be done like this:

{-# LANGUAGE ExistentialQuantification #-} -- This is a GHC extension
-- A SomeNum value contains some kind of Num. Can't see what kind from the "outside".
data SomeNum = forall a. Num a => SomeNum a
f :: SomeNum
f = SomeNum 5.0

In Haskell, a signature like f :: a is syntax for the (conceptually clearer) f :: forall a. a, where the forall is a lot like a "type-level lambda" (and not the f :: exists a. a which you may have been thinking of). In fact, if you look at GHC Core, you will see that all these type applications are explicit: whenever you use a universally quantified function, the "caller" explicitly passes in the types to use for each of the type variables.

However, I would advise that you not try to use existential quantification at this stage: there are often better/easier alternatives. I just wanted to explain it to help show the difference between existentials and universals.

Upvotes: 5

Yann Vernier
Yann Vernier

Reputation: 15887

When you give the compiler a type declaration like f :: Num a => a, you're not just saying f has the typeclass Num but that that's all you know about it here. Since you've entered 1.0, not 1, the compiler concludes that you also need Fractional. Your declaration said that you don't, and so this can't compile. It does compile if you say it's Fractional a => a, and Fractional implies Num.

Upvotes: 0

Related Questions