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