F# and first-class functions, part 4: doing heavy lifting

In previous instalments of this series, I’ve described F#’s support for first-class functions in terms of relatively simple, isolated examples. In this final part, I want to draw on a real-world example to show how F# has made it easy for me to use higher-order functions to address a genuine business requirement.

The project I’ve been working on recently includes a little expression evaluator. Mini-languages of course are meat and potatoes stuff for F#, thanks to the fslex and fsyacc parser tools, but the evaluator has a bunch of behaviours which make life rather more complicated.

For a start, values in the expression language are variants, which have to be coerced to the type of the operation at hand. For example, “2” + 3 should evaluate to 5, because + means numeric addition; whereas “2” & 3 should evaluate to “23”, because & means string concatenation.

For another thing, variants can contain arrays, and the rule is that if you apply an operation or function to an array, it should be performed element-wise across the array, returning a new array. For example, 1 + [2, 3] returns [3, 4], and AVG([1, 2], [3, 4]) returns [2, 3].

Finally, variants can contain errors, and operations and functions on errors should propagate the error. For example, 1 + [1, 2/0, “Bob”] should return [2, Error.DivisionByZero, Error.ExpectedNumeric].

(The rules in the real evaluator are actually rather more complicated than this, but life’s too short.)

Now obviously I could write the coercion, array application and error propagation logic into every operation and function, but that sounds like a really bad plan. Instead, what I’d like to do is write each operation and function as if it worked on scalars of the correct type, and have another function that ‘lifts’ that naive operation or function into the variant world.

Let’s take the numeric addition operation as an example. F#, you’ll be gobsmacked to know, has built-in support for addition via the surprisingly named + operator. Unfortunately, + only operates on good old-fashioned numbers. Let’s see if we can lift it to work on variants according to the rules above.

The first thing we need to do is write a method for coercing variants to numbers. Coercion can fail, so this also means we need to define a “maybe” type (equivalent to Haskell’s Either type):

type Result<'a> =
  | ValidResult of 'a
  | ErrorResult of Error  // Error is a discriminated union -- definition omitted
let coerceToNumeric variant =
  // implementation omitted but returns a Result<float>

Now we can envisage writing the numeric addition operator for scalar, non-error variants something like this:

let addv variant1 variant2 =
  match coerceToNumeric variant1, coerceToNumeric variant2 with
  | ValidResult n1, ValidResult n2 -> NumericValue (n1 + n2)
  | ErrorResult err, _ -> ErrorValue err
  | _, ErrorResult err -> ErrorValue err

(Here we’re using pattern matching to switch between the various different possible outcomes of the type coercion — see here if you’re not familiar with pattern matching. NumericValue and ErrorValue are the type constructors for the variant discriminated union.)

Once I’m confident this works, it’s easy for me to write the variant subtraction function too:

let subv variant1 variant2 =
  match coerceToNumeric variant1, coerceToNumeric variant2 with
  | ValidResult n1, ValidResult n2 -> NumericValue (n1 - n2)
  | ErrorResult err, _ -> ErrorValue err
  | _, ErrorResult err -> ErrorValue err

At this point, of course, all your “Ctrl+V reuse” alarm bells will be going off. But before we spring into action, let’s also write the string concatenation function:

let catv variant1 variant2 =
  match coerceToString variant1, coerceToString variant2 with
  | ValidResult s1, ValidResult s2 -> StringValue (s1 + s2)
  | ErrorResult err, _ -> ErrorValue err
  | _, ErrorResult err -> ErrorValue err

Hmm, this is still eerily similar, but we’re coercing the variants into strings instead of numbers, and wrapping the result as a StringValue instead of a NumericValue. So let’s try to pull out what’s common and what’s varying and create a function that can transform any function into its variant equivalent.

let coerced f coerce uncoerce val1 val2 =
  match coerce val1, coerce val2 with
  | (ValidResult x, ValidResult y) -> uncoerce (f x y)
  | (ErrorResult err, _) -> ErrorValue err
  | (_, ErrorResult err) -> ErrorValue err
let addv = coerced (+) coerceToNumeric NumericValue
let subv = coerced (-) coerceToNumeric NumericValue
let catv = coerced (+) coerceToString StringValue

