如何使用绑定和映射代替嵌套匹配

How to use bind and map in place of nested matches

F# 6.0.3

我在 Google 上看到了一些接近我需要的解决方案;但作为新手,我不太了解如何使用绑定和映射来获得解决方案。

我有很多如下格式的工作程序:

示例 #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

示例 #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   

可以看出,这些是具有几乎相同行为的嵌套方法——不同之处仅在于被调用的异步过程和初始化列表。我想做的是:

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

也就是说,一个过程封装了除 Async 方法及其参数之外的所有内容,但以更好的方式管理所有“None”。

我希望我的意图是明确的。

TIA

使用提到的 ROP 代码可以重写。 Result 用于跟踪错误并在管道末尾抛出错误。使用当前设计可以通过仅记录错误而不是在最后一行之前抛出来避免异常。

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

如果您乐于使用异常,那么您甚至不需要 railway-oriented 编程 (ROP)。 ROP 对于更复杂的验证任务很有用,但我认为异常通常是处理错误的完全合理且简单的方法。在您的情况下,您可以定义一个助手来提取 option<'T> 的值或失败并显示给定的错误消息:

let orFailWith msg opt = 
  match opt with 
  | Some v -> v
  | None -> failwithf "%s" msg

使用它,您可以按如下方式重写代码:

let saveAllDiagnosis =
  let e = m.Encounter |> orFailWith "No encounter found"
  let id = e.EncounterId |> orFailWith "No Encounter id found"
  for dx in m.AllDiagnosisList do
    dx.Key |> Option.iter (fun k ->
      editAllDiagnosisInPreviousEncountersAsync id dx |> Async.RunSynchronously)

let saveEncounterDiagnosis = 
  let e = m.Encounter |> orFailWith "No encounter found"
  let id = e.EncounterId |> orFailWith "No Encounter id found"
  for dx in m.BillingDiagnosisList do
    saveDxAsync id dx |> Async.RunSynchronously

由于我不知道这方面的更广泛背景,所以很难多说 - 你的代码是必要的,但如果你遵循 sandwich pattern.

上面发布的解决方案对这个新手很有帮助。但是加上我自己的两分钱,我会这样做:

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   

为了速度,将来我可能会让异步函数一次作用于整个列表,而不是一个项目;但就目前而言,这段代码最接近我提出问题的意图。改进和批评很高兴受到赞赏! F# 很有趣!

感谢大家。