PureScript - Calculate the mean of an array of numbers

Viewed 90

Consider the following PureScript code

-- returns the mean value of the numbers in an array
mean :: Array Number -> Number
mean numbers = 
  -- code goes here

How would one go about writing this function?

The equivalent in JavaScript would look like this:

function mean (numbers) {
  let g = 0;
  for (let n of numbers) {
    g += n;
  }
  return g / numbers.length;
}

But I do not know how would create a local variable g in the mean function or if there is some different, more PureScript oriented way of doing it.

How do you find the mean value of an array of numbers in PureScript?

2 Answers

While you can technically use mutation in PureScript, it's awkward and uncomfortable, and that's on purpose: PureScript intentionally makes it difficult in order to discourage you from it. Mutation is a source of bugs. It's the modern day goto. Avoid where possible.

Instead, use functional transformations.

For example, the mean is the sum of all numbers divided by their number. So just write it down:

mean numbers = sum numbers / toNumber (length numbers)

(of course this will fail for an empty array, let's ignore this fact for now)


Or were you actually asking how to calculate the sum? Or, even more narrowly, how to do the equivalent of the for loop in your JavaScript example?

The ultimate basis of iteration is recursion. The recursive function at each step checks if it needs to continue the iteration or not. If it does, it calls itself again, and if it doesn't, it doesn't.

Singly-linked lists (present in all FP languages) are delightful to iterate over, because their very structure reflects recursive computation. Unfortunately, in PureScript we have to (by default) deal with arrays. Arrays don't have the delightful recursive structure, but you can still iterate using the index as the iteration medium:

sum :: Array Number -> Int -> Number
sum arr startAt = 
  case arr !! startAt of
    Just x -> x + sum arr (startAt + 1)
    Nothing -> 0

Here, at every iteration, I'm checking if the current index is still within the array, while at the same time fetching the element at that index. If it is, it means I need to continue iterating, so I call myself recursively. If it isn't, it means I'm done, and I don't call myself anymore.

Of course, one inconvenience here is that the consumer of such function has to pass an extra parameter startAt. This can be dealt with nicely by hiding the actual recursive function behind a facade:

sum :: Array Number -> Number
sum arr = go 0
  where
    go startAt = 
      case arr !! startAt of
        Just x -> x + go (startAt + 1)
        Nothing -> 0

One thing to look out for with such function is that it is not stack-safe. It will have to perform as many nested calls as there are elements in the array, so if there are too many, it will blow the stack.

To work around that, any recursive algorithm can be turned into what's called tail recursion. This term means writing your recursive function in such a way that every time it calls itself, it doesn't do anything with the return value except returning it straight away. When written this way, recursion can be executed without consuming stack.

For the example above, the way to turn it into tail recursion would be to pass along the "sum so far" as well as the current index:

sum :: Array Number -> Number
sum arr = go 0 0.0
  where
    go startIndex sumSoFar =
      case arr !! startIndex of
        Just x -> go (startIndex + 1) (sumSoFar + x)
        Nothing -> sumSoFar

But in practice explicit recursion is not used very often. I wouldn't say "never" or even "rarely", but it's not the go-to. Instead, we usually use other, simpler functions that are themselves built on recursion.

One example is at the very top of this answer - sum from Data.Foldable. But the most versatile function, one that captures the nature of iteration itself, is fold (coming in two varieties - foldr and foldl)

Using foldl, the sum function could be written like this:

sum :: Array Number -> Number
sum arr = foldl (\x sumSoFar -> x + sumSoFar) 0 arr

Notice how foldl does the whole iteration part for me, and I only have to provide the "heart" of the process - i.e. how to combine the "current element" x with the "state accumulated so far" sumSoFar.

And finally, you could notice that \x y -> x + y is equivalent to just (+) and rewrite it even shorter:

sum :: Array Number -> Number
sum arr = foldl (+) 0 arr

Using sum from Data.Foldable:

import Data.Foldable
import Data.Int (toNumber)
mean :: Array Number -> Number
mean x = sum x / toNumber (length x)
y = mean [1.0, 2.0, 3.0]

Or alternatively, using foldl from the same module, which hints at how to write more generic code:

import Data.Foldable
import Data.Int (toNumber)
mean :: Array Number -> Number
mean x = foldl (+) 0.0 x / toNumber (length x)
y = mean [1.0, 2.0, 3.0]
Related