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

Naming of Create/Bind/Operator Functions and Types #775

Closed
benjchristensen opened this issue Jan 22, 2014 · 37 comments
Closed

Naming of Create/Bind/Operator Functions and Types #775

benjchristensen opened this issue Jan 22, 2014 · 37 comments

Comments

@benjchristensen
Copy link
Member

Changes are being done in #770 as a result of discussion in #746 that add a new bind operator and change the create signature to inject a Subscription instead of returning it. The reasons for this change can be seen at #746.

The names have thus far been left as is or as raw functions while achieving the right functionality. Before releasing and becoming part of the published API I'd like to finalize the names (some of which have been disputed).

1) "source" Function

Action1<Operator<? super T>>

This is the single function that exists inside an Observable that is executed upon subscription (when subscribe is invoked).

The previous function was called OnSubscribeFunc<T>.

We could just keep the Action1 signature but the generics are obnoxious to remember, type and code-complete. Having a specific type is for ease of use.

This will be the most used of the types discussed in this issue as all Observable.create usage will involve this type. Most users of RxJava will use Observable.create.

A possibility is:

public static interface OnSubscribe<T> extends Action1<Operator<? super T>> 

or it could more explicitly (and like OnSubscribeFunc) be:

public static interface OnSubscribe<T> extends Function {
        public void onSubscribe(Operator<? super T> op);        
}

What other names could this be?

2) "bind" or "lift" Method

The new method added to Observable has a name and signature like this:

public <R> Observable<R> bind(final Func1<Operator<? super R>, Operator<? super T>> bind)

The intent is to perform function composition and return a new Observable representing the composition.

Since my computer science, lambda calculus etc is not the greatest, I was calling this bind but after discussion with others it seems this is probably better called lift as per functional programming principles.

That said, there is nothing requiring us to use names such as these.

The intent of this method is to chain together operators for transforming, combining, filtering and operating on data flowing through an Observable and each time it's chained it returns a new Observable.

For example:

Observable<U> ou = Observable<T>.chain(operatorA);
Observable<V> ov = Observable<U>.chain(operatorB);
Observable<W> ow = Observable<V>.chain(operatorC);

Typically this would be used like this:

Observable<W> ow = Observable<T>.chain(operatorA).chain(operatorB).chain(operatorC);

Here are possible names:

public <R> Observable<R> bind(Function<T, R> f)
public <R> Observable<R> lift(Function<T, R> f)
public <R> Observable<R> compose(Function<T, R> f)

What is the correct name for this?

3) "bind" or "lift" Function

The function that the currently named bind takes is:

Func1<Operator<? super R>, Operator<? super T>>

This could be left as is and will generally only be used by people implementing operators. Thus, it is not in the "common path" for using Rx.

However, it could benefit from the clearer communication of a specific type and name.

Possibilities include:

OnBind<R, T>
OnLift<R, T>
BindFunction<R, T>
LiftFunction<R, T>
Composition<R, T>

Should we give this a specific type and name? If so, what should it be?

4) Operator class

As part of the new bind and create functionality that injects Subscriptions into the source function, a new type Operator was created that combines Observer and Subscription.

public abstract class Operator<T> implements Observer<T>, Subscription

Is Operator the correct name? If not, what should it be called?


Possible Outcomes

// use `OnSubscribe` for the "source" function
public final static <T> Observable<T> create(final OnSubscribe<T> f);

// use `compose` and `Composition` as user friendly representations for the `bind`/`lift` functionality
public <R> Observable<R> compose(final Composition<R, T> cf);

// leave `Operator` as is
public abstract class Operator<T> implements Observer<T>, Subscription

or

// use `OnSubscribe` for the "source" function
public final static <T> Observable<T> create(final OnSubscribe<T> f);

// use `compose` for the name but leave the function
public <R> Observable<R> compose(final Func1<Operator<? super R>, Operator<? super T>> cf);

// leave `Operator` as is
public abstract class Operator<T> implements Observer<T>, Subscription

