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:
- A function takes exactly one input (parameter) and produces one output (result). We can write this as
\(x \mapsto f(x)\ \)
- We can bind a function to an identifier using the let keyword.
let increment x = x + 1
- We can define a function (with or without a binding) by using a lambda
fun x -> x + 1
- A function is applied to the value to its immediate right
increment 3
- A function doesn't have to return a simple value; it can return a function too
let add x = fun y -> x + y
- 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
- 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
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)