practical functional java script

72
Practical Functional JavaScript Oliver Steele Ajax Experience Wednesday, 1 October 2008

Upload: thinkphp

Post on 30-Apr-2015

1.895 views

Category:

Education


3 download

DESCRIPTION

Practical Functional JavaScript

TRANSCRIPT

Page 1: Practical functional java script

Practical Functional JavaScript

Oliver SteeleAjax Experience

Wednesday, 1 October 2008

Page 2: Practical functional java script

Teasers

• AJAX is all about waiting for someone*, and remembering what you were going to do when they got back to you.

• Functions : interactions :: objects : domains

• You didn't really want threads anyway. (Most of the time.)

* user, web server, other server, wall clock, plugin

Page 3: Practical functional java script

About Mewhere? what? graphics languages

implementing usinggraphics languages

✔ ✔

✔ ✔

✔ ✔

✔ ✔

✔ ✔

Oblong IndustriesRuby bindings for “Minority

Report”-style interfaces

Entrepreneurial & Consulting

BrowseGoodsStyle&Share

FansnapWebtop Calendar

Laszlo Systems OpenLaszlo

Apple Advanced Technology Group

Dylan(programming language)

Apple System Software Skia(graphics library)

Page 4: Practical functional java script

About YouRaise your hand if you know*:

* none of these are required; this just helps me calibrate the talk

• Closures

• Ruby / Smalltalk

• XHR / AJAX

• An AJAX framework (Prototype / jQuery / …)

• Threads

Page 5: Practical functional java script

Agenda• Context

• Example Applications

• MVC on the Client

• Fundamentals

• Closures (review)

• Making Functions

• Decorating Functions

• Some Idioms

• Callbacks

• Queueing & Throttling

• Caching & Joining

• Retries & Failover

• Conclusions

• Q&A

Page 6: Practical functional java script

Non-Agenda

• Comet, Bayeux, Gears

• Frameworks*