or

// use `OnSubscribe` for the "source" function
public final static <T> Observable<T> create(final OnSubscribe<T> f);

// use `lift`
public <R> Observable<R> lift(final Func1<Operator<? super R>, Operator<? super T>> cf);

// leave `Operator` as is
public abstract class Operator<T> implements Observer<T>, Subscription

etc


My intent is to solidify the naming before releasing 0.17.0 as these are not easy to change once released.

Please provide your suggestions and reasoning either to support one of the options above or for something new.

Thank you!

This was referenced Jan 22, 2014
@akarnokd
Copy link
Member

How about

Observable<T> o = Observable.<T>with(operatorA).with(operatorB).with(operatorC);

@headinthebox
Copy link
Contributor

public Observable bind(final Func1<Operator<? super R>, Operator<? super T>> bind)

This is actually an instance of map since it takes (Operator->Operator) to (Observable->Observable), so "lift" would be the geek name. I like @akarnokd proposal to use leverage English to emphasize the fluent style. Using "with" sounds OK.

@headinthebox
Copy link
Contributor

Func1<Operator<? super R>, Operator<? super T>>

For this one, I actually like keeping Func1; otherwise I'd have to unfold the synonym. That is because I remember

(Operator<S>->Operator<T>) ->(Observable<T>->Observable<T>)

That is easier than

(XXX<S,T>) -> (Observable->Observable)

@headinthebox
Copy link
Contributor

public static interface OnSubscribe extends Action1<Operator<? super T>>

I like this one. With

public static interface OnSubscribe<T> extends Function {
        public void onSubscribe(Operator<? super T> op);        
}

I need to mentally translate too much.

@headinthebox
Copy link
Contributor

public abstract class Operator implements Observer, Subscription

Can't we just make Observer implement Subscription? Less types is better.

@headinthebox
Copy link
Contributor

This would be my choice. As I said not sure the last one works.

// use `OnSubscribe` for the "source" function
public final static <T> Observable<T> create(final OnSubscribe<T> f);

// use `with` for the name but leave the function
public <R> Observable<R> with(final Func1<Observer<? super R>, Observer<? super T>> cf);

// Modify Observer
public abstract class Observer<T> implements  Subscription { .... }

@samuelgruetter
Copy link
Contributor

Note that with is a keyword in Scala, so in Scala we'll need a different name.

@headinthebox
Copy link
Contributor

What would be a good word for "before". Since "bind" is contravariant, it chains the transformations in reverse order. In my younger days when Algol 68 was all the rage if then else fi, while do od, case esac, I would propose neht, but I am sure that this would not fly in 2014 ;-)

@duarten
Copy link

duarten commented Jan 22, 2014

I'm not sure if I like having Observer implementing a Subscription. Now you can have the same Observer instance subscribed to more than one Observable if you so wish, and what you unsubscribe is the Observable <-> Observer association. I guess it would be okay if Observer were to become only an internal type. Also, there might be some strangeness with Subjects.

@abersnaze
Copy link
Contributor

After playing with this last night I'm beginning to like the idea of changing Observable to a abstract class that extends CompositeSubscription. The two argument creators on Operator seem awkward. Does it make sense to allow the same Subscription was used to create two different Operators with different Observables or vice versa? If Observable was the Operator you wouldn't need any of that.

Changing from an interface to an abstract class would minimize the breakiness to just forcing everyone to switch from implements to extends for just named classes (anonymous inner classes wouldn't need to change).

Come to think of it are there any other Subscription types? Should we think of this as renaming CompositeSubscription to Observer and adding three abstract methods onNext,onError,onCompleted?

@benjchristensen
Copy link
Member Author

I too don't think making Observer extend Subscription is a good approach.

1) Conflates Responsibility

When using an Observable it is rare that someone needs to concern themselves with the Subscription unless they are implementing an operator. By making Observer extend Subscription we would be forcing all users to always concern themselves with it.

