Search code examples
functional-programmingaveragesml

Standard ML : Calculating the average of a given set


I recently had the assignment to calculate the average of a set (given by input) in Standard ML.

The idea is to have a function like below in which you input a list of real numbers and receive the average of those numbers (also a real), such that the terminal gives you this as a return answer when you input the function:

average = fn : real list -> real

We discussed this in a tutorial as well but I wanted to know if there was some sort of trick when creating such functions in Standard ML.

Thanks in advance!


Solution

  • Sum the numbers and divide by the length. A simple recursive sum is typically one of the first examples that you would see in any SML tutorial. You would need to have the empty list basis case of sum evaluate to 0.0 rather than 0 to make sure that the return type is real. Once you define a sum function then you can define average in 1 line using sum and the built in length function. A subtlty is that SML doesn't allow a real to be divided by an int. You could use the conversion function Real.fromInt on the length before dividing the sum by it. There is some inefficiency in passing over the same list twice, once to sum it and once to calculate its length, but there is little reason to worry about such things when you are first learning the language.

    On Edit: Since you have found a natural solution and shared it in the comments, here is a more idiomatic version which computes the average in one pass over the list:

    fun average nums =
        let
            fun av (s,n,[]) = s/Real.fromInt(n)
            |   av (s,n,x::xs) = av (s+x,n+1,xs)
        in
            av (0.0, 0, nums)
        end;
    

    It works by defining a helper function which does the heavy lifting. These are used extensively in functional programming. In the absence of mutable state, a common trick is to explicitly pass as parameters quantities which would be successively modified by a corresponding loop in an imperative language. Such parameters are often called accumulators since they typically accumulate growing lists, running sums, running products, etc. Here s and n are the accumulators, with s the sum of the elements and n the length of the list. In the basis case of (s,n,[]) there is nothing more to accumulate so the final answer is returned. In the non-basis case, (s,n,x::xs), s and n are modified appropriately and passed to the helper function along with the tail of the list. The definition of av is tail-recursive hence will run with the speed of a loop without growing the stack. The only thing that the overall average function needs to do is to invoke the helper function with the appropriate initial values. The let ... helper def ... in ... helper called with start-up values ...end is a common idiom used to prevent the top-level of a program from being cluttered with helper functions.