ZetCode

F# let bindings

last modified May 3, 2025

In this article, we explore let bindings in F#. The let keyword is fundamental to F# programming, used for binding names to values and functions.

A let binding associates a name with a value or function in F#. By default, let bindings are immutable, meaning once a value is assigned, it cannot be changed. This immutability fosters reliability and helps prevent unintended side effects, making F# code more predictable.

The let keyword supports flexible and expressive declarations, enabling developers to define variables, functions, and even nested bindings in a concise manner. It also allows for pattern matching, enabling the extraction of values from complex data structures like tuples and records.

F# basic let binding

The simplest form of let binding assigns a name to a value.

basic.fsx
let x = 5
let name = "John Doe"
let active = true

printfn "%d" x
printfn "%s" name
printfn "%b" active

We create three simple let bindings for different types of values.

let x = 5

Binds the name x to the integer value 5. The binding is immutable by default.

λ dotnet fsi basic.fsx
5
John Doe
true

F# let binding with functions

Let bindings are used to define functions in F#.

functions.fsx
let square x = x * x
let add a b = a + b
let greet name = $"Hello, {name}!"

printfn "%d" (square 5)
printfn "%d" (add 3 4)
printfn "%s" (greet "John")

We define three functions using let bindings and demonstrate their usage.

let square x = x * x

Defines a function that takes one parameter and returns its square.

λ dotnet fsi functions.fsx
25
7
Hello, John!

F# let binding scopes

Let bindings have lexical scope, visible from their point of declaration.

scopes.fsx
let outer = "I'm outside"

if true then
    let inner = "I'm inside"
    printfn "%s" inner
    printfn "%s" outer

// printfn "%s" inner // This would cause an error
printfn "%s" outer

Demonstrates the scoping rules of let bindings in F#.

let inner = "I'm inside"

This binding is only visible within the if block where it's defined.

λ dotnet fsi scopes.fsx
I'm inside
I'm outside
I'm outside

F# nested let bindings

Let bindings can be nested within other let bindings.

nested.fsx
let calculateTotal price quantity =
    let subtotal = price * quantity
    let tax = subtotal * 0.08m
    subtotal + tax

let total = calculateTotal 25.0m 3
printfn "Total: %M" total

Shows how to use nested let bindings within a function.

let subtotal = price * quantity
let tax = subtotal * 0.08m

These intermediate calculations are only visible within the function.

λ dotnet fsi nested.fsx
Total: 81.00

F# mutable let bindings

Let bindings can be made mutable with the mutable keyword.

mutable.fsx
let mutable counter = 0
printfn "Initial: %d" counter

counter <- counter + 1
printfn "After increment: %d" counter

counter <- counter + 1
printfn "After second increment: %d" counter

Demonstrates mutable let bindings and their modification.

let mutable counter = 0

The mutable keyword allows the binding to be changed after declaration.

λ dotnet fsi mutable.fsx
Initial: 0
After increment: 1
After second increment: 2

F# let bindings with type annotations

Types can be explicitly specified in let bindings.

typed.fsx
let age: int = 34
let name: string = "Roger Roe"
let height: float = 172.5

printfn "%s is %d years old and %.1f cm tall" name age height

Shows let bindings with explicit type annotations.

let age: int = 34

The colon followed by the type specifies the binding's type explicitly.

λ dotnet fsi typed.fsx
Roger Roe is 34 years old and 172.5 cm tall

F# let bindings in pattern matching

Let bindings can be used with pattern matching.

pattern.fsx
let person = ("John", "Doe", 34)

let firstName, lastName, age = person
printfn "Name: %s %s, Age: %d" firstName lastName age

let first, _, _ = person
printfn "First name: %s" first

let _, last, _ = person
printfn "Last name: %s" last

Demonstrates using let bindings with tuple pattern matching.

let firstName, lastName, age = person

Destructures the tuple into individual bindings in one operation.

λ dotnet fsi pattern.fsx
Name: John Doe, Age: 34
First name: John
Last name: Doe

F# let bindings with records

Let bindings work naturally with record types.

records.fsx
type Person = { FirstName: string; LastName: string; Age: int }

let createPerson first last age =
    { FirstName = first; LastName = last; Age = age }

let john = createPerson "John" "Doe" 34
let jane = { FirstName = "Jane"; LastName = "Smith"; Age = 28 }

let { FirstName = f1; LastName = l1 } = john
printfn "%s %s" f1 l1

let { FirstName = f2 } = jane
printfn "%s" f2

Shows let bindings used with record types and record patterns.

let { FirstName = f1; LastName = l1 } = john

Destructures the record into individual bindings using pattern matching.

λ dotnet fsi records.fsx
John Doe
Jane

In this article we've explored the various uses of let bindings in F#. They are a fundamental building block of F# programming, used for variables, functions, and pattern matching.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.