United Kingdom: +44 (0)208 088 8978

Slicing improvements in F# 5

How does slicing work in F#, and how is it getting better?

We're hiring Software Developers

Click here to find out more

Array slicing is already a very rich feature in F#, and it's been slightly improved in F# 5. In this post we're going to look at slicing in some detail. If you only want to know what's new for F# 5, skip straight to the end.

Sushi slices

What is array slicing?

Array slicing is an F# feature stolen from many programming languages before it. It's a way to get a subset of items from an array.

// Given the array [| 0; 1; 2; ...; 99 |]
let array = [| 0 .. 99 |]

// Get me the items from index 5 to 10 inclusive
array.[5 .. 10] // [|5; 6; 7; 8; 9; 10|]

// From index 10 to 10
array.[10 .. 10] // [|10|]

// If the slice start is later than the slice end then you get an empty array.
array.[10 .. 9] // [||]

You can also slice F# lists and strings in the same way.

[ 0 .. 9 ].[6 .. 8] // [6; 7; 8]

let text = "Time flies like an arrow; fruit flies like a banana"
text.[16 .. 23] // "an arrow"

Unbounded slices

You can omit the boundary at the beginning or end of any slice. This will extend the slice to start or the end of the collection.

let array = [| 0 .. 99 |]

array.[97 ..] // [|97; 98; 99|]

Custom slicing

If you define your own collection type for any reason in F#, you can implement the GetSlice member to allow slicing directly on your type.

type MyCoolCollection<'a> =
    | MyCoolCollection of 'a array
    member this.GetSlice(start, finish) =
        match this with
        | MyCoolCollection arr ->
            let start = start |> Option.defaultValue 0
            let finish = finish |> Option.defaultValue (arr.Length - 1)
            MyCoolCollection arr.[start .. finish]

let collection = MyCoolCollection [| 0 .. 9 |]

collection.[1 .. 2] // MyCoolCollection [|1; 2|]
collection.[8 .. ] // MyCoolCollection [|8; 9|]

Multi-dimensional slicing

If you use multi-dimensional arrays, you can also use slices in each dimension.

let grid =
    [| [| 1; 2; 3 |]
       [| 4; 5; 6 |]
       [| 7; 8; 9 |] |]
    |> array2D

// Normal index access of individual items.
// On the first row, get the second item.
grid.[0, 1]
// 2

// Using a slice on one dimension.
// On the first row, get the first two items.
grid.[0, 0 .. 1]
// int [] = [|1; 2|]

// Using slices in both dimensions.
// Get items that are in the first two rows and first two columns.
grid.[0 .. 1, 0 .. 1]
// [[1; 2]
//  [4; 5]]

// Using a partially unbounded slice in one dimension.
// Get items that are in the first two rows and skip the first column.
grid.[0 .. 1, 1 ..]
// [[2; 3]
//  [5; 6]]

// Using a completely unbounded in one dimension.
// Get the first row, for all columns.
grid.[0, *]
// [|1; 2; 3|]

// Get the first column for all rows.
grid.[*, 0]
// [|1; 4; 7|]

Note that whenever a fixed index is accessed for only one of the two dimensions, the result will be an array instead of a 2D array.

How is slicing better in F# 5?

Consistency and runtime safety: In F# 5, slicing is slightly more consistent between lists, arrays and strings in a way that is more permissive. For all collection types, if you specify a range that is out of range, you will now get an empty collection instead of a runtime exception. This is useful considering the slice range is often calculated rather than written as a numeric literal, as in my examples. We can now be confident our code won't throw exceptions and the empty collection is usually a more useful result.

[0 .. 3].[5 .. 6] // []
[|0 .. 3|].[5 .. 6] // [||]
"0123".[5 .. 6] // ""

Mixing fixed index and slices for 3D and 4D arrays: Before F# 5, you couldn't use a mix of fixed indexes and slices between the different dimensions of 3D and 4D arrays (this is something that I have already demonstrated for 2D arrays above). But now you can.

Warning: This is still marked as an experimental feature for F# 5 and requires you to be using the preview version.

let cube = Array3D.init 2 2 2 (fun i j k -> (i, j, k))
cube.[0, *, 0 .. 1]
// [[(0, 0, 0); (0, 0, 1)]
//  [(0, 1, 0); (0, 1, 1)]]

Happy slicing! 🍕🔪🍰🔪🥧🔪🍣