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.
// 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.
// 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 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.
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.
#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.
#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.
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.