F# tips weekly #13: Operators
Using operators like =
, +
, &&
or |>
is daily bread of writing F#. Let's look at how they work under the hood.
Operator is a function
Every operator can be used as standard function by enclosing it in parentheses, for example
(+) 1 2
is the same as
1 + 2
This can be used with currying in place of lambda function. For example, we can write product function like this:
let product xs = xs |> Seq.reduce ((*))
Operator methods
During compilation, operator is replaced with their name - (+)
is changed to op_Addition
. In fact, using named form of operator is valid F# code:
op_Addition 1 2
Full list of operator names can be found in F# specification on page 34.
Unary operators
Alongside the classic binary operators, that works on two arguments, there are also unary operators. These operators are prefix (are used before its argument). One example is ~~~
, operator for bitwise negation.
The most common usage of unary operator is unary variant of -
. When -
is used before number without space, it is considered unary operator and gets translated into (~-)
, so compiler can distinguish between unary and binary variant.
That's the reason why missed space after -
leads to compiler error:
1 -2
-
there is applied as unary operator to 2
.
This behavior is specific only to few specific operators, namely +
, -
, %
, %%
, &
, &&
.
Custom operators
We can define new operators using let (op) = ...
syntax. For example
let (=~) x y = abs (x - y) < 1e-6
defines =~
operator for comparing floats with ignoring rounding errors.
Note that this operator is defined on floats only. Defining generic operators that work on multiple types is possible, but it is complicated and discouraged, because it can lead to puzzling compiler errors.
New operators are also translated into named form, new name is based on character used, so in our case we get op_EqualsTwiddle
.
Redefine operators
We can redefine existing operator by defining a new operator. For example:
let (+) a b = (a + b) % 7
Note that this way we redefined +
to work only on ints, +
on floats ceases to work.
Operators as static members
Another way is to (re)define operator on custom type as static member
:
type Vector2D =
{
X: float
Y: float
}
static member (+) (a: Vector2D, b: Vector2D) = { X = a.X + b.X; Y = a.Y + b.Y }
In this case it is quite easy to write generic operator:
type Vector2D =
{
X: float
Y: float
}
static member (+) (a: Vector2D, b: Vector2D) = { X = a.X + b.X; Y = a.Y + b.Y }
static member (*) (a: Vector2D, b: Vector2D) = { X = a.X * b.X; Y = a.Y * b.Y }
static member (*) (a: Vector2D, b: float) = { X = a.X * b; Y = a.Y * b }
static member
operator methods actually works as extension of operators.
Subscribe to my newsletter
Read articles from Jindřich Ivánek directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Jindřich Ivánek
Jindřich Ivánek
Software developer focused mainly on F#, with a passion for functional programming, problem solving, efficient algorithms and programming languages. I have a strong computer science background, focused mainly in graph theory and graph algorithms; also was intrigued by data structures, complexity, and computability theory. Experienced with backend F# programming. I worked with mathematical optimization (mixed integer programming) and use of statistical methods as well. I contributed to several open source F# projects, mostly around developer tooling.