F# Friday – Seq.choose

Filtering over a sequence of values omits values that do not meet certain criteria. Mapping over a sequence of values transforms each value into another value. What if you could do both at the same time—filter out unwanted values, but transform the ones that are left? You can with Seq.choose.

Whereas Seq.filter takes a predicate—a function that takes a value and returns a Boolean—Seq.choose takes a function that takes a value and returns an Option. If that Option is None, then Seq.choose discards it. If it is Some, then Seq.choose extracts the value from the Some and returns it as the next element in the output sequence.

let f = fun n -> match n % 2 with
                 | 0 -> Some (n * n)
                 | _ -> None
let squaredEvens = seq [4..7]
                   |> Seq.choose f
// val squaredEvens : seq<int> = seq [16; 36]

The following graphic illustrates what is going on:

Seq.choose takes a function that may perform a transform on its input that returns an option: Either a Some containing the transformed value or otherwise a None. The resulting sequence only retains the Some values; Seq.choose filters out the None values.
Choosing Items from a Sequence

OK, so Seq.choose performs a filter and a map all in one. Why not just call Seq.filter and then Seq.map? One example I’ve seen is when you’re pattern matching and destructuring and then only using one/some of the potential match cases. Perhaps you have a discriminated union representing orders that were either fulfilled or cancelled before fulfillment:

type Order =
| Fulfilled of id : string * total : decimal
| Cancelled of id : string * total : decimal

You’d like to know how many dollars you “lost” in cancelled orders. Use Seq.choose to extract the dollar value of each cancelled order, and then sum them:

let orders = [
    Fulfilled ("fef3356074b4", 28.50m)
    Fulfilled ("2605c9988f1d", 88.25m)
    Cancelled ("94edac47971f", 22.01m)
    Fulfilled ("2a1ff57b8f46", 39.30m)
    Fulfilled ("9ee0a3e3da3a", 27.97m)
    Cancelled ("db5dc439ad93", 99.49m)
    Fulfilled ("08d58811ed36", 53.72m)
    Cancelled ("63ebd07475ca", 93.66m)
    Cancelled ("12d16ae9c112",  7.79m)
    Fulfilled ("c5ecedaedb0e", 87.21m)
]

let cancelledDollars = 
    orders 
    |> Seq.choose (function
                   | Cancelled (_, dollars) -> 
                        Some dollars 
                   | _ -> None)
    |> Seq.sum
// val cancelledDollars : decimal = 222.95M

One thought on “F# Friday – Seq.choose”

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.