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

Working with js-csp channels #33

Open Chris-Andrews opened 9 years ago

Chris-Andrews commented 9 years ago

I'm enjoying checking out transducers.js, but am currently running into an issue:

I have a js-csp channel created, and I want to create a new channel that applies a transducer transformation to the original. For a channel "chan" and transducer "xform", What I thought was the right approach from the documentation was:

newChan = seq(chan, xform);

This gives me the error "don't know how to sequence collection."

I am able to achieve the desired result with the workaround:

newChan = chan(, xform); operations.pipe(chan, newChan);

This is ok, but doesn't seem like the intended method. Is there a method that will work more in the style of my first attempt? Is this something that should work if I implemented the @@transducer/init, @@transducer/step, and @@transducer/result methods properly for csp.chan.prototype?

Thanks for any help!

jlongster commented 9 years ago

seq is mostly meant for actual concrete collections, hence the name sequence. It's inspired from Clojure. But you are right, that is essentially the pattern to make a new "sequence" with a new transformation. You might be able to implement the transducer protocol in the Channel type, let me know how that goes!

Personally I think it's good to still be somewhat explicit in your code about what type of thing you are working with; if it's something that represents an array or a stream. They are different types of things, even though the API for working with the is the same. For example, js-csp should allow pipe to take an xform and that solves this easily. Worth looking into.

We'll have to think about this a little more, thanks for opening the issue!

Chris-Andrews commented 9 years ago

Thanks for the explanation. This is somewhat of a cosmetic issue, but it's always good for things to be as intuitive as possible. As it stands now, transducers.js and js-csp together seem capable of accomplishing pretty much everything I'm interested in, which is really pretty amazing.

I see your point about code being explicit about what type of transformation is being applied. Actually, I wonder if a good interface would be for transducers.js to include a function for creating protocols that define the methods necessary for the transducers to operate. This might look something like:

var tr = require('transducers.js'); channelProtocol = tr.protocol({init: initFun, step: stepFun, result: resultFun}); newChannel = tr.seq(oldChannel, xform, channelProtocol);

If you see a transducer applied without the protocol defined, you can assume it's a native javascript data structure that is already supported (or perhaps some other data type that natively supports the required methods).

newVector = tr.seq([1, 2, 3], xform);

This way you're always using the same interface for transducers, but it also conveys some information about the type of data being transformed. This also avoids implementing @@transducer/init, @@transducer/step, @@transducer/result methods directly in the Prototype methods. The way those are namespaced, they are unlikely to cause issues, but by defining the protocol outside of the Prototype, you can avoid that risk entirely. Another benefit of this approach would be that you could explicitly import a transducer protocol:

var snoodProtocol = require('snood-transducer'); newSnood = tr.seq(oldSnood, xform, snoodProtocol);

I found this issue: https://github.com/cognitect-labs/transducers-js/issues/20 where the current approach was discussed. It looks like a lot of thought was put into the current method, and some of that revolved around being library-agnostic. So maybe things are a little too far along and everyone is comfortable with the current approach, but I wanted to raise the issue in case you have feedback about this.