Explicit Conversion
C and C++ generally allow you to mix and match integers and floating point numbers at will in arithmetic expressions. The operands in an expression are coerced—that is, implicitly converted—to the least general type that can represent all the operand types in the expression. In Haskell, the primary purpose of the type system is to check that there are no logical errors in your code.1 If you use an integer in a context where a floating point number is expected or vice versa, the compiler worries that this is a logical error in your code. So, instead of silently coercing from one number type to the other, it gives you a type error. If you do want to use an integer value in a floating point expression, something that is not uncommon, you must explicitly say so, by converting from integer to floating point number or the other way.
Similarly, you cannot use an Integer
where an Int
is expected or vice
versa. You need to be explicit about the conversion.
To convert from an integral type (Int
or Integer
) to any other number type,
use fromIntegral
:
>>> fromIntegral 3 :: Double
3.0
You can tell that the conversion happened by the fact that the result is
printed with a decimal point. Since I did not use the result of the conversion
in some context from which the compiler could deduce the type to which I want
to convert 3, I had to specify the type I wanted by stating that I want the
result of fromIntegral 3
to be of type Double
.
To convert between Float
and Double
, you have float2Double
and
double2Float
. To use them, you need to import the GHC.Float
module. In
GHCi, you can either use the standard Haskell command import
, which we will
discuss in detail later, or you can use the GHCi command :module
or :m
.
>>> import GHC.Float
>>> float2Double 3.0
3.0
or
>>> :m GHC.Float
>>> float2Double 3.0
3.0
Hmm, did anything happen here? We can't tell. There are two useful little
commands built into GHCi that allow us to gather information about different
values. First, there is :type
or :t
for short. It tells us the type of a
value:
>>> :t float2Double 3.0
float2Double 3.0 :: Double
>>> :t double2Float 3.0
double2Float 3.0 :: Float
This tells us that the result of float2Double 3.0
indeed has type Double
,
and the result of double2Float 3.0
has type Float
, as expected.
The other useful command is :info
or :i
. It provides basic information
about a type, value or function. For a value or function—remember, in Haskell,
functions are just special types of values—this information includes the type
and where it is defined:
>>> :info (+)
class Num a where
(+) :: a -> a -> a
...
-- Defined in ‘GHC.Num’
infixl 6 +
This tells us that the addition operator (+)
is defined in the GHC.Num
module where it is introduced as part of the Num
type class, which I mentioned
before. For any number type a
, we can apply (+)
to two
arguments of type a
and obtain a result that is again of type a
. The last
line can be interesting in some contexts. It tells you that when used in infix
notation, addition is left-associative: the expression 1+2+3
is the same as
(1+2)+3
. The alternative is that it would be right-associative, which would be
expressed with the keyword infixr
. The number after infixl
is the precedence
level of (+)
. Let's have a look at (*)
:
>>> :info (*)
class Num a where
...
(*) :: a -> a -> a
...
-- Defined in ‘GHC.Num’
infixl 7 *
Multiplication is also left-associative, but it has a higher precedence than
addition. Thus, the expression 3 + 4 * 5
is parsed as 3 + (4 * 5)
, not
(3 + 4) * 5
.
To convert from any floating point type to an integral type,
you have a range of different rounding operations available. truncate
rounds
the floating point number towards 0 and returns the resulting integer value.
>>> truncate 3.7
3
>>> truncate (-3.7)
-3
I had to enclose the -3.7
in parentheses here because otherwise, the parser
would have interpreted the expression as trying to subtract 3.7
from
truncate
, which is a function. Since this makes no sense, you get a rather
cryptic error message from GHCi if you try.
floor
rounds down.
>>> floor 3.7
3
>>> floor (-3.7)
-4
ceiling
rounds up.
>>> ceiling 3.7
4
>>> ceiling (-3.7)
-3
round
applies the type of rounding you may have learned in high school. It
rounds towards the closest integer. If the floating point number is
equidistant to two integers (it is a multiple of 1/2), then the
even number closest to the floating point number is returned.
>>> round 2.3
2
>>> round 2.7
3
>>> round 2.5
2
>>> round 3.5
4
Exercise
Use GHCi as a calculator. Evaluate the following arithmetic expressions:
3 * (4 + 5)
3 ^ 2
3 ** 2
4 ** 0.5
4 ^ 0.5
The last one is expected not to work.
Solution
>>> 3 * (4 + 5)
27
>>> 3 ^ 2
9
>>> 3 ** 2
9.0
>>> 4 ** 0.5
2.0
>>> 4 ^ 0.5
<interactive>:21:3: error:
• Could not deduce (Integral b0) arising from a use of ‘^’
from the context: Num a
bound by the inferred type of it :: Num a => a
at <interactive>:21:1-7
The type variable ‘b0’ is ambiguous
These potential instances exist:
instance Integral Integer -- Defined in ‘GHC.Real’
instance Integral Int -- Defined in ‘GHC.Real’
instance Integral Word -- Defined in ‘GHC.Real’
...plus one instance involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the expression: 4 ^ 0.5
In an equation for ‘it’: it = 4 ^ 0.5
<interactive>:21:5: error:
• Could not deduce (Fractional b0) arising from the literal ‘0.5’
from the context: Num a
bound by the inferred type of it :: Num a => a
at <interactive>:21:1-7
The type variable ‘b0’ is ambiguous
These potential instances exist:
instance Fractional Double -- Defined in ‘GHC.Float’
instance Fractional Float -- Defined in ‘GHC.Float’
...plus one instance involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the second argument of ‘(^)’, namely ‘0.5’
In the expression: 4 ^ 0.5
In an equation for ‘it’: it = 4 ^ 0.5
Exercise
Use Hoogle to find out about the (**)
and (^)
operators. Explain why
the expression 4 ^ 0.5
in the previous exercise did not work.
Solution
The (**)
and (^)
operators both express exponentiation. Neither is
more general than the other.
The type of (**)
is Floating a => a -> a -> a
. This means that both
the base and the exponent can be floating point numbers, but they must
be floating point numbers of the same type a
. That's why the
expression 4 ** 0.5
in the previous exercise worked just fine.
The type of (^)
is (Num a, Integral b) => a -> b -> a
. Thus, the
base of the exponential expression can be of any number type a
, and
the exponent of the expression can be of a different number type b
.
Where (^)
is more restrictive than (**)
is that the exponent needs
to be an integral number type. That's why 4 ^ 0.5
in the previous
exercise didn't work; 0.5 is not representable as any integral number
type.
-
I would argue that this is not the primary purpose of types in C or C++. The main resaon why you specify the types of variables in C and C++ is so the compiler knows which machine-level representation of the values you want to use. If the purpose were to check for logical errors in your code, then C and C++ would be more strict about which types can be mixed and matched in expressions, and they would support less automatic coercion. ↩