Skip to content
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

Add Observable.fromCallable() as a companion for Observable.defer() #3154

Merged
merged 1 commit into from
Sep 4, 2015

Conversation

artem-zinnatullin
Copy link
Contributor

Yep, this is a new operator.

Motivation? Observable.defer() requires function that returns Observable<T> when usually we don't want to create Observable<T> manually, we just want to defer execution of some function.

Like this:

Observable
  .fromCallable(() -> someFunc())
  .subscribeOn(...)
  ...

Instead of this:

Observable
  .defer(() -> Observable.just(someFunc()))
  .subscribeOn(...)
  ...

And more important case with deferring code that throws checked exceptions:

Observable
  .fromCallable(() -> {
    Value value = ...;
    // some code
    return value;
  })
 .subscribeOn(...)
 ...

Instead of this:

Observable
  .defer(() -> {
    try {
      Value value = ...;
       // some code that throws checked exceptions
      return Observable.just(value);
    } catch (Exception e) {
      return Observable.error(e);
    }
  })
 .subscribeOn(...)
 ...

I'd use name defer but both methods will have same type erasure (Func0<Observable> and Func0<T>), so I had to use a different name.

Questions:

  1. Useful or not? For me — deferring some function call is a common task.
  2. Naming.

If the decision about this operator will be positive — I'll add javadoc to this PR and then create separate PR for Single.fromCallable().

@artem-zinnatullin
Copy link
Contributor Author

Also, I see that for Rx newcomers (some of my coworkers, for example) is not very clear why they need to return Observable if they just want to defer some code.

@davidmoten
Copy link
Collaborator

Sounds ok to me. A name using the defer prefix might be nice for discoverability purposes.

@artem-zinnatullin
Copy link
Contributor Author

We can call it from(), I guess it will be nice to write Observable.from(() - > someFunc()) and even better with method Observable.from(this::someFunc())!

@akarnokd
Copy link
Member

We can't call it from because of potential conflict in dynamic languages. I'd prefer start or startAsync .

@@ -1069,6 +1069,10 @@ public void call(Subscriber<? super R> o) {
return create(new OnSubscribeDefer<T>(observableFactory));
}

public static <T> Observable<T> lazy(Func0<T> lazyFunc) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Func0<? extends T>

@experimental

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@artem-zinnatullin
Copy link
Contributor Author

We can't call it from because of potential conflict in dynamic languages. I'd prefer start or startAsync .

Observable.start() will be confusing because actually it won't start until you subscribe to it.

Observable.startAsync() is also confusing and it can be non-async if you won't add subscribeOn().

If we can not use defer() I'd prefer lazy() mm? cc @benjchristensen

@abersnaze
Copy link
Contributor

Whatever the name is (deferJust(Func0<T>)?) I would like the implementation to return a subclass of ScalarSynchronousObservable that overrides the T get() method with a call to the closure passed in.

@artem-zinnatullin
Copy link
Contributor Author

Operator is ready for next review!

Sounds ok to me. A name using the defer prefix might be nice for discoverability purposes.

Another naming suggestion: deferFunc(), it will be obvious for those who used defer(), but it will be strange a little because defer is not deferObservable

@artem-zinnatullin
Copy link
Contributor Author

@abersnaze

I would like the implementation to return a subclass of ScalarSynchronousObservable

Not sure about this, because I don't know what variant will be better (more efficient, maintainable, etc) SingleDelayedProducer or ScalarSynchronousObservable, @akarnokd I'd like to hear your opinion on this if you don't mind.

@abersnaze
Copy link
Contributor

There is special logic in merge() link to code to unwrap ScalarSynchronousObservable by invoking the get() method directly and avoiding all the overhead of creating a Subscriber and SingleDelayedProducer to just get the value. This should make flatMap of these very fast.

@akarnokd
Copy link
Member

You can't return the scalar observable because the value has to be ready at assembly time and be constant. Any side effect would only happen once as well.

