将面向铁路的故障跟踪转换为 Rx 友好错误

Convert Railway oriented failure track to Rx friendly errors

我正在使用一个将结果作为 two-track 值(成功和失败)的库。在 Observable.map 函数体中,我经常从函数的成功轨迹中获得一个可观察值,并且我不知道如何处理它们(在 Observable.map body)。

换句话说,我经常遇到如下情况(当然这是最简单的):

Rop.Result<IObservable<Rop.Result<IObservable,Messages>,Messages>

一方面,将消息转换回异常并引发它们似乎与我有关,另一方面,错误处理程序是不纯函数,我宁愿不传递它们。

我想知道在 Observable.map body.

中处理 two-track 结果失败的标准和干净的解决方案是什么

更新:例子

这是有史以来最简单的问题示例:

module problem
open FSharp.Control.Reactive

type Person = {FirstName:string;LastName:string}

let getPersonByLastName lastName =

    let persons =   Observable.toObservable<|seq{
            yield {FirstName="Jhone";LastName="Smith"}
            yield {FirstName="Joe";LastName="Smith"}
            yield {FirstName="Jack";LastName="Smith"}
            }

    Rop.succeed persons

let main =
  let final =
    Observable.single("Smith")
    |>Observable.map(fun lastName -> 
                    let personResults = getPersonByLastName lastName
                    personResults
                    )

  0

在这个例子中,final 表达式的结果是 IObservable<Result<IObservable<Person>,ErrorMessage list>> 类型,但我希望它是 IObservable<Person>,因为进一步的 Observable 转换最终会很漂亮肮脏和复杂的表达。

在最简单的层面上,您在这里需要的是一种提取然后显示包裹在 RopResult 中的 IObservable 的方法。解决方案的一部分已经在评论中提到(Observable.bind),另一部分是函数RopResult<IObservable<'a>, 'b> -> IObservable<RopResult<'a,'b>>

为此,您需要解构 RopResult 并处理这两种情况。因此,将其放在您陈述的问题的上下文中:

Observable.single("Smith")
|>Observable.bind(fun lastName -> 
    match getPersonByLastName lastName with
    | Rop.RopResult.Success (next, msgs) -> 
        next |> Observable.map (fun x -> Rop.RopResult.Success (x, msgs))
    | Rop.RopResult.Failure msgs ->
        Observable.result <| Rop.RopResult.Failure msgs)

这是好的代码吗?我不这么认为。您可以通过使用其他 ROP api 函数或通过对此处的绑定主体使用计算表达式来稍微改善它,但这不是重点。

使用 Observables 的反应式编程和 "railway-oriented programming" 都是有用的工具,只要它们可以捕获和抽象出您正在建模的任何计算中涉及的一些复杂元素。这是有效的,因为它们建立在可以很好地组合在一起的基元之上。当你尝试 "interleave" 像这样的两个时,你会失去一些,因为你必须自己管理那个组合。

我会在这里说 you're better off using exceptions - 特别是因为 Observables 有对它们的内置支持。

如果您不使用 success with message,那么组合 ObservableRopResult 似乎很自然。

exception ROPExn of string list
let combine = function
                         | Success (x, _) -> x
                         | Failure errors -> Observable.Throw(ROPExn(errors))                  
let main =
  let final =  Observable.single("Smith")  |> Observable.bind(getPersonByLastName >> combine)
  let handleError = function
      |ROPExn(list) -> printfn "ROP fail"
      | _ -> printfn "error"
  let subscription = final |> Observable.subscribeWithCallbacks (fun p -> printfn "%s" p.FirstName) handleError ignore