rwallace
rwallace

Reputation: 33395

F# Option chaining

The F# Option type is a good way to return 'result or failure' from a function.

Sometimes you need to chain such functions, along the lines of:

  1. Call the first function
  2. If it returns Some whatever, pass the whatever to the second function and return the result
  3. Otherwise, just immediately return None

Of course this can be done with pattern matching; an actual code fragment from a function that is itself implementing pattern matching in this way:

    match mtch env a0 a1 with
    |Some env->
        mtch env b0 b1
    |None->
        None

But it feels like there should be a more compact way to express this common idiom, perhaps with a higher order function. Is there something like that?

Upvotes: 2

Views: 1008

Answers (2)

FRocha
FRocha

Reputation: 960

You could use Computation Expressions. I'll use an example found on this page: https://fsharpforfunandprofit.com/posts/computation-expressions-intro/

Create the computation:

type MaybeBuilder() =

    member _.Bind(x, f) = 
        match x with
        | None -> None
        | Some a -> f a

    member _.Return(x) = 
        Some x

let maybe = new MaybeBuilder()

An example function that returns an Option:

let divideBy bottom top =
    if bottom = 0
    then None
    else Some(top/bottom)

A workflow using the Computation and chaining divideBy:

let divideByWorkflow init x y z = 
    maybe{
        let! a = init |> divideBy x
        let! b = a |> divideBy y
        let! c = b |> divideBy z
        return c
    }

Usage examples:

let good = divideByWorkflow 12 3 2 1 //Some 2
let bad = divideByWorkflow 12 3 0 1 //None

Upvotes: 4

kaefer
kaefer

Reputation: 5741

There's an Option module in FSharp.Core, providing "basic operations on options." Specifically, the signature of Option.bind matches your use case:

binder:('T -> 'U option) -> option:'T option -> 'U option

It states in the VisualStudio pop-up hint that bind f inp evaluates to match inp with None -> None | Some x -> f x.

// The actual argument values are not important here
let env, a0, a1, b0, b1 = (), (), (), (), () 
let mtch env _ _ = Some env // or might be None

match mtch env a0 a1 with
|Some env -> mtch env b0 b1
|None -> None

mtch env a0 a1
|> Option.bind (fun env -> mtch env b0 b1)

You might even improve on that by defining custom operators for operations on options.

let (>>=) ma f = Option.bind f ma
let (>>.) ma f = Option.map f ma

mtch env a0 a1
>>= fun env -> mtch env b0 b1
>>= ...

Upvotes: 6

Related Questions