• Stars
    star
    1,726
  • Rank 26,370 (Top 0.6 %)
  • Language
    JavaScript
  • License
    BSD 2-Clause "Sim...
  • Created almost 10 years ago
  • Updated about 7 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A small library for generalized transformation of data (inspired by Clojure's transducers)

transducers.js

A small library for generalized transformation of data. This provides a bunch of transformation functions that can be applied to any data structure. It is a direct port of Clojure's transducers in JavaScript. Read more in this post.

The algorithm behind this, explained in the above post, not only allows for it to work with any data structure (arrays, objects, iterators, immutable data structures, you name it) but it also provides better performance than other alternatives such as underscore or lodash. This is because there are no intermediate collections. See this post for benchmarks.

npm install transducers.js

For browsers, grab the file dist/transducers.js.

When writing programs, we frequently write methods that take in collections, do something with them, and return a result. The problem is that we frequently only write these functions to work a specific data structure, so if we ever change our data type or wanted to reuse that functionality, you can't. We need to decouple these kinds of concerns.

A transducer is a function that takes a reducing function and returns a new one. It can perform the necessary work and call the original reducing function to move on to the next "step". In this library, a transducer a little more than that (it's actually an object that also supports init and finalizer methods) but generally you don't have to worry about these internal details. Read my post if you want to learn more about the algorithm.

var transform = compose(
  map(x => x * 3),
  filter(x => x % 2 === 0),
  take(2)
);

seq([1, 2, 3, 4, 5], transform);
// -> [ 6, 12 ]

function* nums() {
  var i = 1;
  while(true) {
    yield i++;
  }
}

into([], transform, nums());
// -> [ 6, 12 ]

into([], transform, Immutable.List.of(1, 2, 3, 4, 5))
// -> [ 6, 12 ]

All of these work with arrays, objects, and any iterable data structure (like immutable-js) and you get all the high performance guarantees for free. The above code always only performs 2 transformations because of take(2), no matter how large the array. This is done without laziness or any overhead of intermediate structures.

Transformations

The following transformations are available, and there are more to come (like partition).

  • map(coll?, f, ctx?) — call f on each item
  • filter(coll?, f, ctx?) — only include the items where the result of calling f with the item is truthy
  • remove(coll?, f, ctx?) — only include the items where the result of calling f with the item is falsy
  • keep(coll?) — remove all items that are null or undefined
  • take(coll?, n) — grab only the first n items
  • takeWhile(coll?, f, ctx?) — grab only the first items where the result of calling f with the item is truthy
  • drop(coll?, n) — drop the first n items and only include the rest
  • dropWhile(coll?, f, ctx?) — drop the first items where the result of calling f with the item is truthy
  • dedupe(coll?) — remove consecutive duplicates (equality compared with ===)

The above functions optionally take a collection to immediately perform the transformation on, and a context to bind this to when calling f. That means you can call them in four ways:

  • Immediately perform a map: map([1, 2, 3], x => x + 1)
  • Same as above but call the function with this as ctx: map([1, 2, 3], function(x) { return x + 1; }, ctx)
  • Make a map transducer: map(x => x + 1)
  • Same as above but with this as ctx: map(function(x) { return x + 1; }, ctx)

(I will be using the ES6 fat arrow syntax, but if that's not available just function instead)

The signature of running an immediate map is the same familiar one as seen in lodash and underscore, but now you can drop the collection to make a transducer and run multiple transformations with good performance:

var transform = compose(
  map(x => x + 1),
  filter(x => x % 2 === 0),
  take(2)
);

compose is a provided function that simply turns compose(f, g) into x => f(g(x)). You use it to build up transformations. The above transformation would always run the map and filter only twice because only two items are needed, and it short-circuits once it gets two items. Again, this is done without laziness, read more here.

There are also 2 transducers available for taking collections and "catting" them into the transformation stream:

  • cat — take collections and forward each item individually, essentially flattening it
  • mapcat(f) — same as cat, but first apply f to each collection

Just pass cat straight through like so: compose(filter(x => x.length < 10), cat). That would take all arrays with a length less than 10 and flatten them out into a single array.

Applying Transformations

Building data structure-agnostic transformations is cool, but how do you actually use them? transducers.js provides several integration points.

To use a transformation, we need to know how to iterate over the source data structure and how to build up a new one. The former is easy; we can work with arrays, objects, and anything can uses the ES6 iterator protocol (Maps, Sets, generators, etc). All the the below functions works with them.

For the latter, you need to specify what you want back. The following functions allow you to make a new data structure and possibly apply a transformation:

  • toArray(coll, xform?) — Turn coll into an array, applying the transformation xform to each item if provided. The transform is optional in case you want to do something like turn an iterator into an array.
  • toObj(coll, xform?) — Turn coll into an object if possible, applying the transformation xform if provided. When an object is iterated it produces two-element arrays [key, value], and obj will turn these back into an object.
  • toIter(coll, xform?) — Make an iterator over coll, and apply the transformation xform to each value if specified. Note that coll can just be another iterator. Transformations will be applied lazily.
  • seq(coll, xform) — A generalized method that will return the same data type that was passed in as coll, with xform applied. You will usually use this unless you know you want an array, object, or iterator. If coll is an iterator, another iterator will be returned and transformations will be applied lazily.
  • into(to, xform, from) — Apply xform to each item in from and append it to to. This has the effect of "pouring" elements into to. You will commonly use this when converting one type of object to another.
  • transduce(coll, xform, reducer, init?) — Like reduce, but apply xform to each value before passing to reducer. If init is not specify it will attempt to get it from reducer.

The possibilities are endless:

// Map an object
seq({ foo: 1, bar: 2 }, map(kv => [kv[0], kv[1] + 1]));
// -> { foo: 2, bar: 3 }

// Make an array from an object
toArray({ foo: 1, bar: 2 });
// -> [ [ 'foo', 1 ], [ 'bar', 2 ] ]

// Make an array from an iterable
function* nums() {
  var i = 1;
  while(true) {
    yield i++;
  }
}
into([], take(3), nums());
// -> [ 1, 2, 3 ]

// Lazily transform an iterable
var iter = seq(nums(), compose(map(x => x * 2),
                               filter(x => x > 4));
iter.next().value; // -> 6
iter.next().value; // -> 8
iter.next().value; // -> 10

Laziness

Transducers remove the requirement of being lazy to optimize for things like take(10). However, it can still be useful to "bind" a collection to a set of transformations and pass it around, without actually evaluating the transformations.

As noted above, whenever you apply transformations to an iterator it does so lazily. It's easy convert array transformations into a lazy operation, just use the utility function iterator to grab an iterator of the array instead:

seq(iterator([1, 2, 3]),
    compose(
      map(x => x + 1),
      filter(x => x % 2 === 0)))
// -> <Iterator>

Our transformations are completely blind to the fact that our transformations may or may not be lazy.

Utility Functions

This library provides a few small utility functions:

  • iterator(coll) — Get an iterator for coll, which can be any type like array, object, iterator, or custom data type
  • push(arr, value) — Push value onto arr and return arr
  • merge(obj, value) — Merge value into obj. value can be another object or a two-element array of [key, value]
  • range(n) — Make an array of size n filled with numbers from 0..n.

immutable-js

We've talked about how this can be applied to any data structure — let's see that in action. Here's how you could use this with immutable-js.

Immutable.fromJS(
  seq(Immutable.Vector(1, 2, 3, 4, 5),
      compose(
        map(function(x) { return x + 10; }),
        map(function(x) { return x * 2; }),
        filter(function(x) { return x % 5 === 0; }),
        filter(function(x) { return x % 2 === 0; })))
)

We can use our familiar seq function because Immutable.Vector implements the iterator protocol, so we can iterator over it. Because seq is working with an iterator, it returns a new iterator that will lazily transform each value. We can simply pass this iterator into Immutable.Vector.from to construct a new one, and we have a new transformed immutable vector with no intermediate collections except for one lazy transformer!

The builtin transformations perform well because they minimize allocations, but since we don't have any intermediate structures or laziness machinery, this performs slightly better. The point is not to beat it, but to show that both are high-performance but we can apply our performance to any data structure.

CSP Channels

This not only works with all the JavaScript data structures you can think of, but it even works for things like streams. Soon channels from js-csp will be able to take a transformation and you get all of this for channels for free:

var ch = chan(1, compose(
  cat,
  map(x => x + 1),
  dedupe(),
  drop(3)
));

The transducer protocol

While it's great that you can apply transducers to custom data structures, it's a bit annoying to always have to use constructor functions like Immutable.fromJS. One option is to define a new protocol complementary to iterator.

This conforms to the official transducer spec so if you implement this, you can use it with all transducer libraries that conform to it.

To implement the transducer protocol, you add methods to the prototype of your data structure. A transformer is an object with three methods: init, result, and step. init returns a new empty object, result, can perform any finalization steps on the resulting collection, and step performs a reduce.

These methods are namespaced and in the future could be symbols. Here's what it looks like for Immutable.List:

Immutable.List.prototype['@@transducer/init'] = function() {
  return Immutable.List().asMutable();
};

Immutable.List.prototype['@@transducer/result'] = function(lst) {
  return lst.asImmutable();
};

Immutable.List.prototype['@@transducer/step'] = function(lst, x) {
  return lst.push(x);
};

If you implement the transducer protocol, now your data structure will work with all of the builtin functions. You can just use seq like normal and you get back an immutable vector!

t.seq(Immutable.List.of(1, 2, 3, 4, 5),
      t.compose(
        t.map(function(x) { return x + 10; }),
        t.map(function(x) { return x * 2; }),
        t.filter(function(x) { return x % 5 === 0; }),
        t.filter(function(x) { return x % 2 === 0; })));
// -> List [ 30 ]

Running Tests

npm install
gulp
mocha build/tests

BSD LICENSE

More Repositories

1

absurd-sql

sqlite3 in ur indexeddb (hopefully a better backend soon)
JavaScript
4,062
star
2

blog

All the sources for my (not powered by React anymore) blog
CSS
1,223
star
3

electron-with-server-example

An example Electron app with a backend server all wired up via IPC
JavaScript
1,002
star
4

crdt-example-app

A full implementation of CRDTs using hybrid logical clocks and a demo app that uses it
JavaScript
576
star
5

css-animations.js

A library to work with CSS3 keyframe animations from javascript
JavaScript
479
star
6

backend-with-webpack

A simple server using webpack as a build tool
JavaScript
459
star
7

unwinder

An implementation of continuations in JavaScript
JavaScript
304
star
8

canvas-game-bootstrap

A starting point & tutorial for basic 2d canvas games
JavaScript
237
star
9

es6-macros

A collection of sweet.js macros that implement ES6 features for ES5
JavaScript
237
star
10

monkey-hot-loader

A webpack loader to hot reload JavaScript modules
JavaScript
171
star
11

outlet

Outlet is a simple Lisp-like programming language
JavaScript
161
star
12

jsx-reader

A JSX reader for JavaScript, powered by sweet.js.
JavaScript
149
star
13

lively

Components & state
JavaScript
145
star
14

emojiscript

EmojiScript: emotion literals, emotional algebra, and more for JavaScript
JavaScript
138
star
15

dom3d

Rendering 3d with CSS3
JavaScript
111
star
16

gambit-iphone-example

An example iphone app which uses Gambit Scheme.
Scheme
103
star
17

farmageddon

The raw, unedited source for the iOS game I wrote in Gambit Scheme back in 2010.
C
102
star
18

dcpu-lisp

A Lisp-like language that compiles to DCPU-16 assembly code
JavaScript
77
star
19

redux-experiments

A few customizations for my own projects
JavaScript
64
star
20

lljs-cloth

Cloth simulation as a large-scale test for LLJS development
JavaScript
47
star
21

sweetjs-loader

A webpack loader for sweetjs
JavaScript
47
star
22

swank-gambit

Gambit swank backend for SLIME
Scheme
40
star
23

learning-clojurescript

All the apps I'm making to learn ClojureScript (and also have fun)
JavaScript
40
star
24

absurd-example-project

A project to show everything needed to use absurd-sql
JavaScript
38
star
25

grunt-nunjucks

A grunt plugin for nunjucks.
JavaScript
38
star
26

steps

A little hack that allows you to step through JavaScript code (only in Firefox for now)
JavaScript
30
star
27

jlongster-rebuild

A rebuild of my blog w/react, CSP, and more
JavaScript
29
star
28

gulp-sweetjs

A gulp loader for sweet.js
JavaScript
26
star
29

sweet.js-tutorials

All the sweet.js tutorials from my blog and a working sweet.js environment
JavaScript
26
star
30

play-react-native-opengl

An example of using React Native to control OpenGL (terrible hacky code)
JavaScript
25
star
31

webfighter

An r-type inspired 2d game for the web
JavaScript
24
star
32

live-css-layout

A live editor for css-layout
JavaScript
20
star
33

configure-iphone

A utility shell script to help build libraries for iPhone devices.
19
star
34

objective-c-csv-parser

Objective-C CSV Parser
18
star
35

shade

A simple but optimal 3d engine
JavaScript
16
star
36

construct.js

Simple sugar for establishing prototype-based inheritance
JavaScript
15
star
37

mozlando-react-workshop

Lessons for learning React.
JavaScript
15
star
38

genetic-canvas

Implements a genetic algorithm for drawing 2d pictures
Scheme
14
star
39

tcomb-immutable-experiment

An experiment with immutable.js-backed tcomb types
JavaScript
14
star
40

outlet-machine

A register-based virtual machine for a basic Lisp
JavaScript
13
star
41

autoffi

FFI generator for Gambit Scheme
Scheme
13
star
42

ftgles

FTGL, a font rendering library, ported to OpenGLES.
C++
12
star
43

struct.js

Struct datatypes for JavaScript with a few sweet.js macros
JavaScript
12
star
44

gambit-ffi-generator

Generating Gambit-C Scheme FFI's from C header files
12
star
45

schemeray

A simple raytacer written in Gambit Scheme, and ported to other implementations
Scheme
11
star
46

fp-workshop

A workshop to introduce functional-style programming in JavaScript
10
star
47

mozilla.com

Battlegrounds for mozilla.com work (mirror of http://svn.mozilla.org/projects/mozilla.com/)
PHP
10
star
48

reason-http-server-example

A simple HTTP server in Reason using cohttp and built with Rebel.
10
star
49

devrepl

A REPL for the Firefox debugger client/server
JavaScript
9
star
50

weatherme

A simple weather web app
JavaScript
8
star
51

macro-editor

An embeddable sweet.js macro editor
JavaScript
8
star
52

flame

An experimental tile-based game
JavaScript
7
star
53

mozilla.org

A copy of the mozilla.org PHP site.
JavaScript
7
star
54

grime3d

A proof of concept for something special.
Scheme
6
star
55

perf-deets

Get those perf deets
JavaScript
6
star
56

OMGBUGS

Experimental nodejs-powered bugzilla interface. See the demo site at this repo's url.
JavaScript
6
star
57

game-engine-studies

Implementing various game engines in javascript
JavaScript
6
star
58

lljs-minecraft

Port of notch's minecraft demo to LLJS
JavaScript
5
star
59

ahoy

This is nothing.
JavaScript
5
star
60

tilt-this

A web-based builder for 3d Tilt objects (viewable by Firefox's Tilt devtool)
JavaScript
5
star
61

actual-automoto

JavaScript
5
star
62

ftgles-gambit

Gambit Scheme bindings to the FTGLES library
Scheme
4
star
63

webui

Using csuwldcat's WebComponent project to build some UI tools for webapps
JavaScript
4
star
64

relnotes

Release notes generator for Firefox releases
Python
4
star
65

lljs-editor

An experiment with live-editing LLJS code in the browser.
JavaScript
4
star
66

ff-devtools-libs

A quick experiment to help migrate to HTML
JavaScript
4
star
67

demo-cloth

A floating cloth demo with JavaScript/WebGL
JavaScript
4
star
68

channel-debugger

a firefox devtool addon for debugging channels
JavaScript
4
star
69

YPS

Yield Passing Style
JavaScript
4
star
70

entity.js

A little game experiment
JavaScript
3
star
71

spidermonkey-graphics

spidermonkey for 3d graphics and user interfaces
C
3
star
72

strangeloop2015

Slides for my talk "Cleaning the Tar: Using React in Firefox DevTools"
JavaScript
3
star
73

tweetness

A twitter Open Web App
JavaScript
3
star
74

js-lljs-c-benchmarks

Benchmarks comparing js, lljs, and c compiled with emscripten
JavaScript
3
star
75

octave-utility

my utility code for using octave
Objective-C
3
star
76

jlongster-django

My personal site: jlongster.com
Python
3
star
77

writing-webapps

My presentation at the Mozillas Apps Summit
JavaScript
3
star
78

django-basic-site

Python
3
star
79

fiber-modal-error

JavaScript
2
star
80

mozilla-product-details

git svn clone http://svn.mozilla.org/libs/product-details/json/
JavaScript
2
star
81

gollumdoc

Patches the gollum wiki to host documentation better
Ruby
2
star
82

servable-branches

Manifest git branches on the filesystem to be served on the web
JavaScript
2
star
83

persona.org

NodeJS project
JavaScript
2
star
84

my-react-native-babel-preset

JavaScript
2
star
85

voloweb

A site for volo
JavaScript
2
star
86

libgrowth

A tiny web site that estimates the growth of your node libraries over time
JavaScript
2
star
87

build-docker-mochitest

Making mochitests OK.
Makefile
2
star
88

xpcshell-unit-test

A basic xpcshell test runner that can be used outside of Firefox
JavaScript
2
star
89

onGameStart-2013

My presentation for onGameStart 2013, using websockets to connect everybody
JavaScript
2
star
90

devtool-prototype

An experiment with making a new devtool
CSS
2
star
91

actual-docs

markdown content
JavaScript
2
star
92

cordova-firefoxos-plugins

A place for Mozilla to track our Cordova work
Java
2
star
93

rn-android-input-pan

Repro for Android's panning behavior when focusing an input
Objective-C
2
star
94

jlongster.com

TypeScript
2
star
95

bugzillersearch

A quick experiment in hating iOS for nefarious research purposes.
Objective-C
2
star
96

php-product-details

Mozilla's PHP product-details library (mirror of https://svn.mozilla.org/libs/product-details)
PHP
2
star
97

services

services that I run on my computer & servers
Ruby
1
star
98

tmp-node-inspector

JavaScript
1
star
99

jlongster.github.io

HTML
1
star
100

noodleconfware

The conference software for noodleconf
JavaScript
1
star