Arshia001
Arshia001

Reputation: 1872

How to calculate a result based on or-else of many expensive computations in F#

Assuming I have the following pseudo-C# code:

TResult MyMethod()
{
    var firstTry = SomeExpensiveComputation1();
    if (firstTry.IsSuccessful) return firstTry;

    var secondTry = SomeExpensiveComputation2();
    if (secondTry.IsPartiallySuccessful)
    {
        var subTry1 = SomeExpensiveComputationOn2_1(secondTry);
        if (subTry1.IsSuccessful) return subTry1;

        var subTry1 = SomeExpensiveComputationOn2_2(secondTry);
        if (subTry1.IsSuccessful) return subTry1;
    }

    return LastExpensiveComputationThatNeverFails();
}

If I were to do this in F#, it'd look like this:

let MyMethod () =
    let firstTry = SomeExpensiveComputation1 ()
    if firstTry.IsSuccessful then firstTry else
        let secondTry = SomeExpensiveComputation2 ()
        if secondTry.IsSuccessful then
            let subTry1 = SomeExpensiveComputationOn2_1 ()
            if subTry1.IsSuccessful then subTry1 else
                let subTry2 = SomeExpensiveComputationOn2_2 ()
                if subTry2.IsSuccessful then subTry2 else LastExpensiveComputationThatNeverFails ()
        else
            LastExpensiveComputationThatNeverFails()

As you can see above, I had to repeat LastExpensiveComputationThatNeverFails twice. This doesn't have to be a method call, it can be many lines of inline computations (e.g. try to get some value from cache, if it doesn't exist calculate it.) One could refactor the code into another function, but I still don't like how the same code, even if it's just one line, has to be written twice (or more), as it leads to duplication and messy maintenance. What is the correct way to write such code in F#?

Upvotes: 1

Views: 111

Answers (1)

I think it's fine to make LastExpensiveComputationThatNeverFails a local function that is called whenever the result is needed.

However, one could also change the operations to return Option<_> and use the built-in combinator functions.

let MyMethod () =
  SomeExpensiveComputation1 ()
  |> Option.orElseWith
    ( fun () -> 
        SomeExpensiveComputation2 ()
        |> Option.bind (fun _ -> SomeExpensiveComputationOn2_1 () |> Option.orElseWith SomeExpensiveComputationOn2_2)
    )
  |> Option.orElseWith LastExpensiveComputationThatNeverFails

Option.orElseWith LastExpensiveComputationThatNeverFails is only executed if the previous result is None which it will be upon failure.

Upvotes: 3

Related Questions