@@ -1069,6 +1069,11 @@ public void call(Subscriber<? super R> o) {
return create(new OnSubscribeDefer<T>(observableFactory));
}

@Experimental
public static <T> Observable<T> lazy(Func0<? extends T> lazyFunc) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd consider Callable to allow IO operations without the need to do try-catch. An overload won't work due to dynamic languages.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I've thought about this too, I'll take a look what I can do after work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@abersnaze
Copy link
Contributor

@akarnokd I'll try to send in an example of what I mean but I don't see any reason why T get() can't call a Func0<T>. It would still be Scalar and Synchronous but there isn't anything that says that it has to be constant.

@akarnokd
Copy link
Member

Instead of modifying ScalarSynchronousObservable, you could add a new case to the type check in merge.

@abersnaze
Copy link
Contributor

check out #3163 (made it PR so it can be discussed over there).

@akarnokd
Copy link
Member

There are several problems with your proposal

  • increased likelihood of bimorphic/megamorphic dispatch at the call sites of get()
  • get() can now throw which is unexpected at the call sites
  • calling the func might carry side effects and calling it during assembly time is incorrect

@akarnokd
Copy link
Member

👍

@artem-zinnatullin
Copy link
Contributor Author

Switched to Callable (available in JDK since 1.5, so it's okay), now you can defer function that potentially throws checked exception!

I hope Observable.lazy() (or whatever name it will have) will be the easiest way to create Observable that should emit one item only after subscribe() (which is pretty common Rx task).

So, I guess it's time to select the name for this operator and add javadoc.

@stealthcode
Copy link

I would also like to see the observables produced by lazy take advantage of a path similar to ScalarSynchronousObservable (statically allocated and statically available data). I have an idea that I'd like to explore more and I'll open another pull request that enables this for both Observable.defer and Observable.lazy (assuming this gets merged).

@artem-zinnatullin
Copy link
Contributor Author

Added javadoc and squashed.

P.S. Today I used Observable.defer() ~20 times and each time I could use Observable.lazy() and save some lines of code, would be nice to see it in 1.0.15!

@staltz
Copy link
Member

staltz commented Aug 21, 2015

All Observables are lazy. Why can't you just use Observable.create()?

@akarnokd
Copy link
Member

@staltz That would conflict with create(OnSubscribe) in dynamic languages.

My new name proposals:

  • deferJust
  • deferSingle
  • deferScalar

@dimsuz
Copy link
Contributor

dimsuz commented Aug 21, 2015

deferSingle sounds nice to me.
Also thought of deferValue.

@artem-zinnatullin
Copy link
Contributor Author

All Observables are lazy.

It's not true, in Java (and all JVM langs that I know) argument of a method computed before actual invocation of this method.

For example if you have method Value computeValue() { ... } and you want to make an Observable from it, you can use Observable.just(computeValue()) but the problem is that computeValue() will be computed in the thread that called Observable.just() and not on the Scheduler that you specify via subscribeOn().

So, in my opinion just() and from are not "lazy".

deferJust looks good. deferSingle may conflict with Single for RxJava users.

@headinthebox
Copy link
Contributor

Sorry to be late into the game here, but it seems you really want an overload of just that has side-effects, i.e. reruns a factory function every time you subscribe to produce the value. I think the name should reflect that.

I am surprised this is a common scenario since it feel super narrow. Personally I think the longer defer code conveys the intent perfectly. Plus it also works if you want to do generate more than one value

Observable
  .defer(() -> {
    Value value0, value1, ...valuen-1 = ...;
    // some code
    return Observable.just(value0, ..., valuen-1);
  })
 .subscribeOn(...)

Anyway, just my 2 cents.

@artem-zinnatullin artem-zinnatullin changed the title Add Observable.lazy() as a companion for Observable.defer() Add Observable.deferJust() as a companion for Observable.defer() Aug 25, 2015
@artem-zinnatullin
Copy link
Contributor Author

Renamed operator from lazy() to deferJust().

I am surprised this is a common scenario since it feel super narrow. Personally I think the longer defer code conveys the intent perfectly.

Surprise surprise — nobody likes boilerplate. Also, if you need to defer function that throws checked exception via defer() you have to deal with try/catch, deferJust() handles it for you.

There is even post from @dlew Deferring Observable code until subscription in RxJava which, I guess, makes this problem more visible.

@headinthebox
Copy link
Contributor

You have just solved the boilerplate for one very particular case that does not generalize to the other overloads of just, and increased the concept count. But if it makes you happy, it can't be that bad.

@dlew
Copy link

dlew commented Aug 26, 2015

I'm not a fan. I prefer composability. You can easily compose existing operators to recreate deferJust. They're in the name.

@akarnokd
Copy link
Member

I called this operator fromCallable in 2.x where the from operators are suffixed by the type to be very specific and to avoid overload resolution problems.

@artem-zinnatullin
Copy link
Contributor Author

Nice and understandable name 👍 Renamed to fromCallable() to make a switch from v1 to v2 easier.

@artem-zinnatullin artem-zinnatullin changed the title Add Observable.deferJust() as a companion for Observable.defer() Add Observable.fromCallable() as a companion for Observable.defer() Aug 27, 2015
@benjchristensen benjchristensen modified the milestone: 1.0.x Aug 28, 2015
@artem-zinnatullin
Copy link
Contributor Author

@akarnokd @benjchristensen is there anything that blocks this PR?

@akarnokd
Copy link
Member

akarnokd commented Sep 3, 2015

I'm totally in favor of this addition so we can start promoting it instead of create with a single result so commonly found. We are still waiting for the second approve from another member.

@artem-zinnatullin
Copy link
Contributor Author

we can start promoting it instead of create with a single result so commonly found.

Yes!

I see some confusion when newbies hear: "To defer some code and turn it into Observable you need to use Observable.create() (which is pretty tricky and boilerplate) or create Observable via just() or from(), etc and defer it via Observable.defer()".

I literally can read "wtf"s in their eyes when they ask — "So, I need to create Observable to defer an Observable, what?", this thing must be easier :)