• Theory (this isn't your Monad or CPS fix)

• Security (standard practices apply)

* This talk should help you understand their implementation and use, but doesn't explore their APIs in any depth

Page 7: Practical functional java script

Example Applications

Page 8: Practical functional java script

BrowseGoods• Visually browse an

Amazon department, graphically arranged

• Server: Ruby (layout, image processing, app server)

• Client: Prototype + Scriptaculous

Page 9: Practical functional java script
Page 10: Practical functional java script

BrowseGoods Capabilities

• Background prefetch of item maps

• Background fetch of saved items

• Operations on saved items are queued until initialization is complete

Page 11: Practical functional java script

Style & Share

Web Application Processes

Image ProcessGarbage Collector

Image Retriever

Catalog Update Process

Product Request Queue

Amazon ECS Server

Catalog Controller

Image Editor

Catalog Carts Images

Catalog Updater

ECS Client

Cart Controller

Image Controller

Image Request Queue

Cart Sweeper

Amazon Image Servers

Catalog Model

Internet

Client

Internet

Server Architecture

ECS Server

Catalog Search

Cart Manager

ECS Cart Proxy

ECS Connection

Server Cart Proxy

Style&Share Server

ECS Search

Gallery Selector

Server Proxy

Internet

AJAX Throttle

Task Scheduler

Client Architecture

Page 12: Practical functional java script
Page 13: Practical functional java script

Style & Share Capabilities

• Retry with exponential backoff and failover

• Explicit queues to control serialization order

• Background prefetch for catalog items

• Multiple queues to prioritize user interaction

Page 14: Practical functional java script

Fansnap• Visualize available seats

in a venue

• Seatmap is OpenLaszlo (compiled to Flash)

• Widgets are in jQuery

Page 15: Practical functional java script
Page 16: Practical functional java script

FanSnap Capabilities(Seatmap)

• Two-way asynchronous communication between the Flash plugin and the HTML

• Asynchronous communication between the Flash plugin and the server

• Again, initialization is particularly challenging

Page 17: Practical functional java script

Webtop Calendar

Da

ta L

ay

er

Calendar Client

Webtop Calendar Client Data Architecture

Range Cache

Calendar Model

Calendar ServiceCalendar Collection

EventObserver

View Layer

Event Cache

Serializer

Webtop Client Library

CalendarConnection

Event ReportCache

Webtop Server

Internet

Event Model

Page 18: Practical functional java script
Page 19: Practical functional java script

Webtop Calendar Capabilities (Data Model)

• Synchronizes local model with server model

• Local model is cache: some operations update it; others invalidate it

• Race conditions, where prefetch overlaps operations that invalidate the cache

Page 20: Practical functional java script

Motivating Problem:Web MVC

View

Controller

Model

Server

View

Controller

Model User

Client

Page 21: Practical functional java script

MVC Basics

Page 22: Practical functional java script

Server:Waiting on the Client

View

Controller

Model User

Server Client

Page 23: Practical functional java script

Client:Waiting on the Server

View

Controller

Model

Client

Model

Server

Page 24: Practical functional java script

Client:Waiting on the User

View

Controller

Model User

Client

Page 25: Practical functional java script

Lots of Waiting• Client (server session)

• Server (XHR)

• User (UI event)

• Future (setTimeout)

• Concurrency

• State

Page 26: Practical functional java script

Function Fundamentals

Page 27: Practical functional java script

What is a Function?• Math: rule that maps inputs to outputs

• Computer science: abstracted computation with effects and outputs

• Software engineering: one of several units for documentation, testing, assertions, and analysis

• Source code: unit of source text with inputs and outputs

• Runtime: invocable parameterized behavior

Source Code

Runtime Object

Docs

Specs

Def’n

ContractImplementation

Call graphCreate

Invoke

Register

Store

Pass

Page 28: Practical functional java script

Nested Functions: Globals

function callback(x) { log('received "' + x + '"');} function request() { $.get('/request', callback);} request();  

Page 29: Practical functional java script

Nested Functions: Variables

var callback = function(x) { log('received "' + x + '"');} var request = function() { $.get('/request', callback);} request();  

Page 30: Practical functional java script

Nested Functions: Function-First Style

function request() { function callback(x) { log('received "' + x + '"'); } $.get('/request', callback);} request();  

Page 31: Practical functional java script

Nested Functions: Pyramid Order

function request() { $.get('/request', callback); function callback(x) { log('received "' + x + '"'); }} request();  

Page 32: Practical functional java script

Nested Functions: Inlining the Function

function request() { $.get('/request', function callback(x) { log('received "' + x + '"'); });} request();  

Page 33: Practical functional java script

Nested Functions: Anonymous

function request() { $.get('/request', function (x) { log('received "' + x + '"'); });} request(); 

Page 34: Practical functional java script

Creating Functions:Basics

function makeConst1() { return function() { return 1; }} function const1a() { return 1; }var const1b = function() { return 1; }var const1c = makeConst1(); log(const1a());log(const1b());log(const1c()); log(makeConst1()());  

Page 35: Practical functional java script

Creating Functions: Variable Capture

function makeConstN(n) { return function() { return n; }} var const10 = makeConstN(10);log(const10());log(const10(100)); var const20 = makeConstN(20);log(const20());log(const20(100));  

Page 36: Practical functional java script

Creating Functions: Capturing More Variables function makePlus1() { return function(x) { return x + 1; }}log(makePlus1()(10));

function makePlusN(n) { return function(x) { return x + n; }}var plus10 = makePlusN(10);log(plus10(100));   

Page 37: Practical functional java script

Creating Functions: Function-Valued Parameters

function plus1(x) { return x+1;} function twice(fn) { return function(x) { return fn(fn(x)); }} var plus2 = twice(plus1);log(plus2(10));  

Page 38: Practical functional java script

Creating Functions: Storage

var FnTable = { '+1': function(n) { return n+1; }, '+2': function(n) { return n+2; }}; log(FnTable['+1'](10));log(FnTable['+2'](10));  

Page 39: Practical functional java script

Creating Functions: Building a Registry

var FnTable = {};function register(name, fn) { FnTable[name] = fn; }function tableMethod(name) { return FnTable[name]; }

function makeAdder(n) { return function(x) { return x + n; }} register('+1', makeAdder(1));register('+2', makeAdder(2));

log(tableMethod('+1')(10));log(tableMethod('+2')(10));  

Page 40: Practical functional java script

Creating Functions: Manual Guards

for (var i = -5; i < 5; i++) log(i);  function callIfPositive(fn) { return function(x) { return x > 0 ? fn(x) : undefined; }} var logIfPositive = callIfPositive(log); for (var i = -5; i < 5; i++) logIfPositive(i);  

Page 41: Practical functional java script

Creating Functions: Guard Construction

function guard(fn, g) { return function(x) { return g(x) ? fn(x) : undefined; }} function callIfPositive(fn) { return guard(fn, function(x) { return x > 0; });} var logIfPositive = callIfPositive(log); for (var i = -5; i < 5; i++) logIfPositive(i); 

Page 42: Practical functional java script

Closures (1)

var get, set;function assignAccessors() { var x = 1; get = function() { return x; } set = function(y) { x = y; }}

assignAccessors();log(get());set(10);log(get());

Page 43: Practical functional java script

Closures (2) function makeAccessors() { var x; return {get: function() { return x; }, set: function(y) { x = y; }}}

var gf1 = makeAccessors();var gf2 = makeAccessors();gf1.set(10);gf2.set(20);log(gf1.get());log(gf2.get()); 

Page 44: Practical functional java script

Function Construction Idioms:Special Variables

// 'this' and 'arguments' are specialfunction f() { logArguments(this, arguments);}f();f('a');f('a', 'b');  

Page 45: Practical functional java script

Function Construction Idioms:Function Call and Apply

function f() { logArguments(this, arguments); }

// these are equivalent:f(1, 2, 3);f.call(window, 1, 2, 3);f.apply(window, [1, 2, 3]);  

Page 46: Practical functional java script

Function Construction Idioms:Method Call and Apply

var obj = { f: function() { logArguments(this, arguments); }};

// and so are these:obj.f(1, 2, 3);obj.f.call(obj, 1, 2, 3);obj.f.apply(obj, [1, 2, 3]);

Page 47: Practical functional java script

Function Construction Idioms:Borrowing Methods (Bad)

// stealing a method the wrong wayvar o1 = {name: 'o1', show: function() { log(this.name); }};var o2 = {name: 'o2'};o1.show();o2.show = o1.show;o2.show();delete o2.show;  

Page 48: Practical functional java script

Function Construction Idioms:Borrowing Methods (Better)

// using 'apply' to steal a methodvar o1 = {name: 'o1', show: function() { log(this.name); }};var o2 = {name: 'o2'};o1.show();o1.show.call(o2);o1.show.apply(o2, []); 

Page 49: Practical functional java script

Function Construction Idioms:arguments is special

// arguments isn't an Array, so this doesn't work:function capture() { var args = arguments.slice(0); // ...} // instead, steal the 'slice' method from an instance of Array,// and apply it:function capture() { var args = [].slice.call(arguments, 0); // ...} // or just take it from Array's prototypefunction capture() { var args = Array.prototype.slice.call(arguments, 0); // ...} 

Page 50: Practical functional java script

Function Construction Idioms:Wrapping Variadic Functions

function id1(fn) { return function(x) { return fn(x); }} function id2(fn) { return function(x, y) { return fn(x, y); }} function idn(fn) { return function() { return fn.apply(this, arguments); }}

Page 51: Practical functional java script

Function Construction Idioms:Record and Replay

var queue = [];function capture() { queue.push(Array.prototype.slice.call(arguments, 0));}function replay() { while (queue.length) fn.apply(null, queue.shift());}function fn(a, b) { log(a + ' + ' + b + ' = ' + (a+b));}capture(1,2);capture(1,3);capture(10,20);replay();

Page 52: Practical functional java script

Function Construction Idioms:Extending Function’s PrototypeFunction.prototype.twice = function() { var fn = this; return function() { return fn.call(this, fn.apply(this, arguments)); };}

function plus1(x) { return x+1; }var plus2 = plus1.twice();log(plus2(10)); 

Page 53: Practical functional java script

Summary

• Functions are values

• Functions can be arguments, return values, array elements, property values

• Functions can be created and “modified”

• Argument lists can be saved, modified, and replayed

Page 54: Practical functional java script

Case Study: CallbacksServer Client

<%@page

<html>

<?= t

<meta

<canvas

<text

<view

<?xml v

<%@ tag

<xslt:t<?xml

<root>

<row>

</row>

<?xml v

<%@ tag

<xslt:t

<?xml

<root>

<row>

</row>

<?xml

<root>

<row>

</row>

XML

XML

Page 55: Practical functional java script

Callback Scenarii• Chained Callbacks

• Queues and Priority

• Throttling

• Caching

• Timeouts

• Retry and Failover

• Conjunctive-Trigger Callbacks

• Conditional Callbacks

• Outdated Responses

Page 56: Practical functional java script

Throttling:Unthrottled

for (var i = 0; i < 10; i++) $.get('/services/time', log);  

Page 57: Practical functional java script

Frequency Throttling:Call Site

var gCounter = 0;function runNext() { if (gCounter < 10) { setTimeout(function() { $.get('/services/time', log); runNext(); }, 1000); gCounter++; }}runNext();  

Page 58: Practical functional java script

Frequency Throttling:Manual Wrapper

var gQueue = [];var gNextTime = 0;$.throttled = function(url, k) { gQueue.push([url, k]); if (gQueue.length == 1) schedule(); function schedule() { setTimeout(function() { gNextTime = new Date().getTime() + 1000; var entry = gQueue.shift(); $.get(entry[0], entry[1]); if (gQueue.length) schedule(); }, Math.max(0, gNextTime - new Date().getTime())); }};  

for (var i = 0; i < 10; i++) $.throttled('/services/time', log);

Page 59: Practical functional java script

Frequency Throttling:Function Constructor

function makeThrottled(fn, interval) { var queue = []; var nextTime = 0; return function() { queue.push(Array.prototype.slice.call(arguments, 0)); if (queue.length == 1) schedule(); } function schedule() { setTimeout(function() { nextTime = new Date().getTime() + interval; fn.apply(null, queue.shift()); if (queue.length) schedule(); }, Math.max(0, nextTime - new Date().getTime())); }}

$.throttled = makeThrottled($.get, 1000);

for (var i = 0; i < 10; i++) $.throttled('/services/time', log);  

Page 60: Practical functional java script

Count Throttling:Manual Wrapper

var gQueue = [];var gOutstanding = 0;$.throttled = function(url, k) { function k2() { gOutstanding--; k.apply(this, arguments); if (gOutstanding < 2 && gQueue.length) { var entry = gQueue.shift(); $.get(entry[0], entry[1]); } } if (gOutstanding < 2) { gOutstanding++; $.get(url, k2); } else gQueue.push([url, k2]);};

for (var i = 0; i < 10; i++) $.throttled('/services/sleep/2', log);  

Page 61: Practical functional java script

Count Throttling:Constructor

function makeLimited(fn, count) { var queue = []; var outstanding = 0; return function() { var args = Array.prototype.slice.call(arguments, 0); // replace the last arg by one that runs the // next queued fn args.push(adviseAfter(args.pop(), next)); if (outstanding < count) { outstanding++; fn.apply(this, args); } else queue.push(args); } function next() { if (queue.length) fn.apply(null, queue.shift()); }}

$.throttled = makeLimited($.get, 2); for (var i = 0; i < 10; i++) $.throttled('/services/sleep/2', log);

Page 62: Practical functional java script

Frequency Throttling:Function Constructor

function makeThrottled(fn, interval) { var queue = []; var nextTime = 0; return function() { queue.push(Array.prototype.slice.call(arguments, 0)); if (queue.length == 1) schedule(); } function schedule() { setTimeout(function() { nextTime = new Date().getTime() + interval; fn.apply(null, queue.shift()); if (queue.length) schedule(); }, Math.max(0, nextTime - new Date().getTime())); }}

$.throttled = makeThrottled($.get, 1000);

for (var i = 0; i < 10; i++) $.throttled('/services/time', log);  

Page 63: Practical functional java script

Retry$.getWithRetry = function(url, k) { var countdown = 10; $.ajax({url:url, success:k, error:retry}); function retry() { if (--countdown >= 0) { log('retry'); $.ajax({url:url, success:k, error:retry}); } }};

$.getWithRetry('/services/error', log);  

Page 64: Practical functional java script

Throttled Retry var gPageLoadTime = new Date;$.getWithRetry = function(url, k) { var countdown = 10; var delay = 1000; var nextTime = new Date().getTime() + delay; $.ajax({url:url, success:k, error:retry}); function retry() { if (--countdown >= 0) { setTimeout(function() { delay *= 1.5; log('retry@t+' + (new Date - gPageLoadTime)/1000 + 's'); nextTime = new Date().getTime() + delay; $.ajax({url:url, success:k, error:retry}); }, Math.max(0, nextTime - new Date().getTime())); } }};

$.getWithRetry('/services/error', log);

Page 65: Practical functional java script

Failover

$.getWithFailover = function(urls, k) { $.ajax({url:urls.shift(), success:k, error:retry}); function retry() { if (urls.length) $.ajax({url:urls.shift(), success:k, error:retry}); }};

$.getWithFailover( ['/services/error', '/services/error', '/services/time'], log); 

Page 66: Practical functional java script

Caching (1) var gRequestCache = {};$.cachedGet = function(url, k) { if (url in gRequestCache) k(gRequestCache[url], 'success'); else $.get(url, adviseBefore(k, function(result, status) { if (status == 'success') gRequestCache[url] = result; }));};

$.cachedGet('/services/random', log);$.cachedGet('/services/random', log);$.cachedGet('/services/echo/1', log);$.cachedGet('/services/echo/2', log);setTimeout(function() {$.cachedGet('/services/random', log);},

1000);  

Page 67: Practical functional java script

Caching (2)

var gPendingRequests = {};var gRequestCache = {};$.cachedGet = function(url, k) { if (url in gRequestCache) k(gRequestCache[url], 'success'); else if (url in gPendingRequests) gPendingRequests[url].push(k); else { var queue = [k]; gPendingRequests[url] = queue; $.get(url, function(result, status) { if (status == 'success') gRequestCache[url] = result; while (queue.length) queue.shift().call(this, result, status); delete gPendingRequests[url]; }); }}; 

$.cachedGet('/services/random', log);$.cachedGet('/services/random', log);$.cachedGet('/services/echo/1', log);$.cachedGet('/services/echo/2', log);$.cachedGet('/services/random', log);

Page 68: Practical functional java script

Caching (3)function memoizedContinuation(fn) { var cache = {}; return function(key, k) { if (key in cache) k(cache[key]); else fn(key, k); }}function consolidateContinuations(fn) { var queues = {}; return function(key, k) { if (key in queues) queues[key].push(k); else { var queue = queues[key] = [k]; fn(key, function(value) { while (queue.length) queue.shift().call(this, value); delete queues[key]; }); } }}$.cachedGet = consolidateContinuations(memoizedContinuation($.get));

$.cachedGet('/services/random', log);$.cachedGet('/services/random', log);$.cachedGet('/services/echo/1', log);$.cachedGet('/services/echo/2', log);$.cachedGet('/services/random', log); 

Page 69: Practical functional java script

Summary

• Functions-as-objects allow separation of concerns

• Factor how, when, and whether from what

• Functions are to interaction patterns as objects are to domains

Page 70: Practical functional java script

What is FP?

• Functions are pure

• Functions are values

Page 71: Practical functional java script

Q&A

Page 72: Practical functional java script

Thanks!

– Oliver Steele

http://osteele.com