ZetCode

F# list comprehensions

last modified May 17, 2025

In this article, we explore list comprehensions in F# - a concise and powerful way to generate lists by transforming and filtering existing collections.

A list comprehension in F# is a syntactic construct for creating lists based on existing lists or sequences. Using the yield keyword within list expressions, we can generate new lists by specifying the elements to include, often with filtering conditions and transformations. List comprehensions provide a declarative way to work with collections that is both readable and expressive.

Basic list comprehensions

List comprehensions are a powerful feature in F# that allow you to create lists from existing lists or sequences. They provide a concise syntax for generating new lists by applying transformations and filtering conditions.

basic.fsx
// Simple range comprehension
let numbers = [1..5]
printfn "Range: %A" numbers

// Explicit list comprehension
let colors = ["red"; "green"; "blue"]
printfn "Colors: %A" colors

// Comprehension with yield
let squares = [for x in 1..5 do yield x * x]
printfn "Squares: %A" squares

// Inline computation
let cubes = [for x in 1..3 -> x * x * x]
printfn "Cubes: %A" cubes

The code demonstrates basic list comprehensions in F#. The first line creates a list of numbers from 1 to 5 using the range operator. The second line creates a list of colors. The third line uses a list comprehension to generate squares of the numbers from 1 to 5. The fourth line uses an inline computation to create a list of cubes.

λ dotnet fsi basic.fsx
Range: [1; 2; 3; 4; 5]
Colors: ["red"; "green"; "blue"]
Squares: [1; 4; 9; 16; 25]
Cubes: [1; 8; 27]

Filtering conditions

List comprehensions can also include filtering conditions to select specific elements from the source list. This allows you to create new lists that meet certain criteria.

filtering.fsx
// Filter even numbers
let evens = [for x in 1..10 do if x % 2 = 0 then yield x]
printfn "Evens: %A" evens

// Multiple conditions
let filtered = [
    for x in 1..20 do
        if x % 2 = 0 && x % 3 = 0 then
            yield x
]
printfn "Divisible by 2 and 3: %A" filtered

// Using when guard
let primes = [
    for x in 2..20 do
        if [for d in 2..x/2 do if x % d = 0 then yield d] = [] then
            yield x
]
printfn "Primes: %A" primes

The code demonstrates filtering in list comprehensions. In the first comprehension, we filter even numbers from 1 to 10. In the second comprehension, we filter numbers that are divisible by both 2 and 3. The third comprehension uses a guard clause to filter prime numbers from 2 to 20.

λ dotnet fsi filtering.fsx
Evens: [2; 4; 6; 8; 10]
Divisible by 2 and 3: [6; 12; 18]
Primes: [2; 3; 5; 7; 11; 13; 17; 19]

Nested list comprehensions

Nested list comprehensions allow you to create lists of lists or perform Cartesian products. They can also be used to flatten nested lists.

nested.fsx
// Cartesian product
let product = [
    for x in 1..3 do
        for y in 1..3 do
            yield (x, y)
]
printfn "Cartesian product: %A" product

// Nested with filtering
let pythagoreanTriples = [
    for a in 1..10 do
        for b in a..10 do
            for c in b..10 do
                if a*a + b*b = c*c then
                    yield (a, b, c)
]
printfn "Pythagorean triples: %A" pythagoreanTriples

// Flattening nested lists
let matrix = [[1; 2; 3]; [4; 5; 6]; [7; 8; 9]]
let flattened = [for row in matrix do for num in row do yield num]
printfn "Flattened matrix: %A" flattened

The example shows how to use multiple generators in list comprehensions. The first comprehension creates a Cartesian product of two ranges. The second comprehension generates Pythagorean triples by checking the condition a2 + b2 = c2. The third comprehension flattens a nested list (matrix) into a single list.

λ dotnet fsi nested.fsx
Cartesian product: [(1, 1); (1, 2); (1, 3); (2, 1); (2, 2); (2, 3); (3, 1); (3, 2); (3, 3)]
Pythagorean triples: [(3, 4, 5); (6, 8, 10)]
Flattened matrix: [1; 2; 3; 4; 5; 6; 7; 8; 9]

F# list comprehension vs sequence expression

List comprehensions can be compared with sequence expressions in F#. While list comprehensions create concrete lists immediately, sequence expressions generate sequences lazily. This means that the elements of a sequence are computed on demand, which can lead to performance benefits when working with large datasets.

comparison.fsx
#time "on"
// List comprehension (eager evaluation)
let listSquares = [for x in 1L..1000000L -> x * x]
printfn "List length: %d" (List.length listSquares)

let listSum = [for x in 1L..1000000L -> x * x] |> List.sum
printfn "List sum: %d" listSum
#time "off"

#time "on"

// Sequence expression (lazy evaluation)
let seqSquares = seq {for x in 1L..1000000L -> x * x}
printfn "Sequence length: %d" (seqSquares |> Seq.length)

let seqSum = seq {for x in 1L..1000000L -> x * x} |> Seq.sum
printfn "Sequence sum: %d" seqSum

#time "off"

The example demonstrates the differences between list and sequence comprehensions. The first part creates a list of squares using a list comprehension and calculates the sum of the squares. The second part creates a sequence of squares using a sequence expression and calculates the sum of the squares. The #time directive is used to measure the time taken for each operation. The list comprehension eagerly evaluates the entire list, while the sequence expression lazily evaluates the elements as needed. This can lead to performance differences, especially for large datasets.

λ dotnet fsi comparison.fsx
List length: 1000000
List sum: 333333833333500000
Real: 00:00:00.084, CPU: 00:00:00.125, GC gen0: 1, gen1: 0, gen2: 0
Sequence length: 1000000
Sequence sum: 333333833333500000
Real: 00:00:00.098, CPU: 00:00:00.093, GC gen0: 0, gen1: 0, gen2: 0

Using pattern matching

Pattern matching is a powerful feature in F# that allows you to destructure and analyze data types. It can be used within list comprehensions to filter and transform elements based on their structure.

pattern_matching.fsx
type Shape =
    | Circle of radius: float
    | Rectangle of width: float * height: float
    | Triangle of _base: float * height: float

let shapes = [
    Circle 5.0
    Rectangle (4.0, 6.0)
    Triangle (3.0, 4.0)
    Circle 2.5
    Rectangle (5.0, 5.0)
]

// Filter and extract using pattern matching
let circles = [
    for shape in shapes do
        match shape with
        | Circle r -> yield r
        | _ -> ()
]
printfn "Circle radii: %A" circles

// Transform with pattern matching
let areas = [
    for shape in shapes do
        match shape with
        | Circle r -> yield System.Math.PI * r * r
        | Rectangle (w, h) -> yield w * h
        | Triangle (b, h) -> yield 0.5 * b * h
]
printfn "Areas: %A" areas

The code defines a discriminated union type Shape with three constructors: Circle, Rectangle, and Triangle. It creates a list of shapes and demonstrates how to use pattern matching within list comprehensions. The first comprehension filters and extracts the radii of circles, while the second comprehension calculates the areas of different shapes using pattern matching.

λ dotnet fsi pattern_matching.fsx
Circle radii: [5.0; 2.5]
Areas: [78.53981634; 24.0; 6.0; 19.63495408; 25.0]

Using lambdas

You can use lambda expressions inside list comprehensions to define custom filtering or transformation logic inline. This example shows how to use a lambda to filter positive numbers from a list.

lambda_in_comprehension.fsx
let vals = [ 1; -2; -3; 4; 5 ]

[ for v in vals do
      let f = fun e -> e > 0
      if f(v) then yield v ] |> printfn "%A"

Here, a lambda fun e -> e > 0 is defined inside the comprehension to check if a value is positive. Only positive values are yielded to the resulting list.

λ dotnet fsi lambda_in_comprehension.fsx
[1; 4; 5]

The yield keyword

The yield keyword is used in list comprehensions to add a single element to the resulting list. Each yield statement produces one item in the output list.

yield_example.fsx
let chars = [ 'a' .. 'z' ]

let res2 = [for e in chars do yield [e; e; e] ]
printfn "%A" res2

let words = [ "sky"; "cloud"; "park"; "rock"; "war" ]

let res = [ for e in words do yield e ]
printfn "%A" res

In the first example, yield adds a list of three repeated characters for each character in the alphabet, resulting in a list of lists. In the second example, yield adds each word as a single element to the resulting list.

λ dotnet fsi yield_example.fsx
[[a; a; a]; [b; b; b]; ...; [z; z; z]]
["sky"; "cloud"; "park"; "rock"; "war"]

The yield! keyword

The yield! keyword is used to add all elements of a collection to the resulting list. It flattens the collection, inserting each element individually into the output list.

yield_bang_example.fsx
let res =
    [ for a in 1..5 do
          yield! [ a .. a + 3 ] ]

printfn "%A" res

let words = [ "sky"; "cloud"; "park"; "rock"; "war" ]

let res2 = [ for e in words do yield! e ]
printfn "%A" res2

In the first example, yield! inserts all elements from the sublist [a .. a + 3] for each a in the range, resulting in a flattened list of numbers. In the second example, yield! inserts each character from every word into the resulting list, producing a list of all characters from all words.

λ dotnet fsi yield_bang_example.fsx
[1; 2; 3; 4; 2; 3; 4; 5; 3; 4; 5; 6; 4; 5; 6; 7; 5; 6; 7; 8]
['s'; 'k'; 'y'; 'c'; 'l'; 'o'; 'u'; 'd'; 'p'; 'a'; 'r'; 'k'; 'r'; 'o'; 'c'; 'k'; 'w'; 'a'; 'r']

In this article, we've explored the power and flexibility of list comprehensions in F#. They provide a concise, declarative way to create and transform lists that is both readable and expressive.

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.