United Kingdom: +44 (0)208 088 8978

Partial application of operators

In this week's post, Matt shows how arguments can be partially applied to operators. He also suggests when it's suitable, and when it's best avoided.

We're hiring Software Developers

Click here to find out more

F# has many operators available as standard, for example the boolean OR operator ||, the arithmetic addition operator + and the less-than comparison operator <. A binary operator (operator of two arguments) is traditionally used in the infix position, that is between its arguments. For example,

  • true || false
  • 2 + 3
  • 3 < 5

However, it is also possible to use one as a function of two arguments by enclosing the operator in parentheses. For example,

  • (||) true false
  • (+) 2 3
  • (<) 3 5

Partial application of operators

Because operators can be used as regular functions, it is possible to partially apply them, like you can with any other F# function. One reason you might want to do this would be to make a pipeline more concise and/or easier to read: compare

coordinates
|> List.filter (fun (x, _) -> x = 0)

with

coordinates
|> List.filter (fst >> (=) 0)

The second snippet can be interpreted almost like English: take the coordinates list and filter it to only those whose first element equals 0.

⚠️ Warning: partial application of non-symmetric operators can be confusing

One thing worth noting in the above example is that fun (x, _) -> x = 0 and fst >> (=) 0 don't call = in exactly the same way. In the first snippet 0 is the second argument to =, whereas in the second snippet it is the (partially-applied) first argument. This doesn't really matter in this case, because = is symmetric (for a given x and y, x = y and y = x always have the same value). However, it is important to note the distinction when partially applying a non-symmetric operator.

For example, it could be easy to read the code below at a glance and interpret the behaviour as "take the coordinates list and filter it to only those whose first element is greater than 0".

coordinates
|> List.filter (fst >> (>) 0)

However, because 0 is applied as the first argument to >, the behaviour is the same as that for the following code

coordinates
|> List.filter (fun (x, _) -> 0 > x)

which makes it easier to see that the code is actually filtering to values whose x-coordinate is less than 0.

My advice would be to avoid partial application of non-symmetric operators. At the very least, be careful.

Summary

Operators wrapped in parentheses can be used as functions. Because F# lets you partially apply functions, this can be useful for making your code more concise. This technique is particularly useful with symmetric operators, but should be used with caution (or avoided altogether) when working with non-symmetric operators.