'How to use bind and map in place of nested matches

F# 6.0.3

I have seen some solutions on Google that are close to what I need; but being a Newbie I can't quite get how to use bind and map to get the solution.

I have many working procedures of the following format:

Example #1:

let saveAllDiagnosis =
                    let savealldiagnosis = match m.Encounter with
                                           | None -> failwith "No encounter found"
                                           | Some e -> match e.EncounterId with
                                                       | None -> failwith "No Encounter id found"
                                                       | Some id ->  m.AllDiagnosisList 
                                                                       |> List.iter ( fun dx -> match dx.Key with
                                                                                                | None -> ()
                                                                                                | Some k -> Async.RunSynchronously (editAllDiagnosisInPreviousEncountersAsync id dx)) 


                savealldiagnosis

Example #2

let saveEncounterDiagnosis = 
                    let savedx = match m.Encounter with 
                                 | None -> failwith "No encounter found"
                                 | Some e -> match e.EncounterId with
                                             | None -> failwith "No Encounter id found"
                                             | Some id -> m.BillingDiagnosisList |> List.iter ( fun dx -> Async.RunSynchronously (saveDxAsync id dx))
                    savedx   

As can be seen, these are nested methods with almost identical behavior--differing only in the async procedure being called and the initializing list. What I would like to do is something along the lines of:

let runProcedures (fn: Model->Async) Model = ????

That is, a single procedue that encapsulates everything except the Async method and it's parameters but manages all the "None"s in a better way.

I hope my intent is clear.

TIA

f#


Solution 1:[1]

Using mentioned ROP code can be rewritten as such. Result is used to track error and throw it at the end of pipeline. With current design is possible to avoid exceptions by just logging error instead of throwing at before last line.

type Encounter = { EncounterId : int option }

type Diagnostic = { Key : int option }

type Thing = {
    Encounter : Encounter option
    AllDiagnosisList : Diagnostic list
}

let editAllDiagnosisInPreviousEncountersAsync id diag = async { return () }

module Result =
    let ofOption err opt =
        match opt with
        | Some v -> Ok v
        | None -> Error err

    let join res =
        match res with
        | Error v
        | Ok v -> v

let saveAllDiagnosis m =
    m.Encounter
    |> Result.ofOption "No encounter found" // get value from option or log error
    |> Result.map (fun e -> e.EncounterId)
    |> Result.bind (Result.ofOption "No Encounter id found") // get EncounterId or log error
    |> Result.map (fun id -> (
        m.AllDiagnosisList
        |> Seq.where (fun dx -> dx.Key.IsSome)
        |> Seq.iter (fun dx -> Async.RunSynchronously (editAllDiagnosisInPreviousEncountersAsync id dx))
    ))
    |> Result.mapError failwith // throw error
    |> Result.join // Convert Result<unit, unit> into unit

Solution 2:[2]

The solutions posted above are very helpful to this newbie. But adding my own two cents worth, I going with this:

let _deleteDxFromEncounterAsync (encounterId:int) (dx:Diagnosis) : Async<unit> = deleteDxFromEncounterAsync encounterId dx.Description
let _deleteDxFromAllPreviousEncountersAsync (encounterId:int) (dx:Diagnosis) : Async<unit> = deleteDxFromAllPreviousEncountersAsync encounterId  dx.Description   
let _saveDxAsync (encounterId:int) (dx:Diagnosis) : Async<unit> = saveDxAsync encounterId dx
let _editAllDiagnosisInPreviousEncountersAsync (encounterId:int) (dx:Diagnosis) : Async<unit> = editAllDiagnosisInPreviousEncountersAsync encounterId dx 

let listchk (dxs:Diagnosis list) : Diagnosis list option =
                match dxs with
                | [] -> None
                | _ -> Some dxs
    
let _save (fn:int -> Diagnosis-> Async<unit>) (dxs:Diagnosis list) : unit = 
 match dxs |> listchk, m.Encounter |> Option.bind (fun v -> v.EncounterId) with
 | Some dxs, Some id -> dxs |> List.iter (fun dx ->   Async.RunSynchronously(fn id dx)) 
 | _,_ -> failwith "Missing Encounter or EncounterId or Empty List"
   

 
m.DeletedBillingDiagnosis |>_save _deleteDxFromEncounterAsync
m.DeletedAllDiagnosis     |>_save _deleteDxFromAllPreviousEncountersAsync
m.BillingDiagnosisList    |>_save _saveDxAsync                                 
m.AllDiagnosisList        |> List.filter (fun dx -> dx.Key.IsSome)    |>_save _editAllDiagnosisInPreviousEncountersAsync   

For speed, in the future, I will probably have the Async functions act on the entire list at one time rather then one item; but for now, this code comes closest to my intent in asking the question. IMPROVEMENTS AND CRITISM IS GLADDLY APPRECIATED! F# is fun!

Thanks to all.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 JL0PD
Solution 2