# Area under curve

Posted on

Problem

The following code is a solution to a Hackerrank problem in Haskell. Given a list of polynomial coeficients `a` and exponents `b`, find the area under the polynomial’s curve from `l` to `r` using a step size of `0.001`. Then to find the volume of the curve rotated around the x-axis.

As a newbie to Haskell, I ran into rather frustrating type errors, and I feel like I’ve used hacky solutions to fix them. Specifically, I think there’s a lot on unnecessary code needed to make sure all of the `* + - ^` operators have the same type for left and right parameters.

Any tips on naming conventions would also be appreciated.

``````import Text.Printf (printf)

-- This function should return a list [area, volume].
solve :: Int -> Int -> [Int] -> [Int] -> [Double]
solve l r a b = [
sum [(poly x as bs) / 1000.0 | x <- xs], -- area under curve
sum [pi * (poly x as bs) ^ 2 / 1000.0 | x <- xs] -- volume under rotated curve
]
where
as = map fromIntegral a
bs = map fromIntegral b
ls = fromIntegral l
rs = fromIntegral r
xs = [ls, ls+0.001..rs]

poly :: Double -> [Double] -> [Double] -> Double
poly x a b = sum [fst c * x ** snd c | c <- zip a b]

--Input/Output.
main :: IO ()
main = getContents >>= mapM_ (printf "%.1fn"). ([a, b, [l, r]] -> solve l r a b). map (map read. words). lines
``````

The I/O is witchcraft to me, so I’m not too concerned with reviews of that portion.

Solution

`solve`‘s type is a disaster. I guess its provided by HackerRank, so that’s not a surprise and not your fault. This is also the reason why you get so many type errors. Your `poly` reflects that perfectly: you want to use `Double`, not `Int`.

But while we’re at `poly`, let’s change it. First of all, the coefficients and exponents should stay together in my opinion, so let’s change `poly`‘s type slightly:

``````poly :: [(Double, Double)] -> Double -> Double
poly as x = sum [fst c * x ** snd c | c <- as]
``````

Why `x` as second argument and not as first? Because you usually have a function `f` that is a polynom `f = poly coeffs`. That way you can easily give a name to `poly coeffs` and then only use `f 1`, `f 2` and so on:

``````ghci> let f = poly [(1,2)]
ghci> f 1
1
ghci> f 2
4
ghci> f 3
9
``````

However, `poly` is still not optimal, we can get rid of `fst` and `snd`, and we can relax the types if we want to:

``````poly :: (Num a, Integral n) => [(a, n)] -> a -> a
poly cs x = sum [a * (x ^ b) | (a,b) <- cs]
``````

You can, of course, keep the type to `Double` (you have to change `^` back to `**`).

Now to `solve`. Let’s use proper types first:

``````solve :: Double -> Double -> [(Double,Int)] -> (Double, Double)
``````

We lifted the `Int -> Double` part out of the function. Also, instead of a list, we return a pair. We really want two and only two values as a result. Not arbitrary many, which is modelled with a list. The implementation doesn’t differ too much from yours, apart from the pair and some removed magic numbers:

``````solve l r cs =
( sum [     (p x)     * step | x <- xs]
, sum [pi * (p x) ^ 2 * step | x <- xs]
)
where
p    = poly cs
step = 0.001
xs   = [l,l + step..r]
``````

Remark: You really don’t want to use `1000.0` and `0.001` without a proper name in your code. Later you might notice that you need `step = 0.0005`, and suddenly you have to change all your numbers throughout the code.

# IO can be simple

And now to the last part. Proper IO. Many challenge sites give you an IO skeleton that is rather unusable, to be honest. It’s often somewhat clever, but it really sucks for strict type safe languages. For example the code would be fine in C, since `double + int` is `double`. In Haskell, the type of `+` dictates that both sides need to be of the same type.

To get out of the “I/O [..] witchcraft”, let’s write one that’s easier to understand. First, we write a function to read a line:

``````getListLine :: Read a => IO [a]
getListLine = do
line <- getLine
``````

Then, we use that function to get those three lines, calculate the area and the volume, and then print both values:

``````main :: IO ()
main = do
as     <- getListLine
bs     <- getListLine
[l, r] <- getListLine

let (area, volume) = solve l r (zip as bs)
print area
print volume
``````

This should be a lot easier to understand than the function provided by the challenge site.

# Naming conventions

You call something `xs`, `as`, `bs` or similar if you have a collection. You call something `l'` if it’s based on `l`. So in your original `solve` the usual names would be

``````solve :: Int -> Int -> [Int] -> [Int] -> [Double]
solve l r as bs = [
sum [f x / 1000.0 | x <- xs],
sum [pi * (f x) ^ 2 / 1000.0 | x <- xs]
]
where
as' = map fromIntegral as
bs' = map fromIntegral bs
l'  = fromIntegral l
r'  = fromIntegral r
xs  = [l', l'+0.001..r']
f x = poly x as' bs'
``````