Consider the following function that takes a Sequence of (pairs) `int * int`

.

let f s =Essentially I am looking for the first pair in the sequence where the second element in the pair satisfies a particular condition and then returning the corresponding first element or a default (i.e. 10) if no satisfactory pair was found.

let o = Seq.tryFind (fun (_, x) -> x < 0) s

if Option.isSome o then

Option.get o |> fst

else

let p = Seq.tryFind (fun (_, x) -> x = 0) s

if Option.isSome p then Option.get p |> fst

else 10

That code is pretty ordinary. First of all, I would like to improve on the two calls to

`Seq.tryFind`

.let f s =Now if the Scala Option

let tf g = Seq.tryFind (fun (_,x) -> g x) s

let o = tf (fun x -> x < 0)

if Option.isSome o then

Option.get o |> fst

else

let p = tf ((=) 0)

if Option.isSome p then Option.get p |> fst

else 10

`orElse`

and `getOrElse`

functions were available as well as the Haskell backquotes infix syntax then we could really make a difference.Neither

orElse : 'T option -> 'T option -> 'T option

let orElse o p = if Option.isSome o then o else p

getOrElse : 'T option -> 'T -> 'T

let getOrElse o d = match o with | Some x -> x | _ -> d

// N.B. this is not valid F#

let f s =

let tf g = Seq.tryFind (fun (_,x) -> g x) s

tf (fun x -> x < 0) `orElse` tf ((=) 0) |> Option.map fst `getOrElse` 10

`orElse`

or `getOrElse`

exist in the F# `Option`

module. However the Core function `defaultArg`

is essentially `getOrElse`

.Unfortunately F# doesn't have a way to use a (non-operator) function in infix form, like the backquotes in Haskell. However, we can define operators, which are essentially just infix functions.

Now we can write a valid F# version.

let (|?) = orElse

let (|?|) = defaultArg

let f s =

let tf g = Seq.tryFind (fun (_,x) -> g x) s

tf (fun x -> x < 0) |? tf ((=) 0) |> Option.map fst |?| 10

Laziness and Composability

Notice that in the original example, the second

`tryFind`

is only executed if the first one is unsuccessful because the `then`

expression of an `if`

statement is lazy, i.e. it is only evaluated if the condition is false.However functions in F# are strict by default i.e. their arguments are evaluated prior to application. Consequently both

`tryFind`

s are evaluated, irrespective of their values, as they are arguments to `orElse`

.So here is an example where laziness is required to achieve reasonable composability. Implementing this gives the final code.

let orElse o (p:'a option Lazy) = if Option.isSome o then o else p.Force()

let (|?) = orElse

let (|?|) = defaultArg

let f s =

let tf g = Seq.tryFind (fun (_,x) -> g x) s

tf (fun x -> x < 0) |? lazy (tf ((=) 0)) |> Option.map fst |?| 10