@stealthcode
Copy link

👎 this is a less general use case than an Observable.just() or a defer that returns a just and it does not benefit from the merge optimizations.

@akarnokd
Copy link
Member

akarnokd commented Sep 3, 2015

We can always optimize later.

@stealthcode
Copy link

I see the compelling usability need (easy to type, simple to find) so I see that users would adopt it but it would detract from the optimized path. I don't think this will perform well if its adopted in the Netflix ecosystem. Many other users here have raised objections as this use case can be handled with existing operators and that composition would skip the merge ring buffer allocation/draining.

@akarnokd
Copy link
Member

akarnokd commented Sep 3, 2015

That's not how the merge optimizations work.

@stealthcode
Copy link

Oh, you're right. Merge can't optimize either path. At least not as a scalar. It still has to subscribe to de defer. My mistake.

@stealthcode
Copy link

My objections were on faulty assumptions. I take back my -1. 👍

@akarnokd
Copy link
Member

akarnokd commented Sep 4, 2015

Great!

akarnokd added a commit that referenced this pull request Sep 4, 2015
Add Observable.fromCallable() as a companion for Observable.defer()
@akarnokd akarnokd merged commit c451491 into ReactiveX:1.x Sep 4, 2015
@artem-zinnatullin artem-zinnatullin deleted the defer-for-humans branch September 5, 2015 18:53
@jenzz
Copy link

jenzz commented Sep 6, 2015

Great addition! Thanks 👍

@benjchristensen
Copy link
Member

so we can start promoting it instead of create with a single result so
commonly found.

That is generally why Single exists. It still has its place on
Observable, but if just single responses are being done, such as for RPC,
then Single makes more sense.

On Sun, Sep 6, 2015 at 2:13 AM, Jens Driller notifications@github.com
wrote:

Great addition! Thanks [image: 👍]


Reply to this email directly or view it on GitHub
#3154 (comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.