“Form follows function” – that has been misunderstood. Form and function should be one, joined in a spiritual union.
—Frank Lloyd Wright, Architect
Functions First
One of the things that makes F# a functional-first language is that its functions are “first-class values.”1 But what does that really mean, and how genuinely useful is it? In this chapter, you’ll get the answers to these questions and learn how you can use (and sometimes abuse) this feature to build simple, refactorable code. This is one of those topics where we move quite a way from the familiar ground of Object-Oriented code. So buckle up and enjoy the ride!
Functions as Values
Binding a function to another label
A function as a parameter for another function
In Listing 9-2, the function applyAndPrint has a parameter called f, whose signature is a function that takes two values and returns an integer. In its body, applyAndPrint calls the provided function – whatever it is – and prints the result.
Furthermore, the compiler knows the function must return an integer, because we use its result in a printfn statement that uses a %i format string.
Treating functions as values unlocks a rich store of possibilities for expressing yourself in code. But it comes with the need to understand a few distinctly non-ObjectOriented concepts, which I’ll cover in the next few sections.
Currying and Partial Application
I’m using the terms “curried arguments” and “tupled arguments” here even though, strictly speaking, these are function parameters (in the definition), not arguments (actual values at call time). It just happens that “curried arguments” and “tupled arguments” are the more commonly used, if less, precise phrases.
Partial application
In Listing 9-3, we bind a function called addTwo , which takes one argument and adds it to the constant 2. This is achieved by using (applying) add and providing one argument value: 2. The one “left-over” argument of add is now required by addTwo, and when we supply a value for it (last line of Listing 9-3), an actual calculation is done.
Incidentally, we can only use partial application because the add function’s arguments are curried, not tupled. With tupled arguments, the caller always has to provide the complete tuple.
Another way to think of currying is that every function in F# takes only one parameter. If a function is bound with, say, two (nontupled) parameters, it’s really a function that takes one parameter and returns a function that itself takes the other parameter.
Parenthesizing strings using partial application
In Listing 9-4, we start with a simple function called surround, which does the fundamental work of surrounding a string with two other strings. The surround function has curried arguments, which means we can use partial application to specialize the function in various different ways: roundParen and squareParen parenthesize a string with round and square brackets, respectively; and just to prove that we can use longer surrounding strings, xmlComment surrounds the string with <!-- and -->. We also define a quote function, which uses the same string before and after the input string, again by calling surround. Then we specialize quote further, as doubleQuote, to surround the input string with double quotation marks.
Mixing Tupled and Curried Styles
Mixed tupled and curried styles
Note how all the code in Listing 9-5 has been amended so that prefix and suffix are provided as a single tuple. But there is still partial application going on, for instance, when we define specialized versions like roundParen and quote, where we provide the whole prefix/suffix tuple but no value for the s parameter.
Mixed tupled and curried styles in the wild
It’s also worth saying that many F# developers prefer the curried style even when they don’t intend to use partial application, simply because it means there are fewer brackets to type and match up.
When writing a function, it’s important to think through the order of the curried arguments; which arguments will you want to provide “first,” and which will you want to leave open for specialization?
Function Signatures Revisited
Function signatures and function application
Function signature for tupled arguments
The asterisk in the construct int * int shows that these values are part of a tuple, and the function is expecting a whole tuple, not just two integers that might be provided separately.
It’s worth getting familiar with F#’s way of expressing type signatures for two reasons: they let you verify that a function has the “shape” you expect, and they let you pin down that shape, if you want to, using type hints.
Type Hints for Functions
A function as a parameter for another function
Using type hints to specify function types
Work on the body of newFunction first, and let the compiler work out the type of paramFunction itself based on how it is used (as Listing 9-9).
Specify the signature of the paramFunction in newFunction’s parameter list using a type hint so that the compiler can check that you call paramFunction correctly in newFunction’s body (as Listing 9-10).
The final outcome can be just the same, because usually you can remove the type hint when everything is compiling successfully.
For me, there is no hard and fast rule for which approach to take. I usually start by relying entirely on type inference at first, but if either the compiler or I get confused, I try adding a type hint in case that clarifies matters. I normally try removing the type hint at the end of the process, but I don’t let it ruin my day if type inference can’t work out the signature, which sometimes does happen in otherwise valid code. In those cases, I leave the type hint in and move on. Even then, I often find later that my code elsewhere was imperfect, and when I sort that out, I try again to remove type hints I left in earlier.
All that being said, it can be useful to provide type hints for functions you expect other people to have to call or refer to a lot, for example, the public API of a library you are publishing. This can help readability for people who might not be interested in the nuts and bolts of your code, or who are viewing it outside an IDE.
Functions That Return Functions
Explicitly returning a function
See how in Listing 9-11 we use fun b -> to specify that we want to create a function that takes one argument, which we call b. Since this is the last expression in the definition of add, it is this newly minted function that is returned. Notice also how the type signature of the new add is the same as it was in Listing 9-1. This bears out what I was saying earlier: that you can think of a function with two arguments as only really taking one argument and returning a function that itself requires the remaining argument.
A simple counter using explicit returning of a function
The counter function works by initializing a little bit of mutable state (current) and then returning a function that returns the current value and increments the state. This is a nice way of using, but concealing, some mutable state. (As implemented here, though, I wouldn’t want to warrant that it’s thread safe.)
Hiding a System.Random instance by returning a function
In Listing 9-13, the function we return takes unit (expressed as two round brackets) and uses – but does not expose – a System.Random instance to return a random byte. Although we call randomByte() multiple times, only one System.Random() instance is created. In addition to the data-hiding aspect, this pattern is also useful where it takes significant time to initialize the state within the outer function.
Function Composition
Once we realize that functions are simply values, it’s logical to ask if we can in some way add them together, as we can number values (by adding) or string values (by concatenating). The answer, you won’t be surprised to learn, is “yes.” Let’s imagine you have the task of taking some text and replacing all the directional or typographic quote marks with nondirectional or neutral ones. For example, this text:
“Bob said ‘Hello’,” said Alice.
… would be translated to this:
"Bob said 'Hello'," said Alice. (Note the nondirectional quote marks.)
First cut of removing typographic quotes
There’s nothing inherently wrong with the way Quotes.fixTypographic is defined in Listing 9-14. Indeed, I would often be tempted to leave the code in that state. But there are several alternative ways of expressing the same logic, any of which you may encounter in the wild, and some of which you might even prefer.
Basic function composition
In Listing 9-15, we define a function called fix, which is a combination of fixSingle and fixDouble . When fix is called, fixSingle will be called first (using the input to fix), and its output will be passed to fixDouble. Whatever fixDouble returns will be returned as the result of fixTypographic. Having defined fixTypographic, we then call it by passing the input s into it.
Using a composed function without binding it to a name
This does exactly the same thing as Listing 9-15, but without binding the composed function to an arguably unnecessary token.
Eliminating an unnecessary parameter
It takes a while before one starts automatically recognizing where functions can be composed with >>, rather than pipelined together with |>. But once you start “feeling the force,” there is a temptation to go crazy with function composition. You may even find yourself bending other parts of your code just so that you can use composition. This is often a good thing: functions that are easily composable are often well-designed functions. But also remember: composition isn’t a goal in itself. The principles of motivational transparency and semantic focus trump everything else.
For example, if you use function composition extensively, the reader of your code will have fewer named bindings, like fixTypographic, to give them clues as to what is going on. And in the worst case, if the code has to be debugged, they won’t have a bound value to look at because the composed functions have effectively been put into a black box. Sometimes, code with a few explicitly bound intermediate values is simply more readable and more maintainable. Use function composition with restraint!
Recommendations
Remember the twin concepts of currying (defining parameters as separate, untupled items) and partial application (binding a function value by applying another function giving some, but not all, its curried parameters).
Consider defining the parameters of your function in curried style. It can reduce noise (brackets) and make your functions more flexible to use.
Define curried parameters (more commonly known as curried arguments) in an order that is likely to make partial application by a consumer make the most sense.
Use currying and partial application judiciously to clarify and simplify your code and to eliminate code repetition.
Functions can take other functions as arguments. Exploit this to create beautiful, decoupled code. Remember that you have a choice about whether to specify the signature of the incoming function using a type hint or to allow type inference to infer its signature based on how it is used.
Functions can explicitly return other functions. This can be a great way to get data hiding without classes.
Whenever a function’s input is the same type as another function’s output, they can be composed together using the >> operator. The fact that the functions you have written are composable is a good sign, but that doesn’t mean you have to compose them with >>. You may be sacrificing readability and ease of debugging.
Summary
Coding gurus love to talk about “decoupled code.” But in Object-Oriented languages, functions are still coupled to classes in the form of methods, and parameters are still coupled to one another in the form of tuples. F# sets functions free by making them first-class values, able to be declared independently, called, passed as arguments, returned as results, and composed together; all vastly increasing the expressiveness of the language. In part, this is achieved by using the concept of curried arguments, which can be applied one at a time, with each supplied argument taking us one step closer to an actual computation.
One of the keys to writing stylish F# code is to make good but cautious use of these powers. Above all, don’t always use partial application and composition to reduce your code down to the most concise expression humanly possible. It won’t be readable or maintainable.
In the next chapter, we’ll leave the rarefied world of F# functions and start our journey into performant F# code by looking at asynchronous and parallel programming .
Exercises
Define another function called multiply that multiplies its arguments. Can it be used by applyAndPrint?
What if you want to send in a function to subtract its second input from its first? Is it possible to do this without defining a named function called something like subtract?
Define another function called rangeCounter that returns a function that generates numbers in a circular pattern between a specified range, for example, 3, 4, 5, 6, 3, 4, 5, 6, 3….
You can assume that the provided dataset is nonempty.
The list is nonempty but otherwise can have any length.
You can combine values in a nonempty list using List.reduce.
Remember that there is an F# operator that can combine (compose) two functions into one, providing that the output of the first is compatible with the input of the second.
Exercise Solutions
Since List.reduce is a partial function and raises an exception if the list is empty, the function pipeline list must contain at least one function.