-
Notifications
You must be signed in to change notification settings - Fork 7.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reduce an empty observable #423
Comments
@zsxwing, Thanks for diving into this. (aside: When you write snippets like this it is a good idea to also look at onCompleted and onError. Aggregate does not "throw" an exception, but sends out an onError with an exception. And return the empty sequence by immediately calling onCompleted, which is not the same as "do nothing". Subtle, but important to be super precise as we look at the edge cases) var xs = new[]{ 1, 2 , 3}.ToObservable(); xs.Aggregate(accumulator: (x,y)=>x+y).Subscribe // [1, 2, 3].Aggregate((x,y)=>x+1) = 6 ys.Aggregate(accumulator: (x,y)=>x+y).Subscribe // Boom = System.InvalidOperationException: Sequence contains no elements. xs.Aggregate(seed: 0, accumulator: (x,y)=>x+y).Subscribe // [1, 2, 3].Aggregate((x,y)=>x+1) = 6 ys.Aggregate(seed: 0, accumulator:(x,y)=>x+y).Subscribe // [].Aggregate((x,y)=>x+1) = 0 xs.Aggregate(seed: 0, accumulator: (x,y)=>x+y, resultSelector: x => string.Format("[{0}]", x)).Subscribe // [1, 2, 3].Aggregate((x,y)=>x+1) = [6] ys.Aggregate(seed: 0, accumulator:(x,y)=>x+y, resultSelector: x => string.Format("[{0}]", x)).Subscribe // [].Aggregate((x,y)=>x+1) = [0] |
No, looking at scan, I would say that scan in Rx.NET is broken. In Haskell, the seed in a scan is sent out as well Prelude> scanl (+) 0 [] Prelude> scanl (+) 0 [1,2,3] With no seed, the results look like this Prelude> scanl1 (+) [1,2,3] Prelude> scanl1 (+) [] But in C#, the seed is not send out. var xs = new[]{ 1, 2 , 3}.ToObservable(); xs.Scan(accumulator: (x,y)=>x+y).Subscribe // [1, 2, 3].Scan((x,y)=>x+1) = 1 ys.Scan(accumulator: (x,y)=>x+y).Subscribe // Done xs.Scan(seed: 0, accumulator: (x,y)=>x+y).Subscribe // [1, 2, 3].Scan((x,y)=>x+1) = 1 ys.Scan(seed: 0, accumulator:(x,y)=>x+y).Subscribe // Done |
I'd say we define for once and for all that (see http://www.haskell.org/hoogle/?hoogle=scanl) xs.aggregate(op) = xs.scan(op).lastasync() |
RxJava misses .LastAsync() and instead uses takeLast(n), which behaves differently. var xs = Observable.Empty(); |
I found http://msdn.microsoft.com/en-us/library/system.reactive.linq.observable.aspx was out of date. Now Rx.Net has a blocking version of Here is the document I copy from VS: // Summary:
// Returns the first element of an observable sequence.
//
// Parameters:
// source:
// Source observable sequence.
//
// Type parameters:
// TSource:
// The type of the elements in the source sequence.
//
// Returns:
// Sequence containing the first element in the observable sequence.
//
// Exceptions:
// System.ArgumentNullException:
// source is null.
//
// System.InvalidOperationException:
// (Asynchronous) The source sequence is empty.
public static IObservable<TSource> FirstAsync<TSource>(this IObservable<TSource> source);
//
// Summary:
// Returns the first element of an observable sequence that satisfies the condition
// in the predicate.
//
// Parameters:
// source:
// Source observable sequence.
//
// predicate:
// A predicate function to evaluate for elements in the source sequence.
//
// Type parameters:
// TSource:
// The type of the elements in the source sequence.
//
// Returns:
// Sequence containing the first element in the observable sequence that satisfies
// the condition in the predicate.
//
// Exceptions:
// System.ArgumentNullException:
// source or predicate is null.
//
// System.InvalidOperationException:
// (Asynchronous) No element satisfies the condition in the predicate. -or-
// The source sequence is empty.
public static IObservable<TSource> FirstAsync<TSource>(this IObservable<TSource> source, Func<TSource, bool> predicate);
//
// Summary:
// Returns the last element of an observable sequence.
//
// Parameters:
// source:
// Source observable sequence.
//
// Type parameters:
// TSource:
// The type of the elements in the source sequence.
//
// Returns:
// Sequence containing the last element in the observable sequence.
//
// Exceptions:
// System.ArgumentNullException:
// source is null.
//
// System.InvalidOperationException:
// (Asynchronous) The source sequence is empty.
public static IObservable<TSource> LastAsync<TSource>(this IObservable<TSource> source);
//
// Summary:
// Returns the last element of an observable sequence that satisfies the condition
// in the predicate.
//
// Parameters:
// source:
// Source observable sequence.
//
// predicate:
// A predicate function to evaluate for elements in the source sequence.
//
// Type parameters:
// TSource:
// The type of the elements in the source sequence.
//
// Returns:
// Sequence containing the last element in the observable sequence that satisfies
// the condition in the predicate.
//
// Exceptions:
// System.ArgumentNullException:
// source or predicate is null.
//
// System.InvalidOperationException:
// (Asynchronous) No element satisfies the condition in the predicate. -or-
// The source sequence is empty.
public static IObservable<TSource> LastAsync<TSource>(this IObservable<TSource> source, Func<TSource, bool> predicate); |
@benjchristensen do we need to add the new Rx.Net interfaces? |
I have an implementation for this ready. This will fax the issue for reduce/scan. Did you see the comments. Sent from my iPad
|
@headinthebox sorry, I mean that do we need to add these Rx.Net interfaces (lastAsync, firstAsync) into RxJava. |
@zsxwing Yes, that's what I meant. I have an implementation for them, but then recursively found some bugs in other operators. |
We already have a We solved the problem of duplicate naming for blocking/non-blocking in RxJava by separating all blocking operators into the The |
That seems non-obvious why 2 variants of Why should |
I just realized that the question "what should the operator do if the source observable does not have enough elements?" should be asked for some other operators as well:
I'd prefer that onError be called, instead of ignoring that there were not enough elements. That's also what scala collections do. And if takeLast on an empty observable calls onError, reduce can nicely be implemented using scan. |
@headinthebox I am not aware of a rule that covers this, can you provide an answer for @samuelgruetter's question? We can then go build the unit tests and get code to match the rule. |
elementAt ==> OnError("out of range") reduce(Func2) and aggregate(Func2) ==> that's what started this discussion, see above. takeLast ==> as many elements as it can get, ending with onCompleted. |
The behavior in Rx.NET is the same as in Haskell, except for the bug in scan discussed up here. Prelude> take 5 [1..3] etc. |
I reviewed these operators in RxJava. Here is my conclusion.
Please correct me if I have misunderstood something. |
I have completed the blocking and non-blocking |
@zsxwing Are you working on any of these others already so we don't duplicate effort? |
Actually, seems like |
This fixes issue ReactiveX#423 The fix is based on this comment by @headinthebox: ReactiveX#423 (comment)
The When the Observable is empty but a seed is passed in, it just emits the seed: /**
* A reduce on an empty Observable and a seed should just pass the seed through.
*
* This is confirmed at https://github.com/Netflix/RxJava/issues/423#issuecomment-27642456
*/
@Test
public void testReduceWithEmptyObservableAndSeed() {
Observable<Integer> observable = Observable.range(1, 0);
int value = observable.reduce(1, new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer t1, Integer t2) {
return t1 + t2;
}
}).toBlockingObservable().last();
assertEquals(1, value);
} Please confirm that current code and unit test match the expected behavior and then we can close this ... or we correct again. |
There is still a difference between Average in RxJava and Rx.Net when the observable is empty. This is a unit test for Average in RxJava. @Test
public void testEmptyAverage() throws Throwable {
Observable<Integer> src = Observable.empty();
average(src).subscribe(w);
verify(w, never()).onNext(anyInt());
verify(w, times(1)).onError(any(ArithmeticException.class));
verify(w, never()).onCompleted();
} If the observable is empty, average emits an ArithmeticException. Here is a sample for Average in Rx.Net. IObservable<double> o = Observable.Empty<int>().Average();
o.Subscribe(
x => Console.WriteLine("OnNext: " + x),
e => Console.WriteLine("OnError: " + e),
() => Console.WriteLine("Done")
); It outputs "OnError: System.InvalidOperationException: Sequence contains no elements.". |
@benjchristensen Now I'm only working on |
I sent the PR #478 for |
I fixed the |
Closed as it's fixed. |
This fixes issue ReactiveX#423 The fix is based on this comment by @headinthebox: ReactiveX#423 (comment)
* Updated Spring Boot from 1.5.19 to 1.5.20
Hi,
In RxJava,
reduce(Func2<T, T, T> accumulator)
may be not implemented correctly. Nowreduce
is implemented usingscan
. When reducing on empty observable, it will invokeonCompleted
and do nothing. This is against my expectation. I suppose that reducing an empty observable should throw an exception.Actually,
Scan
andAggregate
(I think this isreduce
in C#) have different behaviors in C#.Scan
an empty observable will do nothing, butAggregate
will throw aSystem.InvalidOperationException
.Here are my test codes in C#.
Scan:
Aggregate:
I also tried the
reduce
method in other languages.List[Int]().reduce(_ + _)
will throwjava.lang.UnsupportedOperationException: empty.reduceLeft
in scala.reduce(lambda x, y: x + y, [])
will throwreduce() of empty sequence with no initial value
in python.If reducing an empty observable throws an exception, we can implement
min
andmax
byreduce
directly.The text was updated successfully, but these errors were encountered: