The type of fromIntegral
is (Num b, Integral a) => a -> b
. I'd like to understand how that's possible, what the code is that can convert any Integral number to any number type as needed.
The actual code for fromIntegral
is listed as
fromIntegral = fromInteger . toInteger
The code for fromInteger
is under instance Num Int
and instance Num Integer
They are respectively:
instance Num Int where
...
fromInteger i = I# (integerToInt i)
and
instance Num Integer where
...
fromInteger x = x
Assuming I#
calls a C program that converts an Integer
to an Int
I don't see how either of these generate results that could be, say, added to a Float
. How do they go from Int
or Integer
to something else?
fromInteger
will be embedded in an expression which requires that it produce a certain type. It can't know what the required type will be? So what happens?
Thanks.
Because fromInteger
is part of the Num
class, every instance will have its own implementation. Neither of the two implementations (for Int
and Integer
) knows how to make a Float
, but they aren't called when you're using fromInteger
(or fromIntegral
) to make a Float
; that's what the Float
instance of Num
is for.
And so on for all other types. There is no one place that knows how to turn integers into any Num
type; that would be impossible, since it would have to support user-defined Num
instances that don't exist yet. Instead when each individual type is declared to be an instance of Num
a way of doing that for that particular type must be provided (by implementing fromInteger
).
fromInteger
will be embedded in an expression which requires that it produce a certain type. It can't know what the required type will be? So what happens?
Actually, knowing what type it's expected to return from the expression the call is embedded in is exactly how it works.
Type checking/inference in Haskell works in two "directions" at once. It goes top-down, figuring out what types each expression should have, in order to fit into the bigger expression it's being used in. And it also goes "bottom-up", figuring out what type each expression should have from the smaller sub-expressions it's built out of. When it finds a place where those don't match, you get a type error (that's exactly where the "expected type" and "actual type" you see in type error messages cone from).
But because the compiler has that top-down knowledge (the "expected type") for every expression, it's perfectly able to figure out that a call of fromInteger
is being used where a Float
is expected, and so use the Float
instance for Num
in that call.
One aspect that distinguishes type classes from OOP interfaces is that type classes can dispatch on the result type of a method, not only on the type of its parameters. The classic example is the read :: Read a => String -> a
function.
fromInteger
has type fromInteger :: Num a => Integer -> a
. The implementation is selected depending on the type of a
. If the typechecker knows that a
is a Float
, the Num
instance of Float
will be used, not the one of Int
or Integer
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With