Thomas
Thomas

Reputation: 12107

A computation expression to get the first valid result out, in F#

How can I achieve something like this in a clean way?

let's imagine this simple code:

let a () = checkSomeStuff ();     None
let b () = do Something ();       Some "thing"
let c () = checkSomethingElse (); None


"getOne" {
    do! a()
    do! b()
    do! c()
}

and it would return the first "Some".

I could achieve this exact behavior by using Result where I'd return the value through an Error and continue through with Ok, but that is not readable / nice:

let a () = checkSomeStuff ();     Ok ()
let b () = do Something ();       Error "thing"
let c () = checkSomethingElse (); Ok ()


result {
    do! a()
    do! b()
    do! c()
}

this would work, but I'm looking to achieve that without mis-using the Result type. Can it be done with the existing expressions?

Upvotes: 0

Views: 393

Answers (3)

David Raab
David Raab

Reputation: 4488

You could create a function that does what you want. But you have to think throughout what you want to do.

So, your logic is.

  1. You execute a function that returns an option
  2. Then you check that option. if it is None you execute another function, if it is Some you return the value.

A function like these could look like this:

let getSome f opt =
    match opt with
    | None   -> f ()
    | Some x -> Some x

With such a function, you then could write. ***

let x =
    checkSomeStuff ()
    |> getSome (fun _ -> Something () )
    |> getSome checkSomethingElse

But then i think, hmmm.... isn't there a better name for getSome? In some way i want to say:

Execute some code and check if it is Some, or else pick the next thing.

With this in mind, i think. hmm.... isn't there already a Option.orElse? And yes! There is! There is also a Option.orElseWith function, that fits your need even better. So now, you can write.

let y =
    checkSomeStuff ()
    |> Option.orElseWith (fun _ -> Something () )
    |> Option.orElseWith checkSomethingElse

If you have functions with side-effects, then you should use Option.orElseWith, otherwise, you can just sue Option.orElse


***: I assume you have the following function defined

let checkSomeStuff () =
    None

let Something () =
    Some "thing"

let checkSomethingElse () =
    None

Upvotes: 0

Brian Berns
Brian Berns

Reputation: 17038

You don't need a computation expression for this. F# has a built-in function called Seq.tryPick that applies a given function to successive elements of a sequence, returning the first Some result, if any. You can use tryPick to define getOne like this:

let getOne fs =
    fs |> Seq.tryPick (fun f -> f ())

Trying it with your example:

let a () = checkSomeStuff ();
let b () = Something ();
let c () = checkSomethingElse ();

let x = getOne [ a; b; c ]
printfn "%A" x   // Some "thing"

Upvotes: 5

Tomas Petricek
Tomas Petricek

Reputation: 243061

Some time ago, I wrote a post about imperative computation expression builder that does something along those lines. You can represent computations as option-returning functions:

type Imperative<'T> = unit -> option<'T>

In the computation builder, the main thing is the Combine operation that represents sequencing of operations, but you need a few others to make it work:

type ImperativeBuilder() = 
  member x.ReturnFrom(v) = v
  member x.Return(v) = (fun () -> Some(v))
  member x.Zero() = (fun () -> None)
  member x.Delay(f:unit -> Imperative<_>) = 
    (fun () -> f()())
  member x.Combine(a, b) = (fun () ->
    match a() with 
    | Some(v) -> Some(v) 
    | _ -> b() )

let imperative = new ImperativeBuilder()  

You can then reimplement your example - to return a value, you just use return, but you need to combine individual operations using return!, because the builder does not support do!:

let a () = imperative { printfn "one" }
let b () : Imperative<string> = imperative { return "result" }
let c () = imperative { printfn "two" }

let f = imperative {
    return! a()
    return! b()
    return! c()
}

f()

Upvotes: 1

Related Questions