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

Is Observable the right name? #31

Closed
tom-sherman opened this issue Jul 28, 2023 · 2 comments
Closed

Is Observable the right name? #31

tom-sherman opened this issue Jul 28, 2023 · 2 comments

Comments

@tom-sherman
Copy link

Now this is a kind of crazy question maybe but thought I'd ask it, I understand Observable is a very common name in the community right now - but is it the right one?

Iterable, Thenable, Iterator, generator - these are all protocols or interfaces. It could be argued, or mistaken by a newcomer, that Observable fits into this category - a protocol or interface that allows something to be "observed".

@benlesh
Copy link
Collaborator

benlesh commented Jul 28, 2023

Naming

I understand Observable is a very common name in the community right now - but is it the right one?

Firmly, yes. This is the name for this exact type across dozens of popular languages (JavaScript, C#, Python, C++, Swift, Kotlin, Java, Ruby, etc, etc). As a defined primitive, it's the name for the type. Very well known and understood.

It could be argued that the interface is a "Subscribable" or something like that... but the type itself is idiomatically called an "Observable" across millions and millions of lines of code in many languages.

Observable as a "dual" of Iterable

Iterable, Thenable, Iterator, generator - these are all protocols or interfaces. It could be argued, or mistaken by a newcomer, that Observable fits into this category

They would argue correctly. At least in the case of Iterable. Which is the "dual" of the Observable interface. There's a lot of talks given about this, but I can deconstruct it here... basically take an Iterable and pull it inside out:

We start with Iterable/Iterator

(I'm using a generic non-JS iterable/iterator pattern below, since JS is pretty avante garde in how iteration works, allocating an object every turn and having a possible "yes I'm done and here's a value" state that is generally ignored in most means of consumption)

interface Iterator<T> {
  hasNext(): boolean;
  next(): T;
}

interface Iterable<T> {
  iterator(): Iterator<T>
}

We then push where we were pulling before:

interface Observer<T> {
  hasNext(notDone: boolean): void;
  next(value: T): void;
}

interface Observable<T> {
  observer(observer: Observer<T>): void;
}

We fix the negative notDone, because that's weird

interface Observer<T> {
  complete(done: boolean): void;
  next(value: T): void;
}

interface Observable<T> {
  observer(observer: Observer<T>): void;
}

Which can be fixed further

It doesn't make sense to call complete(false) on every turn. So instead we can just call complete() once and assume that meansit's complete.

interface Observer<T> {
  complete(): void;
  next(value: T): void;
}

interface Observable<T> {
  observer(observer: Observer<T>): void;
}

Error handling

With an iterator, calling next() can throw an error you can catch... but when you're pushing values, it's going to be asynchronous, so we need a different way to communicate that, let's add an error(err: any) channel:

interface Observer<T> {
  complete(): void;
  next(value: T): void;
  error(err: any): void;
}

interface Observable<T> {
  observer(observer: Observer<T>): void;
}

observer isn't a good name for a method

let's call it subscribe. (idiomatically)

interface Observer<T> {
  complete(): void;
  next(value: T): void;
  error(err: any): void;
}

interface Observable<T> {
  subscribe(observer: Observer<T>): void;
}

Cancellation is different

With an Iterator, you can cancel iteration simply by breaking a loop or no longer iterating. With an observable that's different. There are several ways to do this (add/remove registrations, cancellation tokens, or returned cancellation mechanisms like subscriptions)... for this design we're just going to leverage the return value of the subscribe method to return a cancellation mechanism which is a subscription:

interface Observer<T> {
  complete(): void;
  next(value: T): void;
  error(err: any): void;
}

interface Observable<T> {
  subscribe(observer: Observer<T>): Subscription;
}

interface Subscription {
  unsubscribe(): void;
}

@domfarolino
Copy link
Collaborator

Given the really superb explanation here by @benlesh (thanks!) and the fact that if we're being honest, we're probably unlikely to re-litigate the name of this fairly well-known interface, I think I'll close now, especially since we have the reasoning all documented here for other people to see in the future here.

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

3 participants