To Infinity & Beyond: Protocols & sequences in Node - Part 1

  • View
    563

  • Download
    1

Embed Size (px)

Transcript

To Infinity & Beyond!

Protocols & Lazy Sequences in NodePart 1

Bahul Neel Upadhyaya (@bahulneel)BraveNewTalenthttp://github.com/bahulneelhttp://www.bravenewtalent.com

JavaScript

Imperative

Dynamic

OOPPrototypes

Constructors

FunctionalFirst Class Functions

Closures

Brendan Eich

Designed by Brendan Eich in 1994 for Mozilla.Originally called Mocha it was based on Self and SchemeBrings in features from both languagesAllows the developer to adopt any style or any combination of those styles.

List Comprehension

Loopsfor (init; while; step)

Stack & Queuepush/pop/shift/unshift

IteratorsforEach, etc

Map/Reduce

No standard way of doing these

One of the things we spend most of our time doing is manipulating lists of things (numbers, objects, etc).Currently, in the javascript world, we have a number of ways available to us.Classic for loop, push/pop, forEach, map, etc.

Javascript has no standard way to deal with this.

Collections

How do I know if collection x works with my function?Extends Array?

Implements Array?

Impements some iterator?

Do I need to monkey patch?

Collections add a extra layer of complexity.Do they extend the Array prototype?If not do they implement the common array functions and/or patterns?If so which ones?Do I need to add these functions myself?When do I add them?Will they collide with someone elses functions?=> Complexity & bloat

Collections

Is there a better way?Can we find a common set of methods that will let us write a universal function that should work with anyone's collection?

I think there is and all we need to do is borrow a few ideas from the Lisp guys.

Lisp

John McCarthy

Structure and Interpretation of Computer Programs

Designed byJohn McCarthy in 1958Made popular in AI circles by scheme written by Guy Lewis Steele Jr. and Gerald Jay Sussman.Sussman went on to write Structure & Interpetation of Computer Programs with Harold Abelson (must read!)The name LISP was derrived from LISt Processing and had a very simple but powerful way to look at lists

Lists

first

rest

first

rest

first

rest

In lisp a list is linked list that has a head and a tail.Where calling first on a list returns the data at the head of the list and calling rest returns a list of all the elements in the tail.

It turns out that this representation gets us pretty far if we want a common way to deal with lists.

Using Lists

map = function(list, fn) {var head = first(list), tail = rest(list);if (!head) return [];result = map(tail, fn);return [fn(head)].append(result);

};

reduce = function(list, fn) {var head = first(list), tail = rest(list);if (!head) return null;result = reduce(tail, fn);return fn(head, result);

};

forEach = function(list, fn) {var head = first(list), tail = rest(list);if (!head) return null;fn(head);forEach(tail, fn);

};

filter = function(list, fn) {var head = first(list), tail = rest(list);if (!head) return [];result = map(tail, fn);if (fn(head)) return [head].append(result);else return result;

};

Sequences

collection.first()Returns the first item

collection.rest()Returns a sequence of the the tail of the collection

collection.cons(item)Returns a new collection with item at the head and the collection at the tail

In Rich Hickey's Clojure dialect of LISP he added the concept of sequences which is any collection implementing the first, rest and cons methodsI've shown it on the slide in JS form.

Now that we depend on an interface we can now also make an interresting observation.

Lazy Sequences

Only really care about the data in the list when we call first()

rest() can be lazily evaluated

Until we actually ask for an element of the sequence we're not obliged to evaluate it.That means so long as we're able to evaluate the head at will we never need to bother with the rest until we need it.This means we can save on some unncecessary computation and also have potentially infinite sequences.

Using Lazy Seqences

map = function(coll, fn) {return lazySeq(function() {var head = coll.first(list), tail = coll.rest(list);return lazySeq.cons(map(tail, fn), fn(head));

});

};

The Interface Problem

How do we check for the sequence interface?

How do we apply the interface implementation if it's missing?

What do we do if some object already has a first, rest or cons which are different from the ones we expect?

Monkey Patching

MyColl.protoype.first = function(coll) {}MyColl.protoype.rest = function(coll) {}MyColl.protoype.cons = function(coll, item) {}

The Adapter Pattern

mySeq =mySeqAdapter(myColl);head = mySeq.first();tail = mySeq.rest();newSeq = myColl.cons(item);

Switching

function first(coll) {if (coll === null)return null;

else if (isArray(coll))return coll[0];

else if ('function' === typeof coll.first)return coll.first();

elsethrow new Error('not a sequence');

}

Is there some way to do this automatically?

Yes: protocols

Protocols

var Sequence = protocol.define('Sequence',[ 'first', [ 'coll' ] ],[ 'rest', [ 'coll' ] ],[ 'cons', [ 'coll' ] ]

);protocol.extend(SequenceMyCollection,[ 'first', function (coll) { return coll.head(); } ],[ 'rest', function (coll) { return coll.tail(); } ],[ 'cons', function (coll, item) { return coll.cons(item); }

);

Protocols vs. Interfaces

Protocol

Interface

ImplementationProtocolsomeFunction

Calls

Calls

InterfaceObjectsomeFunction

Implements

Calls

Object

Calls

Think of the boxes as source filesMeaning the responsibility is decoulpled.

Using Protocols

var first = Sequence.first, rest = Sequence.rest,cons = Sequence.cons;

map = function(coll, fn) {return lazySeq(function() {var head = first(coll, list), tail = rest(coll, list);return cons(map(tail, fn), fn(head));

});

};

What about Node?

Sequences are synchronous

Node is aynchronous

Pull vs. Push

Async in Node

StreamsEvented

Callbacks

Buffered

Composable

PromisesSingle shot

Callbacks

Unbuffered

Composable

Async Map

map = function(stream, fn) {var result = stream.Stream();result.writable = true;stream.on(data, function(data) {result.write(fn(data));

});return result;

};

Async Sequences

first(stream)A promise to the first item in the first buffer in the stream.

rest(stream)A sequence of the rest of the items in the first buffer with the original stream at the tail.

cons(stream, item)A sequence with a promise to the item at the head and the stream as the tail.

Observations

Lazy Sequences are like promises

Syncronous values can be reresented as promises that resolve immediately

With protocols we can conjoin sequences.

Potential Uses

Higher level abstractions

Mix and match sync & async

Write once run anywhere

Leverage new technologies e.g. WebCL

Get cosy

cosy.langProtocols

Lazy Sequences

Tail Recursion

http://bnt.getcosy.org (v0)http://getcosy.org (v1+)

To be continued...

Part 2Async Sequences

Tail Recursion

Live Demos

Questions

CLIQUE PARA EDITAR O FORMATO DO TEXTO DO TTULO

Clique para editar o formato do texto da estrutura de tpicos2. Nvel da estrutura de tpicos3. Nvel da estrutura de tpicos4. Nvel da estrutura de tpicos5. Nvel da estrutura de tpicos6. Nvel da estrutura de tpicos7. Nvel da estrutura de tpicos