jlongster / transducers.js

A small library for generalized transformation of data (inspired by Clojure's transducers)
BSD 2-Clause "Simplified" License
1.73k stars 54 forks source link

conform to the transformer protocol by updating Reduced #2

Closed rpominov closed 9 years ago

rpominov commented 9 years ago

Is it possible to use a transducer without provided functions sequence, transduce, or into? I.e. is there any public API for objects returned by map(), filter(), etc.?

jlongster commented 9 years ago

Certainly. Right now map/filter/etc just return a function that take another reducer. Usually if you are calling it manually you are giving it the "bottom" reducer that actually appends it to the data structure.

For example:

var trans = map(x => x + 1);

[1, 2, 3, 4].reduce(trans(function(result, x) {
  result.push(x);
}), []);

(untested code, sorry.) This is likely going to change, however, because there are a few properties of transducers that we are missing, and it may become more like an object with a few methods. Will resolve that within the next week or two.

rpominov commented 9 years ago

Thanks! I will wait for final spec for transducer object.

rpominov commented 9 years ago

I think, it would be best for JavaScript ecosystem if we'd agree on some library independent transducers protocol. The easiest way to do that is just port as close as we can original Rich's specification.

Fortunately it quite easy in JavaScript. With exception for Reduced wrapper. In clojure they already have global reduced, which they can use in transducers, but in JavaScript we don't. Using a class as reduced wrapper instantly makes transducers library dependent, which is not good.

I propose use simple object as a reduced wrapper, i.e. {__transducersReduced__: true, val: val} (of course you can add helper functions reduced() and isReduced() to work with it).

If we'd agreed on library independent transducers protocol, it would be easy, for example, for underscore and LoDash to support transducers. Then, for example, FRP libraries could implement transducers support as a protocol and not as a support of transducers.js library.

I think it would be very cool to have such ability for different libraries to work together via transducers protocol.

jeffbski commented 9 years ago

+1 I agree, if we follow Rich's spec from clojure as closely as possible, that would be great.

jlongster commented 9 years ago

Now the Cognitect has release their own transducers lib, this discussion is even more important. This will I will think about this and see how we can work with them.

fwiw, I tweaked my transducers to produce objects with the same API as theirs, so in theory they should be swappable. We had actually converged on basically the same things (I only had to change finalize to result).

jlongster commented 9 years ago

Just read this over again, and yes, we have hit this problem over in https://github.com/ubolonton/js-csp. Reduced is the only problem, as we need to do a direct instanceof check on that class.

This library follows the semantics of Clojure's reducers 100% (as far as I know), but we need to form our own standards for JS APIs. I'm not sure what that will look like. @pozadi's idea of having an object with a special property might work. It depends on how Cognitect wants to do it; hopefully they are open to standardizing some sort of protocol.

ubolonton commented 9 years ago

+1 for the object with a special property.

swannodette commented 9 years ago

A heads up, we've chosen __transducers_reduced__ as the property.

rpominov commented 9 years ago

I think, we should also agree on the name of property where wrapped value will be stored.

ubolonton commented 9 years ago

@pozadi I think that key stores the value also: {__transducers_reduced__: val}.

swannodette commented 9 years ago

Yes the property name is the only thing required

rpominov commented 9 years ago

Now I understand. Thanks!

jlongster commented 9 years ago

+1. Will update my lib soon

rpominov commented 9 years ago

I just thought it would be much faster to check x && x.__transducers_reduced__ than '__transducers_reduced__' in x.

http://jsperf.com/prop-existence-check-vs-prop-value-check

jlongster commented 9 years ago

@pozadi yes, probably won't do the in check. Just need to make sure the property access doesn't hurt the JIT also.

rpominov commented 9 years ago

But wrapped result might be any value including false, null, undefined etc. The in check is the only option here, as far as I understand.

jlongster commented 9 years ago

True. It doesn't make much sense to early terminate with a falsey value though... @swannodette is that true? Can it just be part of the protocol that the value on __transducers_reduced__ must be truthy?

I have to focus on work right now so these are just quick thoughts. Can't respond much more until later.

swannodette commented 9 years ago

@jlongster not sure what is under discussion here.

This is our isReduced check:

transducers.isReduced = function(x) {
    return (x instanceof transducers.Reduced) || (x && x.__transducers_reduced__);
};

I benchmarked this - there's no interesting performance degradation as far as I could tell.

jlongster commented 9 years ago

@swannodette cool. @pozadi's point was that since we store the value in __transducers_reduce__, if the value is false it will fail, but I don't think that really matters it just might be worth mentioning in the protocol docs that Reduced only works with truthy values. (assuming we're not doing instanceof on our own type since that doesn't work across libs)

swannodette commented 9 years ago

Ah sorry I understand now. Reduced values can definitely be false-y. I've changed transducers-js so that __transducers_reduced__ is now a boolean flag, and the value is stored in value.

jlongster commented 9 years ago

Awesome

rpominov commented 9 years ago

I really like the new transducer protocol! Just used it to add transducers support to my small FRP lib. Ironically I didn't even had to use the __transducers_reduced__ property.

Here is the complete implementation in my library if anyone interested: https://github.com/pozadi/kefir/blob/6b5fe3ebc3394a1fb161b3aa774edde1a08d83c2/src/one-source.js#L85-128