(Handily, F# allows me to treat an operator as a function by putting parentheses around it.)

We’re not quite there yet, though, because we’re relying on the function we’re lifting always producing a valid result. But the evaluator spec requires functions to be able to report errors: for example, division by zero should return an error variant rather than a numeric NaN the way the F# division operator does. We can easily write a scalar function that provides the behaviour we want by having it return our “maybe” type:

// divide: float -> float -> Result<float>
let divide val1 val2 =
  match val1, val2 with
  | _, 0.0 -> ErrorResult DivisionByZero
  | _ -> ValidResult (val1 / val2)

But now we can’t write this:

let divv = coerced divide coerceToNumeric NumericValue

because NumericValue expects a float, not a Result<float>. We could fix this by writing a custom uncoerce function for divide, but it’s a fair guess that divide won’t be the last function we come across that has to return errors! Instead, it’s better to update the coerced function to make it error-aware:

let coerced f coerce uncoerce val1 val2 =
  match coerce val1, coerce val2 with
  | (ValidResult x, ValidResult y) -> match (f x y) with
                                      | ValidResult r -> uncoerce r
                                      | ErrorResult err -> ErrorValue err
  | (ErrorResult err, _) -> ErrorValue err
  | (_, ErrorResult err) -> ErrorValue err

Now divv works, but addv and the other existing functions complain, because coerced expects f to return a Result<‘a>, and (+) doesn’t. To make coerced happy, we need to give it a version of + that returns a Result, even though that will always be a ValidResult rather than an ErrorResult. But that’s easy with another higher-level function:

let liftToMaybe f x y =
  ValidResult (f x y)
let addv = coerced (liftToMaybe (+)) coerceToNumeric NumericValue
let subv = coerced (liftToMaybe (-)) coerceToNumeric NumericValue
let catv = coerced (liftToMaybe (+)) coerceToString StringValue
let divv = coerced divide coerceToNumeric NumericValue

We can tighten this up a little more, but you probably get the idea. It’s time to move onto error propagation and array operations.

Again, let’s start by looking at how we might transform addv into something that handles errors and arrays:

let addeav variant1 variant2 =
  match variant1, variant2 with
  | (ErrorValue x, _) -> ErrorValue x
  | (_, ErrorValue y) -> ErrorValue y
  | (MultiValue xs, MultiValue ys) -> MultiValue (List.map2 addv x y)
  | (MultiValue xs, _) -> MultiValue (List.map (fun x -> addv x variant2) xs)
  | (_, MultiValue ys) -> MultiValue (List.map (fun y -> addv variant1 y) ys)
  | _ -> addv variant1 variant2

(MultiValue is the case of the variant discriminated union that contains an array of values, represented as a List<Variant>. Representing the contents as a list rather than an array makes it easier for us to use F# list operations and pattern matching to work with the contents. List.map2 is the F# list function for performing a two-argument function element-wise across two lists.)

Looking at the addeav code, it’s completely generic. It deals only with functions between variants, so it doesn’t care about the different types of variants or the fact that the function might fail: that was already handled by when we used coerced to transform the primitive function into a scalar variant one. So here’s our lifting function:

let lifted f val1 val2 =
  match (val1, val2) with
  | (ErrorValue x, _) -> ErrorValue x
  | (_, ErrorValue y) -> ErrorValue y
  | (MultiValue xs, MultiValue ys) -> MultiValue (List.map2 f xs ys)
  | (MultiValue xs, _) -> MultiValue (List.map (fun x -> f x val2) xs)
  | (_, MultiValue ys) -> MultiValue (List.map (fun y -> f val1 y) ys)
  | _ -> f val1 val2
let addv = lifted (coerced (liftToMaybe (+)) coerceToNumeric NumericValue)
let subv = lifted (coerced (liftToMaybe (-)) coerceToNumeric NumericValue)
let divv = lifted (coerced divide coerceToNumeric NumericValue)
let catv = lifted (coerced (liftToMaybe (+)) coerceToString StringValue)

addv, subv, divv and catv will now all propagate errors in a consistent way, and can be applied to arrays. So when we add multiplication support, we barely have to lift a finger:

let mulv = lifted (coerced (liftToMaybe (*)) coerceToNumeric NumericValue)

Looking at these raises an interesting design question: should we combine coerced and lifted? After all, they always seem to be used together. However, it turns out that it’s useful to be able to lift a scalar function of variants to work on arrays too, even if that scalar function didn’t originally come from coercing a primitive operation. An example is bitwise operations. We can’t use coerced for these, because they have to work on both boolean and numeric variants, and they use different operators and different conversions depending on which they received. So I pretty much have to write a new function for bitwise operations on scalar variants:

let bitand_scalar val1 val2 =
  let b1, b2 = coerceToBoolean val1, coerceToBoolean val2
  let n1, n2 = coerceToNumeric val1, coerceToNumeric val2
  match b1, b2, n1, n2 with  // deep breath
  | ValidResult b1, ValidResult b2, _, _ -> BooleanValue (b1 && b2)
  | _, _, ValidResult n1, ValidResult n2 -> NumericValue (float ((int n1) &&& (int n2)))
  | ValidResult true, _, _, ValidResult n2  -> NumericValue n2
  | ValidResult false, _, _, ValidResult n2 -> NumericValue 0.0
  | _, ValidResult true, ValidResult n1, _  -> NumericValue n1
  | _, ValidResult false, ValidResult n1, _ -> NumericValue 0.0
  | _, _, ErrorResult err, _ -> ErrorValue err
  | _, _, _, ErrorResult err -> ErrorValue err

But once I’ve got the scalar version written, I can get the array version for free:

let bitandv = lifted bitand_scalar

So what have we seen? We’ve been able to write two short functions, coerced and lifted, which respectively turn a function on primitive types like floats or strings into a function on scalar variants, and turn a function on scalar variants into a function which works on both scalar and array variants. With those two functions in place, I can write my core evaluation logic in terms of primitive values, or in terms of single-value variants if I really have to. This keeps my core evaluation logic as simple as possible: the logic of the divide function, for example, isn’t swamped by coercion, error propagation and array application concerns. Having written the simple evaluators, I can then create full-fat evaluators with all the behaviour required by the spec just by feeding them through the coerced and/or lifted functions.

As always, there’s nothing here you can’t do in C#, if you’re sufficiently determined or if multiply nested angle brackets fill you with a great joy. What I hope you can see from the above is that F# enables you to build higher-order functions like coerced and lifted, and to work with the results of those functions, just that bit more easily. I used F# for this project because of its parser tools, but over the course of the project I’ve found the simplicity of defining and working with higher-level functions has encouraged and enabled me to tackle repetition and boilerplate that would have been a lot tougher to tackle in C#. Give it a try — you might be surprised!

Previous entries in this series:

Part 1: composition operators

Part 2: point-free style

Part 3: partial function application

Tagged as F#

F# and first-class functions, part 3: partial function application

We ended the previous instalment with a bit of a mystery. To recap, we were using the List.filter and List.map functions — F#’s equivalents of the LINQ Where and Select operators — to create new functions in point-free style:

let except f = List.filter (not << f)

The mystery was that List.filter, like Where, doesn’t take just a predicate: it takes a predicate and a list:

let numerics = List.filter isNumeric someList

So how come calling List.filter with only a predicate doesn’t cause a compiler error?

Tonight we’re gonna program like it’s 1929

List.filter is a bit complicated, so let’s step back and look at a simpler function:

let add x y = x + y

If we look at the type of this function in Intellisense or in F# Interactive, it looks a bit funny. We defined add to take two ints and returns an int. Now F# represents “returns” by the symbol “->” (the same as in F# lambda expressions), so we’d expect the type of add to be displayed as (int, int) -> int or some similar syntax.

What we actually see is something completely different and weird:

val add : int -> int -> int

That looks like a function which takes an int and returns a function from int to int:

               int -> (int -> int)
// Takes an int ^          ^ Returns an int -> int

But that’s crazy talk. Isn’t it? I mean, if we call add with just a single int, that’s obviously invalid, it’ll give us a compiler error, not–

> let inconceivable = add 1;;
val inconceivable : (int -> int)    // You keep using that word. I do not think it means what you think it means.

So add really does work like a function that takes an int and returns a function from int to int. So when we write the normal version, e.g. add 123 456, it’s as if F# did the add 123 to create an anonymous function that adds 123 to things, and then applied that anonymous function to 456. It doesn’t, of course, because that would be awfully inefficient, but that’s an optimisation.

That is, even though it probably isn’t really, in F# you can always think of a function of n arguments as a function of one argument that returns a function of n-1 arguments.

To the mighty bearded ones of computer science, this trick is known as currying, after its inventor, Moses Schoenfinkel.

Creating functions by partial application

Why do F# and other functional programming languages do this weird currying trick? As we’ve seen, currying allows programmers to supply a subset of the required arguments and get a function out, and this turns out to be a terribly convenient way of creating new functions. Let’s go back to the except function:

let except f = List.filter (not << f)          // List.filter takes a predicate
let numerics = List.filter isNumeric someList  // List.filter takes a predicate and a list

It should be a lot clearer now what’s going on. Here’s the signature of List.filter:

> List.filter;;
val it : (('a -> bool) -> 'a list -> 'a list) = <fun:clo@5>

List.filter doesn’t take a predicate and a list after all. It takes a predicate (‘a -> bool), and returns a function that takes a list (‘a list -> ‘a list). In numerics, we immediately applied that function to someList, so it looked like filter was taking a predicate and a list to a list. But in except, we captured the function generated by that first step, and returned that as the result of the except function.

In general, partially applying a function gives us a way to create new functions. Partially applying the add function creates new functions from integers. Partially applying the List.filter function, or other higher-order functions such as List.map, creates new functions from existing functions.

Partial application, precomputing and performance

An interesting side issue with partial application is when you can precompute some internal state used by the function. Consider an expression evaluator, which takes a string expression and evaluates it in the context of an environment which provides values for variables:

let evalFormula ast env =  // ast is the parsed expression AST
  // ... body omitted -- basically recursing down the tree ...
let parse s =  // parse creates an AST from a source string
  let lexbuff = Lexing.from_string s
  Parser.start Lexer.tokenize lexbuff  // magic incantations -- don't worry about how this works!
let eval s env =
  evalFormula (parse s) env

Now suppose you’re going to need to evaluate the same expression with a bunch of different variable assignments. The expression doesn’t change, so it would be a waste of time to call eval for each set of variable assignments — it would parse the same expression string every time and generate the same AST each time. By partially applying evalFormula, we can create a function that has already performed the parse, and can be fed different environments in which to evaluate that parsed expression:

let makeEval s =
  evalFormula (parse s)

When we call makeEval, it parses the expression string. It then partially applies evalFormula to the result of the parsing and returns the resulting function. We can now call that resulting function as many times as we like, passing in different environments. In this example I’m calling makeEval and the resulting evaluator from from C#:

var f = Evaluator.makeEval(expressionText);  // parse happens here
foreach (var variableAssignment in allVariableAssignments)
  var result = f.Invoke(variableAssignment);  // runs efficiently on parsed AST
  // do something with result

A quick test shows that this is significantly faster than using eval and parsing the expression every time.

Return to the valley of C# and Visual Basic

There’s nothing here you couldn’t do in C# or Visual Basic, of course. For example, the F# except function translates pretty easily into C#:

public static Func<IEnumerable<T>, IEnumerable<T>> Except<T>(IEnumerable<T> source, Func<T, bool> predicate)
  return source.Where(x => !predicate(x));

Of course, the entire F# declaration and implementation of except is only one character longer than just the return type of the C# definition, but that’s why we have ergonomic keyboards, right? Right! We can even capture the general concept of partial application in C#:

public static Func<T2, TResult> PartiallyApply<T1, T2, TResult>(Func<T1, T2, TResult> f, T1 val1)
  return val2 => f(val1, val2);
// Usage:
var inconceivable = PartiallyApply(Add, 1);

I don’t think this is as convenient or readable as F#’s built-in support for partial application (particularly when you have to deal with functional cases and overloaded methods, as you would if you wanted to write Except using PartiallyApply), and of course you would need to write overloads of PartiallyApply for every combination of total and provided argument counts. But it’s certainly doable.

Using first-class functions to do the heavy lifting

The simplicity of working with functions in F# makes it easy to assemble low-level functions like List.filter, List.map and so on along with domain methods into convenient little utility functions. As I said before, it’s not that F# allows you to do anything that you can’t do in C# or Visual Basic: rather, it’s that it makes it so much more natural that it becomes idiomatic. So I don’t feel too embarrassed about using examples that are pretty small and simple. Small and simple doesn’t mean useless!

That said, as you need to do more sophisticated things with functions, the simplicity of working with functions in F# starts to pay back more and more. In the final instalment, I’ll talk about a project I’m working on which has made considerable use of first-class functions, and try to show you how F# has made it easy for me to pull common logic and boilerplate code up into higher-order functions that I’ve been able to apply across the project.

Tagged as F#

F# and first-class functions, part 2: point-free style

In the previous instalment, we saw that F# provided composition operators to allow you to concisely build up anonymous functions without the overhead of lambda syntax. Occasionally, however, a function will be so amazingly useful that you actually want to give it a name so you can use it from more than one place.

A soothing lie about F# functions

F#, among its other daring innovations, does indeed allow you to define named functions:

let isNotError v = not (isError v)

Notice that F# uses the let keyword to declare and define functions. That’s the same keyword it uses to declare and assign variables*:

let penguin = "ppog"

The reason for this is simple: a function is conceptually equivalent to a variable which just happens to be of a function type rather than a data type. (The equivalence is conceptual, not physical — there is an implementation difference. But for the time being it’s helpful to think of everything as variables, just some variables of function types and some of data types.)

* Note: F# variables don’t actually vary — they’re immutable values. But I’m going to keep calling them variables anyway.

Introducing point-free style

So our isNotError example is conceptually creating a variable named isNotError, of type Variant -> bool, and assigning the function body to that variable. Effectively, the declaration is equivalent to:

let isNotError = fun v -> not (isError v)  // fun is F#'s keyword for lambdas
// The C# equivalent would be:
// Func<Variant, bool> isNotError = v => !(IsError(v));

But as we discussed before, in this case the lambda is unnecessary verbosity — we should be able to just use a composition operator on the functions directly:

let isNotError = not << isError

And indeed we can! We can define the isNotError function purely in terms of other functions, and their combination using higher-order functions (in this case the << function). This idiom is called point-free style, and is common in functional languages like F# and Haskell which make heavy use of higher-order functions.

Digression: point-free style and language interop

At the implementation level, point-free and conventional declarations do work differently. Consider the following:

let ine1 x = (not << isError) x
let ine2 = (not << isError)

To F# code, these result in the same thing: an “is not error” function of type Variant -> bool. But at the assembly level they’re quite different. ine1 is emitted as a CLR function, with a signature (in C# terms) of public static bool ine1(Variant x). ine2 is emitted as a CLR property, of type FSharpFunc<Variant, bool>. So there is an implementation distinction between variables and functions, and point-free style puts you on the ‘variable’ side of the fence.

Going further with point-free style

The example we’ve been looking at so far is pretty trivial, though. Let’s consider something a bit more realistic. Let’s suppose that I want a function in my variant library which removes all “missing” values from a list of variants. The F# List module contains a filter function, which is pretty much identical to the LINQ Where operator, so we can easily use that:

let present list = List.filter (not << isMissing) list

Here the list argument seems to be doing as much useful work as v was doing in our first example, i.e. sod all. Let’s see what happens when we get rid of it:

let present = List.filter (not << isMissing)

It works! So we’ve been able to create a new function, present, by applying List.filter to another function. In this case the filter parameter was an anonymous function created by the composition operator, but it could just as easily be a named function. Here’s a function to extract numeric values from a list of variants, using the List.map function which is like the LINQ Select operator:

let toNumeric = List.map coerceToNumeric

In fact, the function could even be a parameter. Suppose I want a function to get me everything in a list that doesn’t match a particular predicate?

let except f = List.filter (not << f)  // except: ('a -> bool) -> ('a list -> 'a list)

The except function takes a predicate, f, and returns a function. That function, when applied to a list, returns the members of the list which do not satisfy the predicate. Here’s how we might apply it:

let presentValues = (except isMissing) someList
// or
let presentValues = someList |> except isMissing

Or, of course, instead of applying it to a list, we can save the function we get back as a new named function:

let present = except isMissing  // present: List<Variant> -> bool
// Applying the present function:
let presentValues = present someList

Surely some mistake?

If you’re reading closely, these last examples seem a bit fishy. The List.map function, for example, takes two arguments: a transform, and a list to apply it to. There’s no overload that takes just a transform and returns a function that you can later apply to a list. It’s as though you tried to write in C#:

Func<IEnumerable<Variant>, IEnumerable<double>> present =
  Enumerable.Select(CoerceToNumeric);  // Won't compile

That doesn’t work because Select has no overload that takes only a Func. Similarly for our except function:

public static Func<IEnumerable<T>, IEnumerable<T>> Except<T>(Func<T, bool> f)
  return Enumerable.Where(Compose(Not, f));  // Won't compile

Obviously, this isn’t actually a limitation — you quickly get around it using lambda notation. I’m just putting these in to illustrate that the ability to apply List.map and List.filter to functions doesn’t happen ‘out of the box’ — it’s a F# special.

(I also put them in to illustrate another way F# makes it much easier to work with higher-order functions: not having to write out all those generic type parameters! Even for a simple function like except, the C# declaration is already a lot ‘noisier’ than the F# declaration. That’s because F# infers the argument and return types automatically, as if you could just write the C# var keyword on arguments and methods. It even automatically makes functions generic where possible, as in the case of the except function.)

So what’s going on in F#? How do List.filter and List.map magically manage to return functions when they’re given a function, instead of complaining that they have the wrong number of arguments? The answer is that F# supports a technique called partial function application, and we’ll look at this in more detail in the next instalment.

Tagged as F#

F# and first-class functions, part 1: the composition operator

One of the things people tout about functional programming languages like F# is that they treat functions as first-class values. That is, you can pass functions around just like any other value, perform operations on them just like any other value, and so on.

To a C# programmer, this initially doesn’t sound like a big deal. C# has delegate types, which allow you to pass functions around, perform operations on them, and so on. But C# has very few primitives for actually working with functions. If you’re doing much work with functions, then a functional language such as F# really does pay for itself. In this series, I’m going to mention a few F# features that make higher-order functions just that bit more concise, starting off with F#’s composition operators.

Composing functions in C#

Suppose I’ve got a list that contains error and non-error values (come on, you remember Visual Basic variants), and I want to find the first error. It’s pretty easy in both C# and F#:

values.Find(IsError);  // C#
List.tryFind isError values  // F#

But what if I want to find the first non-error? Well, of course, I could write an IsNonError helper method. But that feels a bit superfluous, a bit of bloat in the API, and I’d have to start sprouting IsNonNumeric and IsNonString and IsNotMissing methods if I had the same issue with other predicates. Combinatoric explosions are rarely good. Fortunately, C# lets us work around this using lambda expressions or anonymous methods, and F# has the equivalent:

values.Find(v => !IsError(v));  // C#
List.tryFind (fun v -> not (isError v)) values  // F# in a C# style

This is okay, but it’s a bit verbose. In order to compose the ! (not) and IsError methods, we’ve had to stop talking about predicates and start talking about values. Wouldn’t it be nice if we could just compose the methods directly?

values.Find(!IsError);  // invalid C#
List.tryFind (not isError) values  // invalid F#

These don’t work because ! and not want to operate on boolean values, not functions. In C#, this leaves us stuffed: the only way to describe how to compose functions is to spell out their action on a value.

The F# composition operators

In F#, however, we can use the composition operator to combine the functions without having to introduce a value for them to act on:

List.tryFind (isError >> not) values  // F#

The >> operator means “the function you get from first doing the thing on the left, then doing the thing on the right.” In our example, that’s the function you get from doing isError, then doing a not on the result of that. In other words, a function that tests if one of our variants is not an error value — just what we needed!

There’s still one little niggle, though, because the natural English order for what we’re describing here is “not isError” rather than “isError not.” Enter the F# reverse composition operator:

List.tryFind (not << isError) values  // F#

This does exactly the same thing as isError >> not was doing, it just allows us to write and — more importantly — read it in a different order.

As a mnemonic, the arrows describe the flow of data through the sequence of composed functions. In the first fragment, each list element will be fed through the chain of functions from left to right: isError will run on the list element, then not will run on the output of isError. In the second fragment, each list element will be fed through the chain from right to left. Which order is more natural depends on the chain of functions — a processing pipeline reads more naturally from left to right (the >> operator) whereas the boolean not reads more naturally from right to left (the << operator). But you could do that in C# as well!

I certainly could! Let’s give it a go:

public static Func<T, R> Compose<T, TInt, R>(Func<T, TInt> f, Func<TInt, R> g)
  return o => f(g(o));
// Usage:
var composed = Compose(Square, Cube);

Hah, so much for C# not supporting function operations. That was easy! Let’s feed it back into the original example:

values.Find(Compose(!, IsError));  // suck on that, F# fan-- wait, what do you mean, "compiler error"?

Not only is the C# Compose method harder to read (consider Compose(Subtract, Divide) — quick, does the subtraction or division happen first?), it doesn’t work, because ! isn’t a function in C#. We could write Compose(b => !b, IsError), of course, but it’s just too depressing.

Where are we?

F#’s composition operators, << and >>, make it easy to stick functions together in a processing or filtering pipeline without having to write a whole new function every time you want to create a new combination. They’re easy to read and avoid the extraneous verbiage of C# lambdas.

The composition operators are a simple example of a way to define a new function in terms of existing functions alone, without having to introduce a formal argument and write the boilerplate of how the functions combine around that formal argument. While you can do this in C#, having it in the language makes for a very concise style. In fact, this is such an important idiom that it has its own name: point-free style. In the next instalment, we’ll look at how point-free style encourages and facilitates abstraction.

Tagged as F#


Join our mailer

You should join our newsletter! Sent monthly:

Back to Top