问题
I'm using a library that takes the results as two-track values (success and failure).In the Observable.map
function bodies I often get an observable from success track of a function, and I don't know how to handle them (at Observable.map
body).
In the other words I often get stuck in situations that the result looks something like the following (of course it's the simplest one):
Rop.Result<IObservable<Rop.Result<IObservable,Messages>,Messages>
On one hand, translating messages back to exceptions and raising them seems wired to me, and on the other hand, the error handlers are impure functions and I'd rather not pass them around.
I wonder what is the standard and clean solution to handle the failures from two-track results at the Observable.map
body.
Update: Example
This is the simplest ever sample of the problem :
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
In this example the final
result of the expression is of type IObservable<Result<IObservable<Person>,ErrorMessage list>>
but I want it to be IObservable<Person>
, because further Observable
transformations will end up pretty dirty and complicated expressions.
回答1:
On the simplest level, what you need here is a way to extract and then surface an IObservable
that is wrapped in a RopResult
. One part of the solution was already mentioned in the comments (Observable.bind
), the other is a function RopResult<IObservable<'a>, 'b> -> IObservable<RopResult<'a,'b>>
.
To get that, you need to deconstruct the RopResult
and handle both the cases. So to put that in context of your stated problem:
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)
Is this good code though? I don't think so. You could make it slightly better perhaps by using other ROP api functions, or by using a computation expression for the body of the bind here, but that's not the point.
Both reactive programming using Observables, and "railway-oriented programming" are useful tools as long as they can capture and abstract away some element of complexity involved in whatever computation you're modelling. This works because they're build on primitives that compose well together. When you try to "interleave" the two like this, you forfeit some of that as you have to manage that composition yourself.
I would say you're better off using exceptions here - especially since Observables have a built-in support for them.
回答2:
If you don't use success with message it seems pretty natural to combine Observable
and RopResult
.
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
来源:https://stackoverflow.com/questions/49956854/convert-railway-oriented-failure-track-to-rx-friendly-errors