Reputation: 12107
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
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.
option
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
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
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