简体   繁体   中英

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.

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 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.

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 Double s, 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.

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 .

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM