ZetCode

F# sequences

last modified May 17, 2025

In this comprehensive guide, we explore sequences in F# - one of the most powerful and fundamental collection types in the language. Sequences provide lazy evaluation, infinite data structures, and efficient processing of large datasets.

A sequence in F# is a logical series of elements of the same type. Sequences are particularly useful when you have a large, ordered collection of data but don't necessarily need to use all elements. Unlike lists and arrays, sequences are evaluated lazily, meaning elements are computed only when needed. This makes sequences ideal for working with large or infinite datasets.

F# sequence basics

In the first example, we create a simple sequence using the range operator. We can also create explicit sequences using the `seq` keyword. Sequences can be created from arrays, lists, or other collections. The `Seq` module provides functions to manipulate and consume sequences.

basics.fsx
// Range sequence
let numbers = seq { 1..5 }
printfn "Range sequence: %A" numbers

// Explicit sequence
let colors = seq { "red"; "green"; "blue" }
printfn "Color sequence: %A" colors

// Sequence from array
let fromArray = [| 1; 2; 3 |] |> Seq.ofArray
printfn "From array: %A" fromArray

// Consuming sequences
seq { 1..3 } |> Seq.iter (printfn "Number: %d")

// Converting to list
let numberList = seq { 1..5 } |> Seq.toList
printfn "As list: %A" numberList

In the example above, we create a sequence of numbers from 1 to 5 using the range operator. We also create a sequence of colors and convert an array to a sequence. The Seq.iter function is used to consume the sequence, printing each number. Finally, we convert a sequence to a list using Seq.toList.

λ dotnet fsi basics.fsx
Range sequence: seq [1; 2; 3; 4; ...]
Color sequence: seq ["red"; "green"; "blue"]
From array: seq [1; 2; 3]
Number: 1
Number: 2
Number: 3
As list: [1; 2; 3; 4; 5]

F# sequence expressions

F# sequence expressions are a powerful way to generate sequences using comprehensions. They allow you to create sequences using a combination of for, if, and yield keywords. You can use sequence expressions to create complex sequences with filtering and transformation logic.

expressions.fsx
// Simple sequence expression
let squares = seq {
    for n in 1..5 do
        yield n * n
}
printfn "Squares: %A" (squares |> Seq.toList)

// Sequence with filtering
let evens = seq {
    for n in 1..10 do
        if n % 2 = 0 then
            yield n
}
printfn "Evens: %A" (evens |> Seq.toList)

// Nested sequence expression
let coordinates = seq {
    for x in 1..3 do
        for y in 1..3 do
            yield (x, y)
}
printfn "Coordinates: %A" (coordinates |> Seq.toList)

// Yield bang (concatenates sequences)
let combined = seq {
    yield! seq { 1..3 }
    yield! seq { 4..6 }
}
printfn "Combined: %A" (combined |> Seq.toList)

In the example above, we create a sequence of squares using a simple sequence expression. We also create a sequence of even numbers using a filtering condition. The nested sequence expression generates coordinates in a 2D grid. Finally, we use the yield! keyword to concatenate two sequences into one.

λ dotnet fsi expressions.fsx
Squares: [1; 4; 9; 16; 25]
Evens: [2; 4; 6; 8; 10]
Coordinates: [(1, 1); (1, 2); (1, 3); (2, 1); (2, 2); (2, 3); (3, 1); (3, 2); (3, 3)]
Combined: [1; 2; 3; 4; 5; 6]

F# infinite sequences

Infinite sequences are a powerful feature of F#. They allow you to work with potentially unbounded data without consuming memory for all elements at once.

infinite.fsx
// Infinite sequence of natural numbers
let naturals = Seq.initInfinite (fun i -> i + 1)
printfn "First 5 naturals: %A" (naturals |> Seq.take 5 |> Seq.toList)

// Infinite Fibonacci sequence
let rec fibs = seq {
    yield 0
    yield 1
    yield! (Seq.zip fibs (Seq.skip 1 fibs) |> Seq.map (fun (a,b) -> a + b))
}

printfn "First 10 Fibonacci: %A" (fibs |> Seq.take 10 |> Seq.toList)

// Infinite random numbers
let rnd = System.Random()
let randoms = seq {
    while true do
        yield rnd.Next(1, 100)
}

printfn "Random numbers: %A" (randoms |> Seq.take 5 |> Seq.toList)

// Infinite repeating sequence
let repeating = seq {
    while true do
        yield! [1; 2; 3]
}

printfn "Repeating: %A" (repeating |> Seq.take 7 |> Seq.toList)

Infinite sequences can be created using the Seq.initInfinite function. The Fibonacci sequence is generated using recursion and lazy evaluation. The random number generator creates an infinite sequence of random numbers. The repeating sequence demonstrates how to create a sequence that repeats a finite list indefinitely.

λ dotnet fsi infinite.fsx
First 5 naturals: [1; 2; 3; 4; 5]
First 10 Fibonacci: [0; 1; 1; 2; 3; 5; 8; 13; 21; 34]
Random numbers: [42; 17; 89; 3; 76]
Repeating: [1; 2; 3; 1; 2; 3; 1]

F# sequence operations

F# provides a rich set of operations for manipulating sequences. These operations include filtering, mapping, sorting, and aggregating.

operations.fsx
let numbers = seq { 1..10 }

// Filtering
let evens = numbers |> Seq.filter (fun x -> x % 2 = 0)
printfn "Evens: %A" (evens |> Seq.toList)

// Mapping
let squares = numbers |> Seq.map (fun x -> x * x)
printfn "Squares: %A" (squares |> Seq.toList)

// Sorting
let sorted = numbers |> Seq.sortDescending
printfn "Sorted descending: %A" (sorted |> Seq.toList)

// Aggregation
let sum = numbers |> Seq.sum
printfn "Sum: %d" sum

// Chunking
let chunks = numbers |> Seq.chunkBySize 3
printfn "Chunks: %A" (chunks |> Seq.toList)

// Pairwise
let pairs = numbers |> Seq.pairwise
printfn "Pairs: %A" (pairs |> Seq.toList)

// Windowed
let windows = numbers |> Seq.windowed 3
printfn "Windows: %A" (windows |> Seq.take 3 |> Seq.toList)

The example demonstrates various operations on sequences.

λ dotnet fsi operations.fsx
Evens: [2; 4; 6; 8; 10]
Squares: [1; 4; 9; 16; 25; 36; 49; 64; 81; 100]
Sorted descending: [10; 9; 8; 7; 6; 5; 4; 3; 2; 1]
Sum: 55
Chunks: [[|1; 2; 3|]; [|4; 5; 6|]; [|7; 8; 9|]; [|10|]]
Pairs: [(1, 2); (2, 3); (3, 4); (4, 5); (5, 6); (6, 7); (7, 8); (8, 9); (9, 10)]
Windows: [[|1; 2; 3|]; [|2; 3; 4|]; [|3; 4; 5|]]

F# Sequence Performance

Performance characteristics of sequences compared to lists and arrays are important. Sequences are lazy and can be more memory efficient than lists and arrays. However, they may have performance overhead due to lazy evaluation.

To accurately compare performance, list and sequence evaluations are placed in separate files with independent timing. The list_operations.fsx file evaluates lists eagerly, while sequence_operations.fsx measures lazy sequence execution.

list_operations.fsx
#time "on"

open System.Diagnostics

let stopwatch = Stopwatch.StartNew()

let listData = [1L..1000000L]
let listProcessed = listData |> List.map (fun x -> x * x)
let listSum = listProcessed |> List.sum

stopwatch.Stop()

printfn "List sum: %d (Time: %A ms)" listSum stopwatch.ElapsedMilliseconds

#time "off"

The list_operations.fsx file demonstrates eager evaluation of lists. It creates a list of numbers from 1 to 1,000,000, processes them by squaring each number, and calculates the sum. The execution time is measured using a stopwatch.

sequence_operations.fsx
#time "on"

open System.Diagnostics

let stopwatch = Stopwatch.StartNew()

let seqData = seq {1L..1000000L}
let seqProcessed = seqData |> Seq.map (fun x -> x * x)
let seqSum = seqProcessed |> Seq.sum

stopwatch.Stop()

printfn "Sequence sum: %d (Time: %d ms)" seqSum stopwatch.ElapsedMilliseconds

#time "off"

The sequence_operations.fsx file demonstrates lazy evaluation of sequences. It creates a sequence of numbers from 1 to 1,000,000, processes them by squaring each number, and calculates the sum.

λ dotnet fsi list_operations.fsx
List sum: 333333833333500000 (Time: 83 ms)
Real: 00:00:00.089, CPU: 00:00:00.171, GC gen0: 1, gen1: 0, gen2: 0

λ dotnet fsi sequence_operations.fsx
Sequence sum: 333333833333500000 (Time: 16 ms)
Real: 00:00:00.022, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0

F# sequence modules

The Seq module provides a variety of functions for working with sequences, including filtering, mapping, folding, and more. These functions are designed to be efficient and can be used to process large datasets without loading them into memory all at once.

modules.fsx
let numbers = seq { 1..10 }

// Seq.exists - checks if any element satisfies condition
let hasEven = numbers |> Seq.exists (fun x -> x % 2 = 0)
printfn "Has even numbers: %b" hasEven

// Seq.fold - accumulation
let product = numbers |> Seq.fold (fun acc x -> acc * x) 1
printfn "Product: %d" product

// Seq.groupBy - grouping elements
let grouped = numbers |> Seq.groupBy (fun x -> x % 3)
printfn "Grouped by mod 3: %A" (grouped |> Seq.toList)

// Seq.distinct - removing duplicates
let withDups = seq { 1; 2; 2; 3; 4; 4; 4; 5 }
let distinct = withDups |> Seq.distinct
printfn "Distinct: %A" (distinct |> Seq.toList)

// Seq.skip/take - pagination
let page1 = numbers |> Seq.skip 0 |> Seq.take 3
let page2 = numbers |> Seq.skip 3 |> Seq.take 3
printfn "Page 1: %A" (page1 |> Seq.toList)
printfn "Page 2: %A" (page2 |> Seq.toList)

In this example, we demonstrate various operations on sequences using the Seq module. The Seq.exists function checks if any elements in the sequence satisfy a given condition. The Seq.fold function accumulates values in the sequence, allowing us to compute a product of all elements. The Seq.groupBy function groups elements based on a given key, while Seq.distinct removes duplicates from the sequence. Finally, we use Seq.skip and Seq.take to implement pagination, allowing us to retrieve specific pages of elements from the sequence.

λ dotnet fsi modules.fsx
Has even numbers: true
Product: 3628800
Grouped by mod 3: [(1, seq [1; 4; 7; 10]); (2, seq [2; 5; 8]); (0, seq [3; 6; 9])]
Distinct: [1; 2; 3; 4; 5]
Page 1: [1; 2; 3]
Page 2: [4; 5; 6]

In this comprehensive guide, we've explored F# sequences in depth - from basic creation to advanced techniques. Sequences are a powerful tool in F# for working with large, potentially infinite datasets efficiently.

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.