Using functions as building blocks
Function composition and mini-languages make code more readable
A well-known principle of good design is to create a set of basic operations and then combine these building blocks in various ways to build up more complex behaviors. In object-oriented languages, this goal gives rise to a number of implementation approaches such as "fluent interfaces", "strategy pattern", "decorator pattern", and so on. In F#, they are all done the same way, via function composition.
Let's start with a simple example using integers. Say that we have created some basic functions to do arithmetic:
Now we want to create new functions that build on these:
The ">>
" operator is the composition operator. It means: do the first function, and then do the second.
Note how concise this way of combining functions is. There are no parameters, types or other irrelevant noise.
To be sure, the examples could also have been written less concisely and more explicitly as:
But this more explicit style is also a bit more cluttered:
In the explicit style, the x parameter and the parentheses must be added, even though they don't add to the meaning of the code.
And in the explicit style, the functions are written back-to-front from the order they are applied. In my example of
add2ThenMult3
I want to add 2 first, and then multiply. Theadd2 >> mult3
syntax makes this visually clearer thanmult3(add2 x)
.
Now let's test these compositions:
Extending existing functions
Now say that we want to decorate these existing functions with some logging behavior. We can compose these as well, to make a new function with the logging built in.
Our new function, mult3ThenSquareLogged
, has an ugly name, but it is easy to use and nicely hides the complexity of the functions that went into it. You can see that if you define your building block functions well, this composition of functions can be a powerful way to get new functionality.
But wait, there's more! Functions are first class entities in F#, and can be acted on by any other F# code. Here is an example of using the composition operator to collapse a list of functions into a single operation.
Mini languages
Domain-specific languages (DSLs) are well recognized as a technique to create more readable and concise code. The functional approach is very well suited for this.
If you need to, you can go the route of having a full "external" DSL with its own lexer, parser, and so on, and there are various toolsets for F# that make this quite straightforward.
But in many cases, it is easier to stay within the syntax of F#, and just design a set of "verbs" and "nouns" that encapsulate the behavior we want.
The ability to create new types concisely and then match against them makes it very easy to set up fluent interfaces quickly. For example, here is a little function that calculates dates using a simple vocabulary. Note that two new enum-style types are defined just for this one function.
The example above only has one "verb", using lots of types for the "nouns".
The following example demonstrates how you might build the functional equivalent of a fluent interface with many "verbs".
Say that we are creating a drawing program with various shapes. Each shape has a color, size, label and action to be performed when clicked, and we want a fluent interface to configure each shape.
Here is an example of what a simple method chain for a fluent interface in C# might look like:
Now the concept of "fluent interfaces" and "method chaining" is really only relevant for object-oriented design. In a functional language like F#, the nearest equivalent would be the use of the pipeline operator to chain a set of functions together.
Let's start with the underlying Shape type:
We'll add some basic functions:
For "method chaining" to work, every function should return an object that can be used next in the chain. So you will see that the "display
" function returns the shape, rather than nothing.
Next we create some helper functions which we expose as the "mini-language", and will be used as building blocks by the users of the language.
Notice that appendClickAction
takes a function as a parameter and composes it with the existing click action. As you start getting deeper into the functional approach to reuse, you start seeing many more "higher order functions" like this, that is, functions that act on other functions. Combining functions like this is one of the keys to understanding the functional way of programming.
Now as a user of this "mini-language", I can compose the base helper functions into more complex functions of my own, creating my own function library. (In C# this kind of thing might be done using extension methods.)
I can then combine these functions together to create objects with the desired behavior.
In the second case, I actually pass two functions to appendClickAction
, but I compose them into one first. This kind of thing is trivial to do with a well structured functional library, but it is quite hard to do in C# without having lambdas within lambdas.
Here is a more complex example. We will create a function "showRainbow
" that, for each color in the rainbow, sets the color and displays the shape.
Last updated
Was this helpful?