Functional Programming With Ecmascript6 Generators

The web is abuzz right now with ecmascript 6 on the horizon. If you get node 0.11, you can use it server side already. Once of the big features I’m excited about are generators.

I’ve blogged about them previously Alas, I’ve only found nothing out there on the web that covers anything beyond basic instatiation and invocation. With just that to go on, it was hard for me to initially see the hype. A few days ago, I had an appifany.

Generators are first class objects. Like functions, they can be composed from smaller parts. Therefore, much of we know about functions can be applied to generators!

On that note, I will lay the ground work for understanding how to really USE generators.

It all starts with a bind

If you’ve worked with javascript for any of length of time, You should be familiar with bind.

1
2
3
4
5
  var bind = function(fn, ctx, args) {
    return function() {
      return fn.apply(ctx, arguments);
    }
  }

A function has 2 diffrent modes, literal and called.

  • Literal: a function itself, its not being run.
1
2
3
4
var something = function() {
  console.log('do something');
}

  • Called: running the function which gives us its return value along with side effects.
1
something();

The bind is implimented by taking a literal function and calling it within another literal function passing along the context and possible arguments using .apply().

A Generator has 3 states,

  • Literal: A literal Generator function
1
2
3
4
5
var Gen = function *() {
  var value = yield asyncTask();
  return value;
};

  • Instantiated: a runnable instance is created by calling the Generator function
1
var gen = Gen();
  • Run: You can then iterate through the generator by calling next()
1
gen.next();

Unlike a function, We are going to compose generators to be run inside of a co() function. Co will run a generator until it comes accross a yield. Whatever is on right side of the yield will be passed into co. the generator will be frozen until the value can be resolved. This includes thunks, promises and even other generators!

A generator equivilent for a bind looks like this.

1
2
3
4
5
var bind = function(genFunc, ctx) {
  return function *() {
    return yield genFunc.apply(ctx, arguments);
  };
};

Like functions, generators also have a call() and apply() methods which can be used to invoke the function with an explicit context. The yield is there because when we run this new function inside co(), the instantiated generator will be run and the return value will be be spat out to be returned by this generator.

With that in mind, How about a function that takes two generators and runs one inside the other? How would we impliment that?

1
2
3
4
5
var join = function(gen1, gen2) {
  return function *() {
    return yield gen1.call(this, gen2.call(this);
  };
};

With this function in place, we can now run two generators back to back inside a single coroutiine function.

1
2
3
4
5
6
7
8
var co = require('co');
co(join(function *(next) {
  var foo = yield next;
  console.log(foo); // => 'hello world';
  return foo;
}, function *() {
  return 'hello world';
})).call(this);

If you are interested in exploring this subject further, I’m working on a javacript library for composing generators called Shen. Its a toolkit for composing generators for running inside co like lego pieces.

To give you a taste of its power, Instead of join, Shen implements cascade which allows you to merge 1 or more generators.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var shen = require('shen');
co(shen.cascade(
  function *(next) {
//each yield freezes the generator until the next returns.
    console.log(1);
    yield next;
    console.log(1);
    return;
  },
  function *(next) {
    console.log(2);
    yield next;
    console.log(2);
    return;
  },
  shen.cascade(
    function *(next) {
      //thats right, you can nest them too!
      console.log(3);
      return yield next
    },
    function *() {
      return
  })))();

/* Outputs
    1
    2
    3
    2
    1
  */

Shen functions all compose with each other allowing you to put together generators as easily as you would curry a function.

In addition to cascade and bind, Shen also currently includes…

  • branch and dispatch for conditional logic
  • delay for… delaying?
  • parallel: run several generators and get back an array of the return values
  • oscillator: run a generator at a specific interval and get back an immediate event emitter that fires with the latest returned value of the generator

Current use-cases off the top of my head include using oscillator and parrallel to run several network requests at the same time. you’d get an event emitter with all the returned values in one place. One thing to note is that you can’t completely escape callbacks but you can create areas in your code where callbacks are invisible. The generator takes care of the hard stuff.

Its still very much a work in progress and only works on node 0.11 but I invite you all to try it out. If you want to help, I’m always welcome to new ideas for pieces to add to the ecosystem. contributing a few tests or implementations of ideas would be great too!

Here’s the github to the project

Cheers.

Comments