Haskell LanguageArithmetic

Introduction

In Haskell, all expressions (which includes numerical constants and functions operating on those) have a decidable type. At compile time, the type-checker infers the type of an expression from the types of the elementary functions that compose it. Since data is immutable by default, there are no "type casting" operations, but there are functions that copy data and generalize or specialize the types within reason.

Remarks

The numeric typeclass hierarchy

Num sits at the root of the numeric typeclass hierarchy. Its characteristic operations and some common instances are shown below (the ones loaded by default with Prelude plus those of Data.Complex):

λ> :i Num
class Num a where
  (+) :: a -> a -> a
  (-) :: a -> a -> a
  (*) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a
  {-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
      -- Defined in ‘GHC.Num’
instance RealFloat a => Num (Complex a) -- Defined in ‘Data.Complex’
instance Num Word -- Defined in ‘GHC.Num’
instance Num Integer -- Defined in ‘GHC.Num’
instance Num Int -- Defined in ‘GHC.Num’
instance Num Float -- Defined in ‘GHC.Float’
instance Num Double -- Defined in ‘GHC.Float’

We have already seen the Fractional class, which requires Num and introduces the notions of "division" (/) and reciprocal of a number:

λ> :i Fractional
class Num a => Fractional a where
  (/) :: a -> a -> a
  recip :: a -> a
  fromRational :: Rational -> a
  {-# MINIMAL fromRational, (recip | (/)) #-}
      -- Defined in ‘GHC.Real’
instance RealFloat a => Fractional (Complex a) -- Defined in ‘Data.Complex’
instance Fractional Float -- Defined in ‘GHC.Float’
instance Fractional Double -- Defined in ‘GHC.Float’

The Real class models .. the real numbers. It requires Num and Ord, therefore it models an ordered numerical field. As a counterexample, Complex numbers are not an ordered field (i.e. they do not possess a natural ordering relationship):

λ> :i Real
class (Num a, Ord a) => Real a where
  toRational :: a -> Rational
  {-# MINIMAL toRational #-}
      -- Defined in ‘GHC.Real’
instance Real Word -- Defined in ‘GHC.Real’
instance Real Integer -- Defined in ‘GHC.Real’
instance Real Int -- Defined in ‘GHC.Real’
instance Real Float -- Defined in ‘GHC.Float’
instance Real Double -- Defined in ‘GHC.Float’

RealFrac represents numbers that may be rounded

λ> :i RealFrac
class (Real a, Fractional a) => RealFrac a where
  properFraction :: Integral b => a -> (b, a)
  truncate :: Integral b => a -> b
  round :: Integral b => a -> b
  ceiling :: Integral b => a -> b
  floor :: Integral b => a -> b
  {-# MINIMAL properFraction #-}
      -- Defined in ‘GHC.Real’
instance RealFrac Float -- Defined in ‘GHC.Float’
instance RealFrac Double -- Defined in ‘GHC.Float’

Floating (which implies Fractional) represents constants and operations that may not have a finite decimal expansion.

λ> :i Floating
class Fractional a => Floating a where
  pi :: a
  exp :: a -> a
  log :: a -> a
  sqrt :: a -> a
  (**) :: a -> a -> a
  logBase :: a -> a -> a
  sin :: a -> a
  cos :: a -> a
  tan :: a -> a
  asin :: a -> a
  acos :: a -> a
  atan :: a -> a
  sinh :: a -> a
  cosh :: a -> a
  tanh :: a -> a
  asinh :: a -> a
  acosh :: a -> a
  atanh :: a -> a
  GHC.Float.log1p :: a -> a
  GHC.Float.expm1 :: a -> a
  GHC.Float.log1pexp :: a -> a
  GHC.Float.log1mexp :: a -> a
  {-# MINIMAL pi, exp, log, sin, cos, asin, acos, atan, sinh, cosh,
              asinh, acosh, atanh #-}
      -- Defined in ‘GHC.Float’
instance RealFloat a => Floating (Complex a) -- Defined in ‘Data.Complex’
instance Floating Float -- Defined in ‘GHC.Float’
instance Floating Double -- Defined in ‘GHC.Float’

Caution: while expressions such as sqrt . negate :: Floating a => a -> a are perfectly valid, they might return NaN ("not-a-number"), which may not be an intended behaviour. In such cases, we might want to work over the Complex field (shown later).

Basic examples

λ> :t 1
1 :: Num t => t

λ> :t pi
pi :: Floating a => a

In the examples above, the type-checker infers a type-class rather than a concrete type for the two constants. In Haskell, the Num class is the most general numerical one (since it encompasses integers and reals), but pi must belong to a more specialized class, since it has a nonzero fractional part.

list0 :: [Integer]
list0 = [1, 2, 3]

list1 :: [Double]
list1 = [1, 2, pi]

The concrete types above were inferred by GHC. More general types like list0 :: Num a => [a] would have worked, but would have also been harder to preserve (e.g. if one consed a Double onto a list of Nums), due to the caveats shown above.

`Could not deduce (Fractional Int) ...`

The error message in the title is a common beginner mistake. Let's see how it arises and how to fix it.

Suppose we need to compute the average value of a list of numbers; the following declaration would seem to do it, but it wouldn't compile:

averageOfList ll = sum ll / length ll

The problem is with the division (/) function: its signature is (/) :: Fractional a => a -> a -> a, but in the case above the denominator (given by length :: Foldable t => t a -> Int) is of type Int (and Int does not belong to the Fractional class) hence the error message.

We can fix the error message with fromIntegral :: (Num b, Integral a) => a -> b. One can see that this function accepts values of any Integral type and returns corresponding ones in the Num class:

averageOfList' :: (Foldable t, Fractional a) => t a -> a
averageOfList' ll = sum ll / fromIntegral (length ll)

Function examples

What's the type of (+) ?

λ> :t (+)
(+) :: Num a => a -> a -> a

What's the type of sqrt ?

λ> :t sqrt
sqrt :: Floating a => a -> a

What's the type of sqrt . fromIntegral ?

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