Using functions as values in F#

Microsoft F# Fsharp

In my previous post, we discussed about immutable data structure. Now, imagine that we want to write a method similar to SumList but that multiplies the numbers rather than adding them. Making this change looks quite easy: we can copy the SumList method and tinker with it. There are only two changes in the modified method:

int MultiplyList(FuncList<int> numbers) {
if (numbers.IsEmpty) return 1;                        ❶
else return numbers.Head * MultiplyList(numbers.Tail);    ❷

The first change is that we’re using multiplication instead of addition in the branch that does the recursive call ❷. The second change is that the value returned for an empty list is now 1 instead of 0 ❶. This solution works, but copying blocks of code is a bad practice. Instead, we’d like to write a parameterized method or function that can do both adding and multiplying of the list elements depending on the parameters. This allows us to hide the difficult recursive part of the list processing routine in a reusable function, and writing SumList or MultiplyList will become a piece of cake.

The solution is to write a method or a function that takes two arguments: the initial value and the operation that should be performed when aggregating the elements. Let’s see how we can implement this idea in C#.

Passing a Function as an Argument in C#

You’ve seen that in C#, passing a function as an argument can be done using delegates, in particular the Func delegate. The delegate will have two arguments of type int and will return an int as a result. The code shows how we can implement the aggregation as a recursive method that takes a delegate as a parameter.

Adding and multiplying list elements (C#)

Let’s look at the AggregateList method first. It takes the input list to process as the first parameter. The next two parameters specify what should be done with the input. The second parameter is the initial value, which is an integer. It’s used when a list is empty ❶ and we want to return the initial value from the method.

The last parameter is a delegate and is used in the other branch ❷. Here we first recursively calculate the aggregate result for the rest of the list and call the op delegate to calculate the aggregate of that result and the head of the list. In our later examples, it would either add or multiply the given parameters. The delegate type that we’re using here is the generic Func<T1, T2, TResult> delegate from .NET 3.5, which is further discussed in chapter 5. Briefly, it allows us to specify the number and types of the arguments as well as the return type using .NET generics. This means that when we call op ❷ the compiler knows we should provide two integers as arguments and it will return an integer as a result.

Later in the code, we declare two simple methods that are compatible with the delegate type: one for adding two numbers and one for multiplying them. The rest of the code shows how to call the AggregateList method to get the same results as those returned by SumList and MultiplyList in our earlier examples.

Of course, writing the helper methods this way is a bit tedious, because they aren’t used anywhere else in the code. In C# 2.0, you can use anonymous methods to make the code nicer, and in C# 3.0 we have an even more elegant way for writing this code using lambda expressions. Lambda expressions and the corresponding feature in F# (called lambda functions) are used almost everywhere in a real functional code. In the next section, we’re going to look at the last code example in this chapter and see how to implement the same behavior in F#.

Passing a Function as an Argument in F#

The function aggregateList in F# will be quite similar to the method that we’ve already implemented. The important distinction is that F# supports passing functions as arguments to other functions naturally, so we don’t have to use delegates for this.

The function is a special kind of type in F#. Similarly to tuples, the type of a function is constructed from other basic types. With a tuple, the type was specified in code using an asterisk between the types of the elements (e.g., int * string). In the case of functions, the type is specified in terms of the types of arguments and the return type. This provides type safety in the same way delegates do in C#. A function that takes a number and adds 1 to it would be of type int -> int, meaning that it takes an integer and returns an integer. The type of a function that takes two numbers and returns a number would be of type int -> int -> int, and this is exactly the type of the first parameter in our aggregateList function.

Adding and multiplying list elements (F# Interactive)
let rec aggregateList (op:int -> int -> int) init list =
    match list with
    | []       -> init
    | hd::tail ->
        let resultRest = aggregateList op init tail
        op resultRest hd

aggregated. The second parameter is the initial value, and the first one is an F# function. In this example, we wanted to make the function work only with integers to make the code more straightforward, so we added a type annotation for the first parameter. It specifies that the type of the op function is a function taking two integers and returning an integer.

Next we see the familiar pattern for list processing: one branch for an empty list ❶ and one for a cons cell ❷. After entering the code for the aggregateList function in F# Interactive, it prints a signature of the function ❸. This kind of signature may look a bit daunting the first time you see it, but you’ll soon become familiar with it.

The type signature of the aggregateList function in detail. The first argument specifies how two numbers are aggregated, the second is an initial value, and the third is an input list.

Finally, we write two simple functions (add and mul) that both have a signature corresponding to the type of the first parameter of aggregateList and verify that the function works as expected. We wrote these two functions just to make the sample look exactly like the previous C# version, but F# allows us to take any binary operator and work with it as if it were an ordinary function. This means that we don’t need to write the add function and can instead just use the plus symbol directly:

> aggregateList (+) 0 [ 1 .. 5 ];;
val it : int = 15

This feature is often quite helpful, and working with operators makes F# code very succinct. Note that when using an operator in place of a function, we have to enclose it in parentheses, so instead of just writing +, we have to write (+).

You may be thinking that aggregateList isn’t a particularly useful function and that there aren’t many other uses for it other than adding and multiplying elements in a list, but the next section shows one surprising example.

Benefits of parameterized functions

Let’s look at one additional example that will use this function for another purpose—something that at first glance seems very different from adding or multiplying the elements of a list. Let’s see if we can work out the largest value:

> aggregateList max (-1) [ 4; 1; 5; 2; 8; 3 ];;
val it : int = 8

One thought on “Using functions as values in F#

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.