What does implementing unsubscribe even mean when just providing an Observer to an Observable.subscribe(Observer) when one just wants to subscribe to the data?

This is what we (users) would have to implement:

    new Observer<String>() {

        @Override
        public void onCompleted() {

        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onNext(String args) {

        }

        public final void unsubscribe() {
            // what to do here?
        }

    }

In most cases I don't know what the unsubscribe method should do. It certainly wouldn't be implemented correctly.

Contrast this with the current Observable.subscribe which handles the subscription:

public final Subscription subscribe(Observer<? super T> observer)  {
   ... simplified ...
   Operator<T> op = Operator.create(observer, new CompositeSubscription());
   subscribe(op);
   return op;
}

This is not logic that most users of Rx should ever have to worry about.

I feel that Observer and Subscription should be kept separate and that we have a type such as the current Operator that combines them when needed.

2) Major Breaking Change

This would be a massive breaking change to anyone using RxJava. There is no mechanism for making it backwards compatible or providing a deprecation strategy.

@abersnaze
Copy link
Contributor

I'm not sure if you were replying directly to my statement. What about making Observer and abstract class where unsubscribe() in implemented for you? In fact it should probably be final so that users would have to add(Subscription) to register a unsubscribe callback when needed.

@benjchristensen
Copy link
Member Author

I'm not sure if you were replying directly to my statement.

I was replying to @headinthebox but it applies to yours as well (we posted within minutes of each other).

What about making Observer and abstract class where unsubscribe() in implemented for you?

Perhaps, but it's still a pretty major breaking change.

I can see some limited benefit in an Observer being able to call unsubscribe, but that is supported by the subscribe(Operator) interface and it's very rare that people are doing that in the final Observer since they generally should be using things like take and takeUntil for controlling when an Observable is unsubscribed.

The problem obviously is that we now have 2 ways of subscribing:

public Subscription subscribe(Observer<? super T> observer)

and

public void subscribe(Operator<? super T> o)

So the real question is whether we are willing to have a massive breaking change where we eliminate all of the subscribe methods that return Subscription. And if so, why are they called subscribe anymore? Perhaps just observe?

If we were starting from scratch I wouldn't have the Subscription subscribe(Observer) methods, only void observe(Observer)

@benjchristensen
Copy link
Member Author

Another problem I just considered ... Subject must extend from from Observable and implement Observer. Java doesn't support multiple-inheritance so we can't have Observer be an abstract class.

Thus, if we make Observer extend Subscription it can only be an interface which means everyone must implement unsubscribe() which we don't want.

@benjchristensen
Copy link
Member Author

What exactly is wrong with the name Operator since everyone casually calls them "operator" anyways and the package they all live in is rx.operators?

@headinthebox
Copy link
Contributor

Putting the MI part aside, I a do not agree with
What does implementing unsubscribe even mean when just providing an Observer to an Observable.subscribe(Observer) when one just wants to subscribe to the data?
It just means don't do anything. Just like now when you do an observable.create and return a subscription by subscribing to another observable. In .NET enumerator inherits IDisposable, and in most cases the IDisposable just does nothing.

I see absolutely nothing wrong with that.

Now most of the time people do not even pass observers directly but just the onNext, onError, or onCompleted functions to most of the time they won't even notice.

@headinthebox
Copy link
Contributor

What exactly is wrong with the name Operator

http://en.wikipedia.org/wiki/Operator_(programming)

An operator is typically a Function3.

@headinthebox
Copy link
Contributor

I would say, keep just one subscribe

 Subscription Subscribe(Observer) 

Instead of returning void there is no harm in returning the (augmented) subscription in both bases.

@headinthebox
Copy link
Contributor

Now you can have the same Observer instance subscribed to more than one Observable

That is not a smart idea anyway, not sure if that would always guarantee that calls to OnNext are serialized if they get called from tow independ observables.

@benjchristensen
Copy link
Member Author

This is the signature that starts the Observable:

