Gentle Introduction to Haskell 98, Online Supplement
Part 19
Covers Sections 10.1, 10.2, 10.3
> module Part19() where
> import Complex
Section: 10 Numbers
Section: 10.1 Numeric Class Structure
Section: 10.2 Constructed Numbers
Here's a brief summary of Haskell numeric classes.
Class Num
Most general numeric class. Has addition, subtraction, multiplication.
Integers can be coerced to any instance of Num with fromInteger.
All integer constants are in this class.
Instances: Int, Integer, Float, Double, Ratio a, Complex a
Class Real
This class contains ordered numbers which can be converted to
rationals.
Instances: Int, Integer, Float, Double, Ratio a
Class Integral
This class deals with integer division. All values in Integral can
be mapped onto Integer.
Instances: Int, Integer
Class Fractional
These are numbers which can be divided. Any rational number can
be converted to a fractional. Floating point constants are in
this class: 1.2 would be 12/10.
Instances: Float, Double, Ratio a
Class Floating
This class contains all the standard floating point functions such
as sqrt and sin.
Instances: Float, Double, Complex a
Class RealFrac
These values can be rounded to integers and approximated by rationals.
Instances: Float, Double, Ratio a
Class RealFloat
These are floating point numbers constructed from a fixed precision
mantissa and exponent.
Instances: Float, Double
There are only a few sensible combinations of the constructed numerics
with built-in types:
Ratio Integer (same as Rational): arbitrary precision rationals
Ratio Int: limited precision rationals
Complex Float: complex numbers with standard precision components
Complex Double: complex numbers with double precision components
The following function works for arbitrary numerics:
> fact :: (Num a) => a -> a
> fact 0 = 1
> fact n = n*(fact (n-1))
Note the behavior when applied to different types of numbers:
> e1 :: Int
> e1 = fact 6
> e2 :: Int
> e2 = fact 20 -- Hugs may not handle overflow gracefully!
> e3 :: Integer
> e3 = fact 20
> e4 :: Rational
> e4 = fact 6
> e5 :: Float
> e5 = fact 6
> e6 :: Complex Float
> e6 = fact 6
Be careful: values like `fact 1.5' will loop.
As a practical matter, Int operations are usually faster than Integer
operations. Also, overloaded functions can be much slower than non-
overloaded functions. Giving a function like fact a precise typing:
fact :: Int -> Int
may yield much faster code.
In general, numeric expressions work as expected. Literals are
a little tricky - they are coerced to the appropriate value. A
constant like 1 can be used as ANY numeric type.
> e7 :: Float
> e7 = sqrt 2
> e8 :: Rational
> e8 = ((4%5) * (1%2)) / (3%4)
> e9 :: Rational
> e9 = 2.2 * (3%11) - 1
> e10 :: Complex Float
> e10 = (2 * (3:+3)) / ((1.1:+2.0) - 1)
> e11 :: Complex Float
> e11 = sqrt (-1)
> e12 :: Integer
> e12 = numerator (4%2)
> e13 :: Complex Float
> e13 = conjugate (4:+5.2)
A function using pattern matching on complex numbers:
> mag :: (RealFloat a) => Complex a -> a
> mag (a:+b) = sqrt (a^2 + b^2)
> e14 :: Float
> e14 = mag (1:+1)
Section: 10.3 Numeric Coercions and Overloaded Literals
The Haskell type system does NOT implicitly coerce values between
the different numeric types! Although overloaded constants are
coerced when the overloading is resolved, no implicit coercion goes
on when values of different types are mixed. For example:
> f :: Float
> f = 1.1
> i1 :: Int
> i1 = 1
> i2 :: Integer
> i2 = 2
All of these expressions would result in a type error (try them!):
> -- g = i1 + f
> -- h = i1 + i2
> -- i3 :: Int
> -- i3 = i2
Appropriate coercions must be introduced by the user to allow
the mixing of types in arithmetic expressions.
> e15 :: Float
> e15 = f + fromIntegral i1
> e16 :: Integer
> e16 = fromIntegral i1 + i2
> e17 :: Int
> e17 = i1 + fromInteger i2 -- fromIntegral would have worked too.
Continued in part20.lhs