Skip to content
Matthew Adams By Matthew Adams Co-Founder
Learning to Program – A Beginners Guide – Part Ten – Getting Started With Operators in F#

Last time, we saw how to define a function in F#.

We're going to build on that this time, so it might be a good idea to go over the key points:

  1. A function takes exactly one input (parameter) and produces one output (result). We can write this as

\(x \mapsto f(x)\ \)

  1. We can bind a function to an identifier using the let keyword.

let increment x = x + 1

  1. We can define a function (with or without a binding) by using a lambda

fun x -> x + 1

  1. A function is applied to the value to its immediate right

increment 3

  1. A function doesn't have to return a simple value; it can return a function too

let add x = fun y -> x + y

  1. Applying 4) and 5) allows us to create functions which appear to take multiple parameters. F# has shorthand syntax to help

let add x y = x + y add 2 3

  1. We can still capture the intermediate function, effectively binding one of its parameters. We call this 'currying'

let add2 = add 2

Finally, we left off with an exercise.

Exercise: Create a function that applies the logical XOR operator we worked out in the previous section.

Remember that we learned that the derived operator XOR can be constructed from AND, OR and NOT operators like this:

\(x \oplus y = (x \lor y) \land \lnot(x \land y)\ \)

You will probably also remember that the F# symbol for the logical AND operator is &&, OR is || and NOT is not. Knowing what we do about functions, we can define a function for the XOR operator.

Spot test: Define a function bound to the identifier xor which implements the XOR operator.

As usual, give it a go yourself before you look at the answer. Check it out in your F# environment.

Answer:

let xor x y = (x || y) && not (x && y)

Try that, and F# responds

Programming C# 12 Book, by Ian Griffiths, published by O'Reilly Media, is now available to buy.

val xor : x:bool -> y:bool -> bool

So, we have defined a function bound to an identifier called xor that takes a boolean, and returns a function that takes a boolean and returns a boolean - our usual pattern for a function that "takes two parameters".

We can now make use of this to build the truth table for XOR, tidying up a loose end from our section on logic.

xor false false val it : bool = false xor true false val it : bool = true xor false true val it : bool = true xor true true val it : bool = false

That's a good start, but it doesn't look quite right. We're calling our xor function in the standard way: applying the function to the value to its right (or prefix syntax). But the similar operators || and && appear between the parameters, which we call infix syntax.

F# provides us with a means of defining a special kind of function called, unsurprisingly, an operator, which works in just this way.

Defining an operator in F#

Defining an operator is just like defining a function - with a couple of little wrinkles.

The first wrinkle is the name - the name has to consist of some sequence of these characters:

!%&*+-./<=>?@^ and |

The second wrinkle is to do with operator precedence. You'll remember in the section on logic that we discussed how multiplication and division take precedence over addition and subtraction, and that logical AND takes precedence over logic OR. The precedence of a custom operator that we define is determined by the characters we use in its identifier. This can be a bit tricky to get used to!

For XOR we want a name that reminds us of the XOR symbol \(\oplus\ \) but which takes the same kind of precedence as OR. Let's use |+|. It has got the pipe characters of OR, along with a plus symbol, so it looks vaguely similar.

So - how do we define an operator? As you might expect, the syntax is very similar to a function:

let ({identifier}) x y = {function body}

And here's how we might define our XOR operator:

let (|+|) x y = (x || y) && not (x && y)

Just like a regular function binding to an identifier, except that we're wrapping the identifier in parentheses (round brackets).

Spot test: What do you think F# will respond?

Answer: This is basically just our standard "two parameter" function pattern, so you'd expect a function that takes a boolean, and returns a function that takes a boolean and returns a boolean. And that's just what we get. Notice that the round brackets are still shown around the identifier.

val ( |+| ) : x:bool -> y:bool -> bool

Now, though, we can try out our infix XOR operator.

true |+| false val it : bool = true true |+| true val it : bool = false

So, now we know how to define functions and operators, and we're armed with a basic knowledge of logic, we can go on to try to solve some more complex problems. But first, a couple of exercises.

Exercise 1: Another derived boolean operator is called the equivalence operator. It is true if the two operands are equal, otherwise it is false. First, draw out the truth table for the equivalence operator. Then, work out a compact boolean expression for it. Finally, implement the equivalence operator as an F# operator.

Hint: What is the relationship between the equivalence operator and the exclusive or operator?

Exercise 2: Remember the exercises in our first introduction to algorithms? Can you implement functions in F# for the sum of an arithmetic series and the sum of a geometric series?

Hint: It is probably useful to know that, in addition to + and -, F# uses / for division and * for multiplication. These are all infix operators. There is also a function called pown which is of the familiar "two parameter" prefix style, and raises one value to the power of another. Here's \(2^3\ \) , for example:

pown 2 3 val it : int = 8

(Answers will be at the start of next week's instalment)

The Introduction to Rx.NET 2nd Edition (2024) Book, by Ian Griffiths & Lee Campbell, is now available to download for FREE.

Matthew Adams on Twitter

Matthew Adams

Co-Founder

Matthew Adams

Matthew was CTO of a venture-backed technology start-up in the UK & US for 10 years, and is now the co-founder of endjin, which provides technology strategy, experience and development services to its customers who are seeking to take advantage of Microsoft Azure and the Cloud.