ericelliott / object-list

Treat arrays of objects like a db you can query.
MIT License
43 stars 2 forks source link

Implement object-list methods #4

Open ericelliott opened 9 years ago

ericelliott commented 9 years ago

object-list

Treat arrays of objects like a db you can query. A single object from an object-list is called a record.

A common API for object collections

You may be scratching your head right now and wondering how this is different from Underscore, Lodash, or Rx Observables. Astute observations. The implementation will likely lean heavily on both Lodash and Rx Observables.

The difference is that this is intended to provide a universal facade for many types of object collections. It is an interface contract that will hopefully support a number of modular collection adapters. Read on for more details.

Current Status

Developer preview.

API Design Goals

Only a few of these design goals have been met in the current implementation, so read this section like everything is prefixed with "eventually..." See future.

See Future.

What needs doing?

benlesh commented 9 years ago

This is interesting. How will this handle the asynchronous nature of Observables?

Assuming something like this: (I don't actually know the API)

var myObservable = Rx.Observable.interval(1000);
var index = 9;
var objList = objectList.from(myObservable);
var result = objList.at(index);

Is result a Promise? an Observable? It can't be a value, because index 9 might not have happened yet...

FWIW: I'm trying to get an effort going to provide a common interop API for reactive streams libraries like RxJS, Bacon and Most.js. It should look a lot like the Observable signature from @jhusain's ES7 async generator proposal. Which is the dual of the ES6 Iterable.

ericelliott commented 9 years ago

From future doc:

let myList = list({ list: apiResource, async: true })
  .reverse()
  .subscribe(onNext, onError, onCompleted);
ericelliott commented 9 years ago

Can you link to jhusain's proposal? Also from future doc:

mattpodwysocki commented 9 years ago

@blesh since there are no blocking operators allowed at that point, it'd be best if it is still a wrapped value, eg Observable. Any aggregate operations should be "async" or wrapped values:

interface Observable<T> {
    sum() : Observable<T>
    sum<T, U>(selector: (item: T) => U) : Observable<U>
}

One thing that bugged me about the Streams API in Dart, in particular the Stream object is that any one and done operations are Future<T> or Promise values. That breaks the whole composition model and now you need to differentiate between Stream<T> and Future<T>. I'm sure @jhusain would have plenty to say about that particular issue.

But that breaks between Array and Observable because Array reductions provide a single unwrapped value.

ericelliott commented 9 years ago

:+1:

Yeah, we're going to be (mostly) ES6 array compatible so you can easily use an object-list almost anywhere you'd use an array, which is why you have to specify an option if you want async.

benlesh commented 9 years ago

Here's the link: https://github.com/jhusain/asyncgenerator#introducing-observable

ericelliott commented 9 years ago

Thanks! =)

mattpodwysocki commented 9 years ago

One thing that we should have is composition through some sort of flatMap or concatMap feature. This would enable such scenarios as several level deep queries. For Array values, it should be pretty easy of a Cartesian product.

for (let x of xs) {
  for (let y of f(x))
    yield y;
}

This enables scenarios such as this:

let videos = user.videoLists
  .flatMap(videoList =>
    videoList.videos.filter(video => video.rating === 5))

videos.forEach(onNext, onError, onCompleted)    

For Observable sequences, the water gets muddied in that you could base it upon a merge, meaning when a value from any stream is ready, it is sent down, which is preferable, or a concat which waits for the previous stream to end before emitting values.

I know there was some interest from @BrendanEich about getting flatMap or something akin to it into ES-Harmony.

benlesh commented 9 years ago

@mattpodwysocki I agree, and I think flatMap is important for interop.

mattpodwysocki commented 9 years ago

@ericelliott should we categorize the kinds of collections we are dealing with? For example, Set is different from Map is different from Array and of course Observable. Then perhaps you should also focus on say, immutable collections. Each will have a different contract.

ericelliott commented 9 years ago

Just implement equivalents of .concatAll() and .merge()?

I don't want to categorize with different contracts. Might as well just use RxJS, Lodash, etc... in that case.

The point of this is to present a unified API for all collection management needs. In cases where there's a distinction dependent on async, we'll figure out what makes the most sense on a case-by-case basis. I have a feeling those will be the exceptions rather than the rule.

BrendanEich commented 9 years ago

Everyone wants flatMap -- here's a sweet.js issue wanting it:

https://github.com/mozilla/sweet.js/pull/441#issuecomment-68223347

I think Matt's point about different contracts is good. Set and Map do not have the same contract, as sets have values but no keys, offer has/add/delete methods not get/set/delete, provide different iterators, etc.

ericelliott commented 9 years ago

:+1:

Should we avoid calling it flatMap to avoid potentially incompatible collision with the proposal that will almost certainly land in the spec, or should we just do it and break backward compat with ES-native when the dust settles?

ericelliott commented 9 years ago

I think Matt's point about different contracts is good. Set and Map do not have the same contract, as sets have values but no keys, offer has/add/delete methods not get/set/delete, provide different iterators, etc.

Yeah, I don't think values with no keys belongs in this API. We're specifically concentrating on collections of objects with key/value pairs because we need a good general-purpose utility belt for everything that makes heavy use of them (db, JSON APIs, event streams, etc...).

Those would probably be great separate modules sharing a lot of the same ideas, though! =)

mattpodwysocki commented 9 years ago

@ericelliott my comments were inline with having things like add and push being added but for most collections, that's simply not needed. Or you have differing APIs based upon whether immutable or mutable.

JScheerleader commented 9 years ago

This is cool.

ericelliott commented 9 years ago

@mattpodwysocki These are never mutable. Any change will return a new object-list.

Immutable.js has an interesting api for interim mutations that might be useful to emulate if we decide to allow mutations in some steps in the chain.

ericelliott commented 9 years ago

@JScheerleader Thanks! I'm already using and enjoying the current proof-of-concept. It should get really interesting when we start to implement the adapters. =)