Integral Calculus in Haskell

by Elben Shira

We started learning Haskell in Professor Misra’s class. Since this is the first functional programming language I have been exposed to, I decided to write an integrator.

small_dx = 0.0001

-- integrate a function f from a to b
integrate f a b = integrateGen f a b small_dx 0

-- Generalize the integrate function to take
-- advantage of tail recursion.
-- integrate a function f from x to b
integrateGen f x b dx sum
 | x > b = sum
 | otherwise = integrateGen f (x+dx) b dx (sum + (f x) * dx)

f1 x = 1
f2 x = x**2
f3 x = x * exp (x**2)

-- normal distribution
std = 1
mean = 0
normal x =
  (1/(std*sqrt (2*pi)))*exp(-(x-mean)**2 / (2*std**2))

integrate takes in three parameters, f is a function, and a and b are doubles. If you are familiar with Haskell types, here is the type of integrate:

*Main> :t integrate
integrate :: (Double -> Double) -> Double -> Double -> Double

integrate calls a general version of integrate called integrateGen. integrateGen takes advantage of tail recursion, which allows the compiler to internally convert the recursive function (which would take a lot of stack space) to an iterative function. The dx in integrateGen is the usual calculus dx , and sum holds what we have calculated so far.

The functions we declared are:
f_{1}(x) = 1.
f_{2}(x) = x^{2}.
f_{3}(x) = x \exp{\{x^{2}\}}. where \exp{\{\phi\}} \equiv e^{\phi}.

Now we are ready to test this baby out. Let’s start with an easy one:

*Main> integrate f1 0 5
5.000000000001686

This is pretty close to the actual answer: \int_{0}^{5} f_{1}(x) \ dx = \int_{0}^{5} 1 \ dx = x\bigr\vert^{5}_{0} = 5.

Let’s try a more interesting problem:

*Main> integrate f3 0 1
0.8592768342831555

The true answer is:

\displaystyle\int_{0}^{1} f_{3}(x) \ dx = \int_{0}^{1} x \exp{\{x^{2}\}} \ dx = \frac{\exp{\{x^{2}\}}}{2} \ \biggr\vert^{1}_{0} = \frac{e}{2} - \frac{1}{2} \approx 0.859141.

Next time, we will use this little program to do some useful statistical calculations.

Optimizations

We generalized integrate to take advantage of tail recursion, but for some reason, the GHC interpreter does not figure this out. As a result, we can get stack overflows when we have large limits. So we are forced to compile it with optimizations turned on. First we need to add a main function:

-- replace with whatever you wanted to integrate
main = print (integrate funct2 0 999)

Then we compile it with optimizations turned on:

$ ghc -O --make filename.hs

This is one thing that bugs me about functional programming. Thinking about and writing programs recursively may be more elegant and easier to understand, but then we are putting our trust on the compiler for optimizations, which is scary if we don’t understand how the compiler works. But nevertheless, Haskell is a fun and powerful language, and I can’t wait to do some other cool things with it!