mobx-utils icon indicating copy to clipboard operation
mobx-utils copied to clipboard

Question about naming and interfaces

Open sonhanguyen opened this issue 6 years ago • 5 comments

I was just curious what was the reasoning behind .value from fromPromise and current() from fromResources and lazyObservable as those names don't appear anywhere else in mobx.

In RxJs most of the return values are Rx.Observable. Coming from there, one would expect these to implement some common interface like boxed values (.get). What's the equivalent predictable interface for the return values inmobx and mobx-utils?

sonhanguyen avatar Nov 14 '17 05:11 sonhanguyen

@sonhanguyen I don't think there is a clear reason for these distinctions. Feel free to PR an alignment :) (preferably a backward compatible one)

mweststrate avatar Nov 23 '17 14:11 mweststrate

RxJS doesn't return Rx.Observable to make things predictable for user, but because it has to. It has to return some common abstraction so it can be used further by library (e.g. observed). But MobX "observability" works without the need to wrap objects into a specific interface. So we can directly return arbitrary object representing specific things without the need to wrap them into some common abstraction and unwrap them later to get the actual object. The difference is that RxJS wraps things with "unpredictable interfaces" into Rx.Observable with predictable interface, while Mobx makes things with "unpredictable interface" directly observable. However Mobx can't make a single value alone observable, therefore it must be wrapped into some "observable box". In such case the uniform accessor like get() make sense. But note that the object returned from fromPromise is not an "observable box" for a single value. Its really a representation of the Promise, with other fields (state) and methods (then,case). I am not sure if get() is appropriate here. I actually think that current() was chosen, because it's less ambiguous than get() in cases where multiple fields/methods are exposed ("get what?").

urugator avatar Nov 23 '17 22:11 urugator

Btw check the current description for initialValue of fromResource in the README.md :)

urugator avatar Nov 23 '17 22:11 urugator

@mweststrate sure let me have a look @urugator I'd say fromX has to return observable as well. To me, It seems the point of these functions is to normalize data sources to a common interface. If those functions simply convert an arbitrary interface to another arbitrary interface then they are not really helpful. Take fromPromise for example, I don't find the fact that it returns a promise-like object particularly useful (while it doesn't hurt). If I want to deal with that api, I'll just deal with the source promise directly. What likely to be the usecase of the function will be for instance you have a function that expects an observable, but you are provided with a promise. You should be able to convert the promise to an observable and just pass it to a function and the function wouldn't have to know that this observable came from some fromPromise with a special signature. That's not the case here, you'd have to change .get() to .value inside the function.

In fact, I wouldn't mind those naming if it didn't dereference at .value or .current() but instead give us an observable, doesn't have to be boxed but must be of one of the four types that client code already knows how to deal with.

About the name, .get() is what we stick with in mobx and while it's not an useful name it's a "filler" name that doesn't bother anyone. While current() to me feels equally unhelpful (when dealing with mutable values regardless of how you name your function it already implies a current value) but not generic enough so it can cause confusion, does it mean there is next() and previous() as well?

sonhanguyen avatar Nov 24 '17 01:11 sonhanguyen

Observability in MobX is not defined by interface. Any object can be observable (consumed by Mobx) no matter what the interface is. Unlike RxJS there aren't methods which consumes a special "observable" interface. Why would you introduce an abstraction, if it's not used or required by anything? The values produced by Rx.Observable (the value supplied to observer's next) also have an arbitrary interface, these are the actual interfaces you have to work with, but these can't be observed, therefore must be wrapped into Rx.Observable, which is not the MobX case. Yes you can create a function, which accepts any Rx.Observable ... imagine that there wouldn't be any function in RxJS requiring this interface and only thing you could do with Rx.Observable is to obtain (get) some arbitrary value it produces. How is it useful? You have a generic type without any generic methods...

Because the ObservablePromise is not just an observable result of promise, the RxJS analogy would not look like this:

promise.subscribe(value => {
  console.log(value);
})

but rather like this:

promise.subscribe(promise => {
  console.log(promise.value);
  console.log(promise.state);
})

The MobX analogy of the former would actually be:

const value = computed(() => fromPromise(promise).value);
console.log(value.get()) // now we are truly working with single boxed value, so the .get() makes sense

And observable promise with a common interface inspired by Rx.Observable would imho looked like this:

const promise = fromPromise(Promise.resolve(5));
promise.get().value;
promise.get().state;

normalize data sources to a common interface

The returned objects does not neccesarily represents the same thing as suggested in above code samples, therefore they don't have to share the same interface. Again object returned from fromPromise does not represent the data "sourced" from Promise, it represents the Promise itself (but consumable by MobX). However most of these returned objects have in common that they "wrap" some "value" (this is quite vague definition however - should class UserList have .get() to return the actual array of users? probably not right?). So we could came up with a consensus that these "value holders" can always expose the "most significant value" with .get() which is aligned with boxed observables. But we will end up with an interface:

promise.get()
promise.state;

how consistent is that? So maybe:

promise.value();
promise.state();

This seems reasonable to me, but it's not aligned with boxed, so we would have to change them:

boxed.value();
computed.value();

That seems nice from the perspective of "API seems familiar", but is it useful as an abstraction? Do these "values" really represent the same thing? Is it neccesary?

If those function just simply converting an arbitrary interface to another arbitrary interface then they are not really helpful

They convert specific non-observable interface to an observable object, whose interface tries to reflect/describe the original non-observable thing (which may be a single value or something more complex).

While current() to me feels like equally unhelpful

I can imagine that it could suggest (maybe even distinguish) that the value changes spontaneously over time, unlike the boxed which must be explicitely set. But get() seem equally fine to me in this case.

does it mean there is next() and previous() as well

Currently not, but technically could be, or initial() perhaps ... I am just speculating what could be the author's thoughts at the time of writing....

Btw I don't mind aligning the naming, I just feel the motivation/reasoning could be a bit off (especially in relation to RxJS).

urugator avatar Nov 24 '17 06:11 urugator