4

Given two Maybe values, how can I combine them into a single Maybe that will either:

  1. call onSuccess() whenever either of the source Maybes calls onSuccess
  2. call onComplete() whenever both of the source Maybes call onComplete()?

(Cf. Option.orElse() in Scala or Vavr.)

E.g., assuming the existence of a combine() method that does what I want:

combine(Maybe.just(a), Maybe.empty())  ≍ Maybe.just(a)
combine(Maybe.empty(), Maybe.just(b))  ≍ Maybe.just(b)
combine(Maybe.empty(), Maybe.empty())  ≍ Maybe.empty()
combine(Maybe.never(), /*anything*/ )  ≍ /*the thing*/ 
combine(/*anything*/,  Maybe.never())  ≍ /*the thing*/ 

At first I thought amb() & family were what I was looking for, but that completes as soon as either source Maybe completes, meaning if the first Maybe completes without a value, you never get the value from the second Maybe.

Right now I'm using

Maybe.mergeArray(m1, m2).firstElement()

which seems to do what I want, but I’m not certain it’s correct and I’m not certain it’s the cleanest way to do it. (For instance, if there’s some delay, will it call onSuccess() immediately when one or the other source does, or will it wait for both onComplete()s?)

Is this correct? Is there a more idiomatic approach?


ETA: I'm happy taking the first value; I don't need to wait for both to complete:

combine(Maybe.just(a), Maybe.just(b))  ≍ Maybe.just(/* a or b, don't care */)

(I can imagine situations in which I might prefer one or the other and want to indicate that by order of the arguments, but in that situation I suspect sequential would be better than parallel.)

David Moles
  • 48,006
  • 27
  • 136
  • 235
  • I believe that a `zip` function will wait for a terminal event from all sources observables before emitting the results, if you want to wait for terminal event of all sources? – Mark Mar 14 '18 at 22:36
  • @MarkKeen I don't want to wait for a terminal event; if there's a value, I want to take whichever comes first. – David Moles Mar 14 '18 at 22:39
  • A question is when `combine(just(a), never())`, answer in first code block is 'never()'. But in the edit you said you don't wait for complete, so it should be 'just(a)'. Your description is inconsistent. – Dean Xu Mar 20 '18 at 03:52
  • @DeanXu That's a good point. I'll edit that section. – David Moles Mar 20 '18 at 15:31
  • 1
    @DavidMoles Your approach is the best. The only thing is you can use `merge` rather than `mergeArray` – Dean Xu Mar 21 '18 at 00:56

1 Answers1

1

There's a slightly different approach which might be a little nearer to your definition. This would be using Observable.switchMapMaybe():

Maps the upstream items into MaybeSources and switches (subscribes) to the newer ones while disposing the older ones (and ignoring their signals) and emits the latest success value of the current one if available while failing immediately if this Observable or any of the active inner MaybeSources fail.

Observable.just(m1, m2).switchMapMaybe(m -> m).firstElement()

But the approach using Maybe.mergeArray(m1, m2).firstElement() should be sufficient as well. The firstElement() operator emits the first element emitted by the mergeArray() flowable. This one is unordered and thus there's no information about the completion of any of the maybes.

tynn
  • 38,113
  • 8
  • 108
  • 143
  • I think `mergeArray()` is more readable but it does seem (after reading [this answer on `switchMap()`](https://stackoverflow.com/a/28180793/27358); I think the `switchMapXXX` docs need a few more revisions) that `switchMapMaybe()` more precisely captures the intent. – David Moles May 21 '18 at 18:13
  • I downvoted, because it doesn't satisfy condition 1. For example when `m1 = Maybe.just(1).delay(1, SECONDS)` and `m2 = Maybe.empty().delay(2, SECONDS)` it will call `onComplete` even though `m1` would call `onSuccess` – arekolek Jan 17 '19 at 13:10