Generic number type

From HaskellWiki
Revision as of 12:12, 20 June 2007 by Lemming (talk | contribs) (extracted from the Haskell-Cafe discussion)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Problem

Question: Can I have a generic numeric data type in Haskell which covers Integer, Rational, Double and so on, like it is done in scripting languages like Perl and MatLab?

Answer: In principle you can define a type like

data GenericNumber =
    Integer Integer
  | Rational Rational
  | Double Double

and define appropriate instances for Num class et. al. However you will find that it is difficult to implement these methods in a way that is appropriate for each use case. There is simply no type that can emulate the others. Floating point numbers are imprecise - a/b*b=a does not hold in general. Rationals are precise but pi and sqrt 2 are not rational. That is, when using GenericNumbers you will encounter exactly the problems that all scripting language users have encountered so far (or ignored :-).


Solutions

It is strongly advised to carefully check whether a GenericNumber is indeed useful for your application. So let's revisit some examples and their idiomatic solutions in plain Haskell 98.

average

You may find it cumbersome to write

average :: Fractional a => [a] -> a
average xs = sum xs / fromIntegral (length xs)

and you may prefer

average :: [GenericNumber] -> GenericNumber
average xs = sum xs / genericNumberLength xs

with an appropriate implementation of genericNumberLength. However, there is already Data.List.genericLength and you can write

average :: Fractional a => [a] -> a
average xs = sum xs / genericlength xs

ratios

You find it easy to write

1 / 3 :: Rational

but uncomfortable that

1 / floor pi :: Rational

does not work. The first example works, because the numeric literals 1 and 3 are interpreted as rationals itself. The second example fails, because floor always returns an Integral number type, where Rational is not an instance. You should use % instead. This constructs a fraction out of two integers:

1 % 3 :: Rational
1 % floor pi :: Rational


See also