Search code examples
purescript

PureScript - Calculate the mean of an array of numbers


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?


Solution

  • 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