Red_Black
Red_Black

Reputation: 61

Why Num can be treated as Floating in haskell?

I defined a function that computes sqrt and converts argument from and to Integral class:

isqrt :: Integral i => i -> i
isqrt = floor . sqrt . fromIntegral

I don't understand why that compiles. If we write out signatures of the individual functions we get:

fromIntegral :: (Num b, Integral a) => a -> b
sqrt :: Floating a => a -> a
floor :: (RealFrac a, Integral b) => a -> b

So "sqrt" takes something of Floating type but it is supplied with Num. If you take a look at class hierarchy, you can see that Floating "inherits" from Num but not the other way around. I would understand if Floating could be implicitly treated as Num because it is a more "specialized" type. Why this is OK for the compiler ?

Upvotes: 1

Views: 263

Answers (2)

Will Ness
Will Ness

Reputation: 71099

No, sqrt is not supplied with any Num.

Its supplier fromIntegral is able to produce any Num as needed indeed, whereas sqrt demands a Floating as its input. And Floating is a subclass of Num.

So fromIntegral happily obliges. Since it can produce any Num, surely it can produce any type in any subclass of Num. So whatever the specific concrete type it ends up to be, it is in Floating, and thus necessarily it is in Num.

Thus fromIntegral has no problem providing it.

edit: Floating is not a type. It is a class of types. A concrete type might be in Floating class of types. Since Floating is a subclass of Num, such type is also guaranteed to be in Num. It is not something that happens automatically due to "inheritance" that you mention. That "inheritance" i.e. subclass relation is a requirement on that specific type which is Floating to also be (in) Num i.e. to implement Num's methods as well as the ones from Floating.

So yes, a (specific, concrete) Floating type can also be treated as a Num type.

On the other side of things, sqrt produces the same type as its input type, so it will be the same concrete Floating type. But floor expects a RealFrac type.

Observe

> :t floor            ----------
floor :: (Integral b, RealFrac a) => a -> b

> :t sqrt . fromIntegral            ----------
sqrt . fromIntegral :: (Integral a, Floating c) => a -> c

> :t floor . sqrt            ----------  ----------
floor . sqrt :: (Integral c, RealFrac b, Floating b) => b -> c

> :i Floating
class Fractional a => Floating a where
  ....
instance Floating Float
instance Floating Double

> :i RealFrac
class (Real a, Fractional a) => RealFrac a where
  ....
instance RealFrac Float
instance RealFrac Double

So the concrete type produced (and thus, accepted) by that sqrt call must be (in) RealFrac as well as Floating.

Since it is not observed from outside of that application chain it could be any compliant type, and is thus ambiguous; but type defaulting kicks in and selects Double, unless you've changed the defaults.

Upvotes: 3

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477170

So sqrt takes something of Floating type but it is supplied with Num.

No. The fromIntegral guarantees that it can convert any number with a type that is a member of the Integral typeclass to any Num type, not to a Num type.

Since sqrt needs an number that is a member of the Floating typeclass, for that specific case, fromIntegral will specialy to return something of a type that is a member of the Floating typeclass.

Upvotes: 2

Related Questions