Underarm

Transducers inspired by the familiar API from Underscore.js] that closely follow the Clojure implementation with extra goodies like lazy generators and callback processes.

Download

Wait... what?


You can do something like this:

Mouse me!
  var $demo = $('#demo1'),
      coords = _r()
        .each(updateText)
        .asCallback();

  $demo.on('mousemove', coords);

  function updateText(e){
     $demo.html('['+e.clientX+', '+e.clientY+']');
  }

Don't want to pass events to your callbacks? Map it!

Mouse me!
  var $demo = $('#demo2'),
      coords = _r()
        .map(function(e){return {x: e.clientX, y: e.clientY}})
        .map(function(p){return '('+p.x+', '+p.y+')'})
        .each(updateText)
        .asCallback();

  $demo.on('mousemove', coords);

  function updateText(p){
     $demo.html(p);
  }

Like to multitask?

Mouse and Click!

  var $demo = $('#demo3'),
      clickCount = 0,
      coords = _r()
        .where({type:'mousemove'})
        .map(function(e){return {x: e.clientX, y: e.clientY}})
        .map(function(p){return '('+p.x+', '+p.y+')'})
        .each(updateText)
        .asCallback(),

      click = _r()
        .where({type:'click'})
        .each(updateCount)
        .asCallback(),

      events = _r()
        .each(coords)
        .each(click)
        .asCallback();

  $demo.on('mousemove click', events);

  function updateText(p){
     $demo.html(p);
  }

  function updateCount(){
     $demo.html('Click '+(clickCount++));
  }

Feel like generating 10 random integers divisible by 3 between 0 and 100 on demand? Of course you do.

Click me!


var $demo = $('#demo4'),
    $results = $('#demo4results'),
    random = _.partial(_.random, 0, 100),
    take10Odd = _r()
      .generate(random)
      .filter(isDivisibleBy(3))
      .take(10),
    click = _r.each(updateNumbers);

$demo.on('click', _r.asCallback(click));

function updateNumbers(){
    var tenOdd = take10Odd.value();
    $results.prepend(tenOdd.join(', ')+'\n');
}

function isDivisibleBy(x){
  return function(y){
    return !(y % x);
  }
}

Here is the previous example with a few modifications.

Click me!


var $demo = $('#demo5'),
    $results = $('#demo5results'),
    random = _.partial(_.random, 0, 300),
    filter7 = _r.filter(isDivisibleBy(7)),
    take10 = _r.take(10),
    filtered = _.compose(filter7, take10),
    click = _r.each(updateNumbers);

$demo.on('click', _r.asCallback(click));

function updateNumbers(){
    var ten = _r.into([], filtered, _r.generate(random));
    $results.prepend(ten.join(', ')+'\n');
}

function isDivisibleBy(x){
  return function(y){
    return !(y % x);
  }
}

We removed all method chaining. Notice filter and take accept a single argument. Also notice that filtered uses simple function composition to combine the two steps (_.compose is from underscore, nothing special here).

We are simply composing transducers. If you would like to know how these work, check out Transducers Explained. The previous examples are all using transducers behind the scenes. Method chaining is simple composition, _r.generate uses an iterator and passes on to transduce. Even asCallback uses transducers but steps through the results using the argument of a callback, instead of reducing over the results.


Let's play with the mouse again.

Mouse and click  me!
var $demo = $('#demo6'),
  clickCount = 0,
  coords = _r()
    .where({type:'mousemove'})
    .map(function(e){return {x: e.clientX, y: e.clientY}})
    .map(function(p){return '('+p.x+', '+p.y+')'})
    .each(updateText)
    .asCallback(),

  click = _r()
    .where({type:'click'})
    .each(updateCount)
    .asCallback(),

  events = _r()
    .each(coords)
    .each(click)
    .asCallback();

$demo.on('mousemove click', events);

function updateText(p){
 $demo.html(p);
}

function updateCount(e){
 $demo.html('Click '+(clickCount++));
}

Too fast for you? You can throttle or debounce the actions.

Mouse!

var $demoA = $('#demo7a'),
  $demoB = $('#demo7b'),
  clickCount = 0,

  coords = _r()
    .throttle(500)
    .map(function(e){return {x: e.clientX, y: e.clientY}})
    .map(function(p){return '['+p.x+', '+p.y+']'})
    .each(updateText)
    .asCallback(),

  click = _r()
    .each(updateWait)
    .debounce(500)
    .each(updateCount)
    .asCallback();

$demoA.on('mousemove', coords);
$demoB.on('click', click);

function updateText(p){
 $demoA.html(p);
}

function updateWait(){
  $demoB.html('Wait....');
}

function updateCount(e){
 $demoB.html('Click '+(clickCount++));
}

Or delay actions.

var $demo = $('#demo8'),
  clickCount = 0,
  click = _r()
    .where({type:'click'})
    .each(updateWait)
    .delay(500)
    .each(updateCount)
    .asCallback();

$demo.on('click', click);

function updateWait(){
 $demo.html('Click... '+clickCount);
}

function updateCount(e){
 $demo.html('Clicked '+(clickCount++));
}

Use async actions to transduce over Promises using any-promise. Install your favorite ES6 Promise Pollyfill for browsers that do not support.

Click above to load.

var $demo = $('#demo9'),
  click = _r()
    .async()
    .first()
    .each(updateWait)
    .map(loadAthletes)
    .map(displayAthletes)
    .asCallback();

$demo.on('click', click);

function updateWait(){
 loading = true;
 $demo.html('Loading... ');
}

function loadAthletes(){
return $.ajax('/static/resources/OlympicAthletes_head.csv');
}

function displayAthletes(text){
  $demo.html('Loaded!');
  $('#demo9results').html(text);
}

Allows all normal transducers in chained async transformation.

var $demo = $('#demo10'),
  rowToTd = _r()
    .split(',')
    .map(function(cell){return '<td>'+cell+'</td>'})
    .join(),

  csvToTable = _r()
    .split('\n')
    .map(rowToTd)
    .map(function(row){return '<tr>'+row+'</tr>'})
    .join(),

  click = _r()
    .async()
    .first()
    .each(updateWait)
    .map(loadAthletes)
    .map(csvToTable)
    .map(displayAthletes)
    .asCallback();

$demo.on('click', click);

function updateWait(){
 loading = true;
 $demo.html('Loading... ');
}

function loadAthletes(){
return $.ajax('/static/resources/OlympicAthletes_head.csv');
}

function displayAthletes(text){
  $demo.html('Loaded!');
  $('#demo10results').html(text);
}

More documentation on github.