Beginner

Operators in F#

Learn arithmetic, comparison, logical, and functional operators in F#, including the pipe operator and function composition

Operators in F# are not just symbols sprinkled between values — they are functions in their own right. Because F# is a functional-first language with static, strong, inferred typing, operators carry the same first-class status as named functions: you can pass them around, partially apply them, and define new ones.

This makes operators particularly important in F#. While most languages treat + and - as built-in syntax, F# treats them as functions that happen to use infix notation. That same principle gives rise to the pipe operator (|>) and the composition operator (>>), which are central to idiomatic F# code.

In this tutorial you will see arithmetic, comparison, logical, and bitwise operators, plus the functional operators that make data flow through F# programs feel natural and readable. We will use a single comprehensive script that you can run end-to-end with the .NET SDK Docker image.

Arithmetic Operators

F# supports the usual infix arithmetic operators. Unlike many dynamic languages, F# performs no implicit numeric conversion: adding an int to a float is a type error. You convert explicitly using functions like float or int.

OperatorMeaningExample
+Addition2 + 3
-Subtraction10 - 4
*Multiplication6 * 7
/Division15 / 4
%Modulus (remainder)17 % 5
**Power (floats only)2.0 ** 8.0

Integer division truncates toward zero. To get a float result, at least one operand must be a float.

Comparison and Logical Operators

Comparison operators return a bool and work on any type that supports structural comparison (numbers, strings, tuples, records, discriminated unions). Note that F# uses a single = for equality (not ==) and <> for inequality (not !=).

Logical operators are && (and), || (or), and not as a prefix function. Both && and || short-circuit.

The Pipe and Composition Operators

These two operators define the F# style:

  • |> — forward pipe. x |> f is equivalent to f x, but reads left-to-right.
  • >> — forward composition. f >> g is the function fun x -> g (f x).

Piping is so common that you will see entire programs structured as a sequence of pipes feeding data through transformations. It is the F# answer to method chaining in OOP languages.

A Comprehensive Example

Create a file named operators.fsx:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// Arithmetic operators
let sum = 2 + 3
let diff = 10 - 4
let product = 6 * 7
let intDiv = 15 / 4        // integer division: 3
let remainder = 17 % 5     // modulus: 2
let power = 2.0 ** 8.0     // power requires floats

printfn "Arithmetic:"
printfn "  2 + 3        = %d" sum
printfn "  10 - 4       = %d" diff
printfn "  6 * 7        = %d" product
printfn "  15 / 4       = %d  (integer division truncates)" intDiv
printfn "  17 %% 5       = %d" remainder
printfn "  2.0 ** 8.0   = %.1f" power

// Explicit numeric conversion (no implicit coercion)
let mixed = float 15 / 4.0
printfn "  float 15 / 4.0 = %.2f" mixed

// Comparison operators (= for equality, <> for inequality)
printfn ""
printfn "Comparison:"
printfn "  3 = 3        : %b" (3 = 3)
printfn "  3 <> 4       : %b" (3 <> 4)
printfn "  5 < 10       : %b" (5 < 10)
printfn "  \"abc\" < \"abd\" : %b" ("abc" < "abd")

// Logical operators (short-circuit)
let a, b = true, false
printfn ""
printfn "Logical:"
printfn "  true && false : %b" (a && b)
printfn "  true || false : %b" (a || b)
printfn "  not true      : %b" (not a)

// String concatenation uses + (not ++ or .)
let greeting = "Hello, " + "F#" + "!"
printfn ""
printfn "String concat: %s" greeting

// Bitwise operators on integers
printfn ""
printfn "Bitwise:"
printfn "  6 &&& 3      = %d  (AND)" (6 &&& 3)
printfn "  6 ||| 3      = %d  (OR)"  (6 ||| 3)
printfn "  6 ^^^ 3      = %d  (XOR)" (6 ^^^ 3)
printfn "  1 <<< 4      = %d  (left shift)"  (1 <<< 4)

// The pipe operator: data flows left-to-right
let pipedSum =
    [1; 2; 3; 4; 5]
    |> List.map (fun x -> x * x)
    |> List.filter (fun x -> x > 5)
    |> List.sum

printfn ""
printfn "Pipe result (sum of squares > 5): %d" pipedSum

// Function composition: build a new function from two others
let addOne x = x + 1
let double x = x * 2
let addThenDouble = addOne >> double   // fun x -> double (addOne x)
printfn "addThenDouble 3 = %d" (addThenDouble 3)

// Operators are just functions — you can pass them around
let addFn = (+)                         // the + function as a value
printfn "(+) 4 5 = %d" (addFn 4 5)
let totals = List.reduce (+) [10; 20; 30; 40]
printfn "List.reduce (+) [10;20;30;40] = %d" totals

// Define your own infix operator
let (.+.) x y = (x * 2) + (y * 2)       // custom operator: doubled-sum
printfn "5 .+. 3 (custom) = %d" (5 .+. 3)

// Operator precedence: standard math rules apply
let precedence = 2 + 3 * 4              // 14, not 20
printfn ""
printfn "2 + 3 * 4 = %d" precedence
printfn "(2 + 3) * 4 = %d" ((2 + 3) * 4)

Running with Docker

1
2
3
4
5
# Pull the official .NET SDK image
docker pull mcr.microsoft.com/dotnet/sdk:9.0

# Run the operators script
docker run --rm -v $(pwd):/app -w /app mcr.microsoft.com/dotnet/sdk:9.0 dotnet fsi operators.fsx

The dotnet fsi command launches F# Interactive, which executes the script directly without a separate compile step.

Expected Output

Arithmetic:
  2 + 3        = 5
  10 - 4       = 6
  6 * 7        = 42
  15 / 4       = 3  (integer division truncates)
  17 % 5       = 2
  2.0 ** 8.0   = 256.0
  float 15 / 4.0 = 3.75

Comparison:
  3 = 3        : true
  3 <> 4       : true
  5 < 10       : true
  "abc" < "abd" : true

Logical:
  true && false : false
  true || false : true
  not true      : false

String concat: Hello, F#!

Bitwise:
  6 &&& 3      = 2  (AND)
  6 ||| 3      = 7  (OR)
  6 ^^^ 3      = 5  (XOR)
  1 <<< 4      = 16  (left shift)

Pipe result (sum of squares > 5): 50
addThenDouble 3 = 8
(+) 4 5 = 9
List.reduce (+) [10;20;30;40] = 100
5 .+. 3 (custom) = 16

2 + 3 * 4 = 14
(2 + 3) * 4 = 20

Key Concepts

  • Operators are functions. (+), (*), and even (|>) are normal functions wrapped in parentheses; you can pass them to higher-order functions like List.reduce.
  • Equality uses =, inequality uses <>. Coming from C-family languages, this is the most common F# stumble.
  • No implicit numeric conversion. Mixing int and float is a compile error — use float, int, decimal, etc. to convert explicitly.
  • Integer division truncates. 15 / 4 is 3, not 3.75. Use floats for fractional results.
  • The pipe operator |> is idiomatic F#. It turns nested calls f (g (h x)) into a clear left-to-right pipeline x |> h |> g |> f.
  • Composition >> builds new functions. Use it when you want to name a pipeline rather than apply it immediately.
  • Custom operators are easy. Define let (.+.) x y = ... and you have a new infix operator with the same first-class status as +.
  • Bitwise operators are triple-character (&&&, |||, ^^^) so they cannot be confused with logical && and ||.

Running Today

All examples can be run using Docker:

docker pull mcr.microsoft.com/dotnet/sdk:9.0
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining