F# Ranges
last modified May 17, 2025
In this article, we explore ranges in F# and how they simplify the generation of ordered sequences. Ranges provide an elegant way to create lists of values without manual repetition, making them especially useful in loops and collection operations.
A range in F# defines a sequence of values between a specified start and end point. They can be used with integers, characters, and other incrementable types. The syntax allows for concise and readable code, improving efficiency when handling sequences. F# offers both inclusive and exclusive range operators, along with optional step values to define custom increments.
An inclusive range generates values between two points, including the final
value. For example, [1 .. 5]
produces [1; 2; 3; 4; 5]
,
while ['a' .. 'e']
yields ['a'; 'b'; 'c'; 'd'; 'e']
.
This approach ensures predictable iteration without requiring explicit value
assignments. To specify a custom step value, a third parameter can be
introduced, such as [2 .. 2 .. 10]
, which generates [2; 4; 6; 8; 10]
.
Exclusive ranges omit the last value in a sequence. When using seq { 1 .. 4 }
,
the resulting sequence contains [1; 2; 3]
, excluding
4
. This behavior is useful when ensuring that certain boundary
conditions are maintained.
Ranges integrate naturally with loops, allowing for structured iteration over
predefined values. For example, using a for
loop with
for i in 1 .. 5 do printfn "Iteration %d" i
prints numbers from 1
to 5
, reducing the need for manually managing loop counters.
Beyond iteration, ranges enhance collection processing by enabling
transformations and filtering. A list comprehension, such as
[ for i in 1 .. 5 -> i * i ]
, applies a transformation to each
element, yielding [1; 4; 9; 16; 25]
. This approach keeps code
concise while leveraging functional programming principles.
Since F# ranges are generated lazily when used with sequences (seq
expressions), they provide memory-efficient handling of large datasets. Unlike
directly materializing lists, lazy evaluation ensures that values are computed
only when needed, optimizing performance. When processing extensive sequences,
alternatives like Seq.init
or Seq.unfold
may offer
further efficiency gains.
F# basic integer range
The simplest form of range generates integers between two values.
let numbers = [1..5] printfn "Inclusive range: %A" numbers let exclusive = [1..4..15] printfn "With step 4: %A" exclusive [1..5] |> List.iter (printfn "Number: %d")
We create three different integer ranges and demonstrate their usage.
let numbers = [1..5]
Creates an inclusive range from 1 to 5 (1, 2, 3, 4, 5).
let exclusive = [1..4..15]
Creates a range from 1 to 15 with step 4 (1, 5, 9, 13).
λ dotnet fsi basic.fsx Inclusive range: [1; 2; 3; 4; 5] With step 4: [1; 5; 9; 13] Number: 1 Number: 2 Number: 3 Number: 4 Number: 5
F# descending range
Ranges can count downward by using a negative step.
let countdown = [10..-1..5] printfn "Countdown: %A" countdown let bigStepDown = [20..-3..5] printfn "Big steps down: %A" bigStepDown [5..-1..1] |> List.iter (printfn "%d")
The example demonstrates descending ranges with different step values.
let countdown = [10..-1..5]
Creates a range from 10 down to 5 (10, 9, 8, 7, 6, 5).
λ dotnet fsi descending.fsx Countdown: [10; 9; 8; 7; 6; 5] Big steps down: [20; 17; 14; 11; 8; 5] 5 4 3 2 1
F# character range
Ranges work with characters as well as numbers.
let alphabet = ['a'..'z'] printfn "Alphabet: %A" (alphabet |> List.take 5) let vowels = ['a'..'e'..'z'] printfn "Every 5th letter: %A" vowels ['A'..'Z'] |> List.iter (printf "%c ") printfn ""
Shows how to create ranges of characters.
let alphabet = ['a'..'z']
Generates all lowercase letters from 'a' to 'z'.
λ dotnet fsi characters.fsx Alphabet: ['a'; 'b'; 'c'; 'd'; 'e'] Every 5th letter: ['a'; 'f'; 'k'; 'p'; 'u'; 'z'] A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
F# range with sequences
Ranges can be used with F# sequences for lazy evaluation.
let bigRange = seq {1..1000000} printfn "First 5: %A" (bigRange |> Seq.take 5) let infinite = seq {0..} printfn "Infinite first 10: %A" (infinite |> Seq.take 10) seq {10..-1..1} |> Seq.iter (printfn "Countdown: %d")
Demonstrates using ranges with sequences for memory efficiency.
let bigRange = seq {1..1000000}
Creates a sequence range that doesn't allocate all elements immediately.
let infinite = seq {0..}
Creates an infinite sequence starting at 0 (be careful with these!).
λ dotnet fsi sequences.fsx First 5: seq [1; 2; 3; 4; ...] Infinite first 10: seq [0; 1; 2; 3; ...] Countdown: 10 Countdown: 9 Countdown: 8 Countdown: 7 Countdown: 6 Countdown: 5 Countdown: 4 Countdown: 3 Countdown: 2 Countdown: 1
F# range in for loops
Ranges are commonly used with for loops.
for i in 1..5 do printfn "Loop iteration %d" i printfn "---" for i in 10..-2..0 do printfn "Countdown: %d" i printfn "---" for c in 'a'..'f' do printf "%c " c printfn ""
The example demonstrates using ranges in for loops.
for i in 1..5 do
Standard for loop with inclusive range from 1 to 5.
λ dotnet fsi loops.fsx Loop iteration 1 Loop iteration 2 Loop iteration 3 Loop iteration 4 Loop iteration 5 --- Countdown: 10 Countdown: 8 Countdown: 6 Countdown: 4 Countdown: 2 Countdown: 0 --- a b c d e f
F# range with floating point
Floating-point ranges require explicit step values.
let floatRange = [0.0..0.5..2.0] printfn "Floating range: %A" floatRange let descendingFloats = [5.0.. -1.0 ..0.0] printfn "Descending floats: %A" descendingFloats [0.0..0.1..1.0] |> List.iter (printfn "%.1f")
Shows how to create ranges with floating-point numbers.
let floatRange = [0.0..0.5..2.0]
Floating-point ranges must specify a step value.
λ dotnet fsi floating.fsx Floating range: [0.0; 0.5; 1.0; 1.5; 2.0] Descending floats: [5.0; 4.0; 3.0; 2.0; 1.0; 0.0] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
F# custom range for DateTime
Ranges are not built-in for DateTime
, but you can create a custom
range using a sequence expression and a step value. This is useful for
generating a series of dates.
open System let dateRange (start: DateTime) (finish: DateTime) (step: TimeSpan) = seq { let mutable current = start while current <= finish do yield current current <- current + step } let startDate = DateTime(2024, 5, 1) let endDate = DateTime(2024, 5, 5) let step = TimeSpan(1, 0, 0, 0) // 1 day for d in dateRange startDate endDate step do printfn "%A" d
This example defines a dateRange
function that yields dates from
the start to the end date, stepping by the specified interval. Here, it prints
all dates from May 1, 2024 to May 5, 2024.
In this article we've explored the versatility of ranges in F#. They provide a concise syntax for generating sequences of values, particularly useful with loops and collection operations.