void subscribe(Observer&Subscription o)

The Subscription specifically needs to be of type CompositeSubscription and is the seed for the entire sequence.

Also, it must have the add and isUnsubscribed methods:

    private final CompositeSubscription cs;

    public final void add(Subscription s) {
        cs.add(s);
    }

    public final void unsubscribe() {
        cs.unsubscribe();
    }

    public final boolean isUnsubscribed() {
        return cs.isUnsubscribed();
    }

Thus, a user can not just pass in an empty unsubscribe. They must pass in the exactly correct implementation, which is not at all what a user should have to do.

This is why subscribe(Observer) does this internally:

Operator op = Operator.create(observer, new CompositeSubscription());
subscribe(op)

If Observer implements Subscription it would not change this, as CompositeSubscription is more than the Subscription interface.

Thus we would now have users implementing an unsubscribe method with no hook to the thing it should unsubscribe from.

        subscribe(new ObserverThatExtendsSubscription<String>() {

            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(String s) {
                // receive data
                if(s == null) {
                     unsubscribe();
                }
            }

            public void unsubscribe() {
                // what do I do here?
                // I want to do this =>
                parentSubscription.unsubscribe();
                // but there is no hook to a "parentSubscription"
            }

        }

The problem is that if I'm implementing just an interface, there is no "parentSubscription" passed in that I can do something with. Thus, what would someone do inside the unsubscribe method they implement in their Observer? There is nothing they can do.

The only way I see to make this work is to replace the Observer interface with the abstract class Operator. This would indeed work as then it is a legit Subscription backed by a CompositeSubscription.

But it won't work with Subject as Java doesn't support multiple inheritance and Subject needs to extend both Observable and Observer.

Using this abstract class would work exactly like the current Operator class does, like this:

        subscribe(new OperatorOrObserver<String>() {

            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(String s) {
                // receive data
                if (s == null) {
                    unsubscribe(); // this will now work
                }
            }

        });

This would work great, except for (1) it's a massive breaking change and (2) Subjects would all break.

@duarten
Copy link

duarten commented Jan 22, 2014

That is not a smart idea anyway, not sure if that would always guarantee that calls to OnNext are serialized if they get called from tow independ observables.

Agreed. But it's additional flexibility that comes from separating those ideas. And the Subscription Subscribe(Observer) signature would be misleading.

Another benefit is that now a subscription can be shared between a chain of observers, whereas otherwise each operator would have to cancel the parent observer, which in turn would cancel its parent, and so forth.

@headinthebox
Copy link
Contributor

But it won't work with Subject as Java doesn't support multiple inheritance and
Subject needs to extend both Observable and Observer.

One way around that would be to make it have a toObservable method.

@benjchristensen
Copy link
Member Author

Talking with @headinthebox and @abersnaze we have discussed taking these changes all the way instead of part-way as done so far and doing the following:

Change interface Observer<T> to an abstract class:

// Observer & Subscription 
public abstract class Observer<T> implements Subscription {

    public static create(onNext);
    public static create(onNext, onError);
    public static create(onNext, onError, onCompleted);

    abstract void onNext(T);
    abstract void onError(Throwable);
    abstract void onCompleted();

    final add(Subscription);
    final unsubscribe();
    final boolean isUnsubscribed();
}

This would effectively be the same class as is currently Operator.

Then Subject would change to:

Subject<T> extends Observer<T> {

    public Observable<T> toObservable();

}

This would allow subscribe(Operator) and subscribe(Observer) to collapse into one.

The benefit of this is:

  • no new types, just Observer
  • it forces subscribing "the right way" where an Observer can unsubscribe even on synchronous data

Regarding the naming of bind we decided that bind is not the correct name as it is not exactly a monadic bind. It is in fact a lift or transform. We feel it is likely better to stick to proper CS or mathematical names for this function.

Thus it will be:

// lift 
public <R> Observable<R> lift(final Func1<Observer<? super R>, Observer <? super T>> cf);
... or ...
public <R> Observable<R> transform(final Func1< Observer <? super R>, Observer <? super T>> cf);

This means version 0.17 will be a significant breaking release. We will leverage this to do all of the breaking changes here including removing deprecated methods and changing Scheduler to explicitly have Inner/Outer.

@duarten
Copy link

duarten commented Jan 22, 2014

Would it make sense for Observer to extend from CompositeSubscription, as it will have those semantics?

Being able to do Observer.create({ }).add(Observer.create{..}) still feels a bit weird. I wonder if this approach is just due to a limitation of Java's type system, as we can't express Observer with Subscription without explicitly introducing a new type.

@benjchristensen
Copy link
Member Author

Would it make sense for Observer to extend from CompositeSubscription, as it will have those semantics?

I don't think we want to expose the clear and remove methods that CompositeSubscription has. Also I consider the use of CompositeSubscription inside Observer an implementation detail. We could use something different if it makes sense so I don't want to commit long-term to CompositeSubscription.

Observer.create({ }).add(Observer.create{..})

It looks odd like that but there are actually use cases where I think that's more-or-less what we'll do (zip, groupBy, merge) albeit with a little more code involved and not directly chained like that.

There are always things we can do that shouldn't be done, such as this infinite loop with a Subject:

Subject<String, String> s = PublishSubject.create();
s.subscribe(s);

This fits into the "don't do that" category. Similar to anInfiniteObservable.toList().

I wonder if this approach is just due to a limitation of Java's type system

I don't know regarding this specific decision. We are limited in our implementation design though due to things such as lack of extension methods which drives us to use abstract class instead of interface for Observable and now Observer and then that affects Subject since we can't have multiple inheritance.

benjchristensen added a commit to benjchristensen/RxJava that referenced this issue Jan 23, 2014
benjchristensen added a commit to benjchristensen/RxJava that referenced this issue Jan 23, 2014
@benjchristensen
Copy link
Member Author

Here is my branch working on this refactor as a preview of the changes: https://github.com/benjchristensen/RxJava/commits/lift-observer

The biggest issue I now have is figuring out how to make the unit tests work since mock(Observer.class) can no longer be used (the abstract method constructor is not invoked and thus the subscription state is null). Probably going to sleep on it ... enough carpal tunnel pain on tedious refactoring for one night.

@akarnokd
Copy link
Member

I don't quite understand why Observer and Subscription needs to be merged this way. I got pretty "far" with one of the previously suggested structures:

private final Action2<Observer<T>, CompositeSubscription> onSubscribe;
    private Obsurvable(Action2<Observer<T>, CompositeSubscription> onSubscribe) {
        this.onSubscribe = onSubscribe;
    }
    public Subscription subscribe(Observer<T> obsurver) {
        CompositeSubscription token = new CompositeSubscription();
        onSubscribe.call(obsurver, token);
        return token;
    }
    public Subscription subscribe(Observer<T> obsurver, CompositeSubscription token) {
        onSubscribe.call(obsurver, token);
        return token;
    }
    public static <T> Obsurvable<T> create(Action2<Observer<T>, CompositeSubscription> onSubscribe) {
        return new Obsurvable<>(onSubscribe);
    }
    public <U> Obsurvable<U> bind(Func2<Observer<U>, CompositeSubscription, Observer<T>> binder) {
        return new Obsurvable<>((o, t) -> onSubscribe.call(binder.call(o, t), t));
    }

This way, both create((u, k) -> { }) and bind((u, k) -> t) offers a composite subscription k which can be naturally closed over with an Observer (or that of the Operator if one really wants to reduce inner class clutter). The "when to unsubscribe" remains largely the same.

@benjchristensen
Copy link
Member Author

It definitely works on the previous signatures. I was arguing for leaving Observer as is but @headinthebox and @abersnaze convinced me to pursue changing Observer. The argument was for ...

  1. Having a signature that only dealt with Observer rather than Observer and some other Subscription implementation.

In other words, specifically avoiding methods like you show:

(Observer<T> obsurver, CompositeSubscription token)
(Action2<Observer<T>, CompositeSubscription>)
(Func2<Observer<U>, CompositeSubscription, Observer<T>>)

That is where I started this journey at, and it works just fine as you say. Making Observer implement Subscription is taking it to the extreme possibly end state as I see it, allowing the signatures to be simpler like this:

(Observer<T> obsurver)
(Action1<Observer<T>)
(Func1<Observer<U>, Observer<T>>)
  1. Elegance due to simpler signatures while it ends up "doing the right thing" in all cases rather than a user having to choose the right signature if they want unsubscribe support.

It's quite nice now that any subscribe(Observer o) can call unsubscribe() from within an onNext() method. Any user of Rx can do that with this Observer implements Subscription design whereas the code on master right now requires them choosing a different subscribe overload.

The "when to unsubscribe" remains largely the same.

Yes. This last set of changes is purely one of semantics and signature design. The change in functionality is already what was merged into master that added the bind/lift function. It basically was the discussion with @headinthebox that if we were starting from scratch with this lift model we wouldn't have combined Observer and Subscription so how about let's try and get it all refactored to that model now before we hit 1.0 instead of keeping signatures of "old" and "new".

@Ladicek
Copy link

Ladicek commented Feb 4, 2014

Apparently, I'm too late to the party (just seen the 0.17 release notes preview), but I wanted to say it anyway: the mathy-sciency-geeky lift thing sounds scary to me. I'm a programmer. I don't want to care about category theory. I want to chain method calls. Give me chain or with and I'm a happy guy. I could understand andThen or compose, but I get a little bit nervous with bind. With lift, I'm just put off. Thanks for listening to this complaint :-)

@benjchristensen
Copy link
Member Author

the mathy-sciency-geeky lift thing sounds scary to me

I understand this sentiment. I'm torn on this one but there is a principle that pushed us towards using lift. We try hard not to re-invent new names for things when they already have names. Arbitrarily putting "plain english" names on top of things does not really help our industry as then we have multiple synonyms for the same concept or functionality and moving across languages and libraries is made more difficult than it should be.

Part of the goal for Rx is to be consistent across platforms and languages which is why we work with @headinthebox and @mattpodwysocki to try and stay as close as we can. As we have pursued this path with lift and Subscriber @headinthebox has mediated across languages.

Another aspiration is to not try and redefine what operators and functions mean. If we are using capabilities from "functional programming" or "math" it is our perspective that it is better for us to learn and use the proper names for things so we can speak the same language.

Take the map function for example: http://en.wikipedia.org/wiki/Map_(higher-order_function) That has become common in imperative languages even though the name originates from functional programming and is not plain english like "transform".

Last, we have avoided adding new types as much as possible and let the functional interfaces stay as such in most places such as Func1<Subscriber<? super R>, Subscriber<? super T>>. As seen in #792 we tried very hard to not end up adding the new Subscriber type but the cost of not having it was worse than adding the type.

All that said ...

With lift, I'm just put off.

... what specifically puts you off about this?

Despite not liking the concept of alias methods, I'm not against having one if it is truly a blocker for users. Nor is the name lift locked in stone yet as we have not yet released nor was there much feedback (though @headinthebox has a very big say in this matter and I invite his input on this).

@Ladicek
Copy link

Ladicek commented Feb 4, 2014

Ben, first let me say that I very much appreciate your detailed explanation. I beg to differ, though. With RxJava, you are a bit in a special position, as you are bringing Rx to masses of Java programmers (which is a big deal itself), and you are also innovating on Rx at the same time (this Operator+lift thing is new and doesn't exist in other Rx implementations, AFAIK). This requires careful API design, of which naming things is an important part. Of course it's a highly subjective matter and in the end, it's your call; I just wanted to present a different opinion.

What I've seen is that APIs in mainstream-ish languages tend to name things differently from the computer science "upstreams". map is a bit of an exception, as it kinda makes sense as a synonym for transform (transformation is a mapping from source to target). Though it's called transform somewhere and even select elsewhere (in LINQ and Rx.NET/Rx.JS, where map is defined as an alias). And looking at monadic bind, that's called flatMap in Scala and most other mainstream-ish languages/libraries, LINQ and Rx.NET/Rx.JS call it selectMany (Rx defines a flatMap alias) and Dart uses expand (which is IMHO almost perfect). fold is often called reduce (or, like in Scala, reduce is a special case of fold) or even aggregate. Et cetera. So again, I believe it's common to diverge from functional/CS terminology in mainstream, and I guess that the reason is understandability.

Regarding lift: looking at http://en.wikipedia.org/wiki/Lift_(mathematics), it looks like for practical purposes, lift is a function that is used in function composition. Eh... come on... What we have here is a chain of method calls that creates a chain of operators (which is a great name, BTW!). And what we are looking for is a name of a method that adds a previously-unknown operator to the chain. To me, lift doesn't come even close. It's more abstract, yes, but more abstract doesn't necessarily mean more good. For me, [part of] Rx's awesomeness lies in its accessibility to masses. Abstraction over abstraction is usually an opposite of that.

@benjchristensen
Copy link
Member Author

@Ladicek Your make a strong argument and I'm willing to concede and make a change that benefits ease of adoption and comprehension.

I'd like others to weigh in on this ... so /cc @samuelgruetter @jmhofer @zsxwing @mairbek @mattrjacobs @abersnaze @akarnokd @michaeldejong @mttkay @JakeWharton @loganj @adriancole

The proposal is to change from:

public <R> Observable<R> lift(final Func1<Subscriber<? super R>, Subscriber<? super T>> lift)

to

public <R> Observable<R> chain(final Func1<Subscriber<? super R>, Subscriber<? super T>> chain)

The use is for custom operator chaining such as:

Observable<String> os = observable_of_integers.chain(TAKE_5).chain(MAP_INTEGER_TO_STRING);

More information about the background on this can be found at #802 in summary form.

@loganj
Copy link
Contributor

loganj commented Feb 4, 2014

@benjchristensen Thanks for asking. For what it's worth, I disagree with @Ladicek's reasoning, if not the proposed change.

There are a number of good criteria for choosing a function name, but whether or not it's "scary" because it has the whiff of math about it doesn't strike me as one of them.

lift has the advantage of actually being a well-known name for that function. People familiar with it will immediately know what it is and how to use it. People who are not will have a body of knowledge to draw from if they go searching.

That said, chain is also a perfectly fine name, and you could always make lift an alias or just mention it in the javadoc.

@benjchristensen
Copy link
Member Author

Thanks for your input @loganj I appreciate you taking the time to weigh in and you make good points.

@Ladicek
Copy link

Ladicek commented Feb 5, 2014

Thanks @loganj for your input. I agree that "it looks scary" is a lousy argument. However, it looks scary enough to make me join this discussion. Don't underestimate the power of fear :-)

Anyway, let me rephrase an argument I'm trying to make here. I argue that it's common for mainstream to have a different terminology from the math/computer science origins. I hope you will agree with me on that (we don't necessarily have to agree on whether it's a good thing or not). And I argue that we shouldn't deviate from the common practice.

One other example of importance of good naming. Dart's core library (dart:async) has a class called Stream, which is essentially Observable, and they have these two methods called map and transform. I like the name Stream, I like their map (it's the same as ours map), but I hate their transform. It's not an alias for map, it's this exact bind/lift/chain thing. TBH, I'd much rather have it called lift than transform :-)

@akarnokd
Copy link
Member

akarnokd commented May 8, 2014

I think this is settled.

@headinthebox
Copy link
Contributor

yup.

jihoonson pushed a commit to jihoonson/RxJava that referenced this issue Mar 6, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants