Arrows in Commercial Web Applications - HotWeb2016.pdf · Research History Directing JavaScript...

Post on 19-Sep-2020

4 views 0 download

Transcript of Arrows in Commercial Web Applications - HotWeb2016.pdf · Research History Directing JavaScript...

Arrows in Commercial Web Applications

Eric Fritz Jose Antony Tian Zhao

University of Wisconsin - Milwaukee

1 / 13

Research History

Directing JavaScript with ArrowsKhoo Yit Phang, Michael Hicks, Jeffrey S. Foster, and Vibha Sazawal.5th Symposium on Dynamic Languages, 2009.

Inferring Types of Asynchronous Arrows in JavaScriptEric Fritz and Tian Zhao.Reactive and Event-based Languages and Systems, 2015.

Typing and Semantics of Asynchronous Arrows in JavaScriptEric Fritz and Tian Zhao.In Review, 2016.

Arrows in Commercial Web ApplicationsEric Fritz, Jose Antony, and Tian Zhao.HotWeb, 2016.

2 / 13

Research History

Directing JavaScript with ArrowsKhoo Yit Phang, Michael Hicks, Jeffrey S. Foster, and Vibha Sazawal.5th Symposium on Dynamic Languages, 2009.

Inferring Types of Asynchronous Arrows in JavaScriptEric Fritz and Tian Zhao.Reactive and Event-based Languages and Systems, 2015.

Typing and Semantics of Asynchronous Arrows in JavaScriptEric Fritz and Tian Zhao.In Review, 2016.

Arrows in Commercial Web ApplicationsEric Fritz, Jose Antony, and Tian Zhao.HotWeb, 2016.

2 / 13

Research History

Directing JavaScript with ArrowsKhoo Yit Phang, Michael Hicks, Jeffrey S. Foster, and Vibha Sazawal.5th Symposium on Dynamic Languages, 2009.

Inferring Types of Asynchronous Arrows in JavaScriptEric Fritz and Tian Zhao.Reactive and Event-based Languages and Systems, 2015.

Typing and Semantics of Asynchronous Arrows in JavaScriptEric Fritz and Tian Zhao.In Review, 2016.

Arrows in Commercial Web ApplicationsEric Fritz, Jose Antony, and Tian Zhao.HotWeb, 2016.

2 / 13

Research History

Directing JavaScript with ArrowsKhoo Yit Phang, Michael Hicks, Jeffrey S. Foster, and Vibha Sazawal.5th Symposium on Dynamic Languages, 2009.

Inferring Types of Asynchronous Arrows in JavaScriptEric Fritz and Tian Zhao.Reactive and Event-based Languages and Systems, 2015.

Typing and Semantics of Asynchronous Arrows in JavaScriptEric Fritz and Tian Zhao.In Review, 2016.

Arrows in Commercial Web ApplicationsEric Fritz, Jose Antony, and Tian Zhao.HotWeb, 2016.

2 / 13

Example Application - Inventory Search

Requirements:Filter displayed results by name and category

Paginate results when filtered set is > 50 items

Performance Optimizations:Cache server results for same query/page

Attempt to pre-fetch the next page of resultsDon’t make a remote request while the user is typing

3 / 13

Example Application - Inventory Search

Requirements:Filter displayed results by name and category

Paginate results when filtered set is > 50 items

Performance Optimizations:Cache server results for same query/page

Attempt to pre-fetch the next page of resultsDon’t make a remote request while the user is typing

3 / 13

Example Application - Inventory Search

Requirements:Filter displayed results by name and category

Paginate results when filtered set is > 50 items

Performance Optimizations:Cache server results for same query/page

Attempt to pre-fetch the next page of resultsDon’t make a remote request while the user is typing

3 / 13

Sequencing Filtering + Paging (Using Callbacks)

function call(query , page , handler) {

$.ajax({ 'url': makeURL(query , page), 'success ': handler });

}

function showPage(resp) {

const pageTo = (page) => () => {

$('#prev , #next'). unbind('click ');call(resp.query , page , showPage );

};

displayTable(resp);

$('#prev').one('click ', pageTo(resp.prev ));

$('#next').one('click ', pageTo(resp.next ));

}

$('#filter ').keyup((ev) => {

$('#prev , #next'). unbind('click ');call($(ev.target ).val(), 1, showPage );

});

4 / 13

Sequencing Filtering + Paging (Using Callbacks)

function call(query , page , handler) {

$.ajax({ 'url': makeURL(query , page), 'success ': handler });

}

function showPage(resp) {

const pageTo = (page) => () => {

$('#prev , #next'). unbind('click ');call(resp.query , page , showPage );

};

displayTable(resp);

$('#prev').one('click ', pageTo(resp.prev ));

$('#next').one('click ', pageTo(resp.next ));

}

$('#filter ').keyup((ev) => {

$('#prev , #next'). unbind('click ');call($(ev.target ).val(), 1, showPage );

});

Call Stack Event Queue

main

4 / 13

Sequencing Filtering + Paging (Using Callbacks)

function call(query , page , handler) {

$.ajax({ 'url': makeURL(query , page), 'success ': handler });

}

function showPage(resp) {

const pageTo = (page) => () => {

$('#prev , #next'). unbind('click ');call(resp.query , page , showPage );

};

displayTable(resp);

$('#prev').one('click ', pageTo(resp.prev ));

$('#next').one('click ', pageTo(resp.next ));

}

$('#filter ').keyup((ev) => {

$('#prev , #next'). unbind('click ');call($(ev.target ).val(), 1, showPage );

});

Call Stack Event Queue

main keyup keyup 7→ closure1

4 / 13

Sequencing Filtering + Paging (Using Callbacks)

function call(query , page , handler) {

$.ajax({ 'url': makeURL(query , page), 'success ': handler });

}

function showPage(resp) {

const pageTo = (page) => () => {

$('#prev , #next'). unbind('click ');call(resp.query , page , showPage );

};

displayTable(resp);

$('#prev').one('click ', pageTo(resp.prev ));

$('#next').one('click ', pageTo(resp.next ));

}

$('#filter ').keyup((ev) => {

$('#prev , #next'). unbind('click ');call($(ev.target ).val(), 1, showPage );

});

Call Stack Event Queue

main keyup 7→ closure1

4 / 13

Sequencing Filtering + Paging (Using Callbacks)

function call(query , page , handler) {

$.ajax({ 'url': makeURL(query , page), 'success ': handler });

}

function showPage(resp) {

const pageTo = (page) => () => {

$('#prev , #next'). unbind('click ');call(resp.query , page , showPage );

};

displayTable(resp);

$('#prev').one('click ', pageTo(resp.prev ));

$('#next').one('click ', pageTo(resp.next ));

}

$('#filter ').keyup((ev) => {

$('#prev , #next'). unbind('click ');call($(ev.target ).val(), 1, showPage );

});

Call Stack Event Queue

keyup 7→ closure1

4 / 13

Sequencing Filtering + Paging (Using Callbacks)

function call(query , page , handler) {

$.ajax({ 'url': makeURL(query , page), 'success ': handler });

}

function showPage(resp) {

const pageTo = (page) => () => {

$('#prev , #next'). unbind('click ');call(resp.query , page , showPage );

};

displayTable(resp);

$('#prev').one('click ', pageTo(resp.prev ));

$('#next').one('click ', pageTo(resp.next ));

}

$('#filter ').keyup((ev) => {

$('#prev , #next'). unbind('click ');call($(ev.target ).val(), 1, showPage );

});

Call Stack Event Queue

closure1 keyup 7→ closure1

4 / 13

Sequencing Filtering + Paging (Using Callbacks)

function call(query , page , handler) {

$.ajax({ 'url': makeURL(query , page), 'success ': handler });

}

function showPage(resp) {

const pageTo = (page) => () => {

$('#prev , #next'). unbind('click ');call(resp.query , page , showPage );

};

displayTable(resp);

$('#prev').one('click ', pageTo(resp.prev ));

$('#next').one('click ', pageTo(resp.next ));

}

$('#filter ').keyup((ev) => {

$('#prev , #next'). unbind('click ');call($(ev.target ).val(), 1, showPage );

});

Call Stack Event Queue

callclosure1 keyup 7→ closure1 ajax 7→ showPage

4 / 13

Sequencing Filtering + Paging (Using Callbacks)

function call(query , page , handler) {

$.ajax({ 'url': makeURL(query , page), 'success ': handler });

}

function showPage(resp) {

const pageTo = (page) => () => {

$('#prev , #next'). unbind('click ');call(resp.query , page , showPage );

};

displayTable(resp);

$('#prev').one('click ', pageTo(resp.prev ));

$('#next').one('click ', pageTo(resp.next ));

}

$('#filter ').keyup((ev) => {

$('#prev , #next'). unbind('click ');call($(ev.target ).val(), 1, showPage );

});

Call Stack Event Queue

closure1 keyup 7→ closure1 ajax 7→ showPage

4 / 13

Sequencing Filtering + Paging (Using Callbacks)

function call(query , page , handler) {

$.ajax({ 'url': makeURL(query , page), 'success ': handler });

}

function showPage(resp) {

const pageTo = (page) => () => {

$('#prev , #next'). unbind('click ');call(resp.query , page , showPage );

};

displayTable(resp);

$('#prev').one('click ', pageTo(resp.prev ));

$('#next').one('click ', pageTo(resp.next ));

}

$('#filter ').keyup((ev) => {

$('#prev , #next'). unbind('click ');call($(ev.target ).val(), 1, showPage );

});

Call Stack Event Queue

keyup 7→ closure1 ajax 7→ showPage

4 / 13

Sequencing Filtering + Paging (Using Callbacks)

function call(query , page , handler) {

$.ajax({ 'url': makeURL(query , page), 'success ': handler });

}

function showPage(resp) {

const pageTo = (page) => () => {

$('#prev , #next'). unbind('click ');call(resp.query , page , showPage );

};

displayTable(resp);

$('#prev').one('click ', pageTo(resp.prev ));

$('#next').one('click ', pageTo(resp.next ));

}

$('#filter ').keyup((ev) => {

$('#prev , #next'). unbind('click ');call($(ev.target ).val(), 1, showPage );

});

Call Stack Event Queue

showPage keyup 7→ closure1 ajax 7→ showPage

4 / 13

Sequencing Filtering + Paging (Using Callbacks)

function call(query , page , handler) {

$.ajax({ 'url': makeURL(query , page), 'success ': handler });

}

function showPage(resp) {

const pageTo = (page) => () => {

$('#prev , #next'). unbind('click ');call(resp.query , page , showPage );

};

displayTable(resp);

$('#prev').one('click ', pageTo(resp.prev ));

$('#next').one('click ', pageTo(resp.next ));

}

$('#filter ').keyup((ev) => {

$('#prev , #next'). unbind('click ');call($(ev.target ).val(), 1, showPage );

});

Call Stack Event Queue

showPage keyup 7→ closure1 prev 7→ closure2next 7→ closure3

4 / 13

Sequencing Filtering + Paging (Using Callbacks)

function call(query , page , handler) {

$.ajax({ 'url': makeURL(query , page), 'success ': handler });

}

function showPage(resp) {

const pageTo = (page) => () => {

$('#prev , #next'). unbind('click ');call(resp.query , page , showPage );

};

displayTable(resp);

$('#prev').one('click ', pageTo(resp.prev ));

$('#next').one('click ', pageTo(resp.next ));

}

$('#filter ').keyup((ev) => {

$('#prev , #next'). unbind('click ');call($(ev.target ).val(), 1, showPage );

});

Call Stack Event Queue

keyup 7→ closure1 prev 7→ closure2next 7→ closure3

4 / 13

Sequencing Filtering + Paging (Using Callbacks)

function call(query , page , handler) {

$.ajax({ 'url': makeURL(query , page), 'success ': handler });

}

function showPage(resp) {

const pageTo = (page) => () => {

$('#prev , #next'). unbind('click ');call(resp.query , page , showPage );

};

displayTable(resp);

$('#prev').one('click ', pageTo(resp.prev ));

$('#next').one('click ', pageTo(resp.next ));

}

$('#filter ').keyup((ev) => {

$('#prev , #next'). unbind('click ');call($(ev.target ).val(), 1, showPage );

});

Call Stack Event Queue

closure3 keyup 7→ closure1 prev 7→ closure2next 7→ closure3

4 / 13

Sequencing Filtering + Paging (Using Callbacks)

function call(query , page , handler) {

$.ajax({ 'url': makeURL(query , page), 'success ': handler });

}

function showPage(resp) {

const pageTo = (page) => () => {

$('#prev , #next'). unbind('click ');call(resp.query , page , showPage );

};

displayTable(resp);

$('#prev').one('click ', pageTo(resp.prev ));

$('#next').one('click ', pageTo(resp.next ));

}

$('#filter ').keyup((ev) => {

$('#prev , #next'). unbind('click ');call($(ev.target ).val(), 1, showPage );

});

Call Stack Event Queue

closure3 keyup 7→ closure1 prev 7→ closure2

4 / 13

Sequencing Filtering + Paging (Using Callbacks)

function call(query , page , handler) {

$.ajax({ 'url': makeURL(query , page), 'success ': handler });

}

function showPage(resp) {

const pageTo = (page) => () => {

$('#prev , #next'). unbind('click ');call(resp.query , page , showPage );

};

displayTable(resp);

$('#prev').one('click ', pageTo(resp.prev ));

$('#next').one('click ', pageTo(resp.next ));

}

$('#filter ').keyup((ev) => {

$('#prev , #next'). unbind('click ');call($(ev.target ).val(), 1, showPage );

});

Call Stack Event Queue

callclosure3 keyup 7→ closure1 ajax 7→ showPage

4 / 13

Callbacks - The Problems

Lose flow of (synchronous) code

Event registration/de-registration is ad-hocLose imperative exception semantics

try {

User.findOne ({name: 'Eric'}, (err , obj) => {

if (err != null) { throw err; }

else if (obj == null) { throw new Error('Not Found '); }

else { /* ... */ }

});

} catch (err) {

/* ... */

}

20% of function calls provide a callback72% of callbacks are asynchronous48% of callbacks are anonymous

(Gallaba et al.)

5 / 13

Callbacks - The Problems

Lose flow of (synchronous) codeEvent registration/de-registration is ad-hoc

Lose imperative exception semantics

try {

User.findOne ({name: 'Eric'}, (err , obj) => {

if (err != null) { throw err; }

else if (obj == null) { throw new Error('Not Found '); }

else { /* ... */ }

});

} catch (err) {

/* ... */

}

20% of function calls provide a callback72% of callbacks are asynchronous48% of callbacks are anonymous

(Gallaba et al.)

5 / 13

Callbacks - The Problems

Lose flow of (synchronous) codeEvent registration/de-registration is ad-hoc

Lose imperative exception semantics

try {

User.findOne ({name: 'Eric'}, (err , obj) => {

if (err != null) { throw err; }

else if (obj == null) { throw new Error('Not Found '); }

else { /* ... */ }

});

} catch (err) {

/* ... */

}

20% of function calls provide a callback72% of callbacks are asynchronous48% of callbacks are anonymous

(Gallaba et al.)

5 / 13

Callbacks - The Problems

Lose flow of (synchronous) codeEvent registration/de-registration is ad-hoc

Lose imperative exception semantics

try {

User.findOne ({name: 'Eric'}, (err , obj) => {

if (err != null) { throw err; }

else if (obj == null) { throw new Error('Not Found '); }

else { /* ... */ }

});

} catch (err) {

/* ... */

}

20% of function calls provide a callback72% of callbacks are asynchronous48% of callbacks are anonymous

(Gallaba et al.)

5 / 13

Promises – A Better Abstraction

PendingWait toSettle

Fulfilled

Rejected

resolve(promise)

resolve(value)

reject(reason)

Settled

var promise = new Promise((resolve , reject) => {

conn.query('SELECT * from items ', (err , row) => {

if (!!err) resolve(row);

else reject(err);

});

});

promise.then(onSuccess );

promise.then(onSuccess , onError );

promise.then(onSuccess ). catch(onError );

6 / 13

Promises – A Better Abstraction

PendingWait toSettle

Fulfilled

Rejected

resolve(promise)

resolve(value)

reject(reason)

Settled

var promise = new Promise((resolve , reject) => {

conn.query('SELECT * from items ', (err , row) => {

if (!!err) resolve(row);

else reject(err);

});

});

promise.then(onSuccess );

promise.then(onSuccess , onError );

promise.then(onSuccess ). catch(onError );

6 / 13

Promises – A Better Abstraction

PendingWait toSettle

Fulfilled

Rejected

resolve(promise)

resolve(value)

reject(reason)

Settled

var promise = new Promise((resolve , reject) => {

conn.query('SELECT * from items ', (err , row) => {

if (!!err) resolve(row);

else reject(err);

});

});

promise.then(onSuccess );

promise.then(onSuccess , onError );

promise.then(onSuccess ). catch(onError );

6 / 13

Arrows – Attacking the Abstraction Differently

fv v ′

7 / 13

Arrows – Attacking the Abstraction Differently

fv v ′

Invoked Many Times

7 / 13

Arrows – Attacking the Abstraction Differently

fv v ′ g v ′′

7 / 13

Arrows – Attacking the Abstraction Differently

fv v ′ g v ′′

Synchronous

7 / 13

Arrows – Attacking the Abstraction Differently

fv v ′ g v ′′

Synchronous

delay

event

ajax

query

v

Elem

Conf

String

v

Event

Response

Response

Asynchronous

7 / 13

Arrows - Async Machine Composition

ajax

explode

nth(3)

handle

onclick onclick

nth(1) nth(2)

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

[Item] ; >(α, β, [Item]) ; (α, β, [Item])

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

(Str ,Num) ; >

(µ, ν, θ) ; µ \ {µ ≤ (Str ,Num)} and(µ, ν, θ) ; ν \ {ν ≤ (Str ,Num)}

(Str ,Num) ≤ µ ≤ (Str ,Num)(Str ,Num) ≤ ν ≤ (Str ,Num)> 6≤ (µ, ν, θ)

let page = Arrow.fix(a => Arrow.seq([

ajax ,

explode ,

Arrow.seq([

new NthArrow (3),

handle

]). remember(),

Arrow.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). onclick('#next'),

]),

a,

]));

let filter = Arrow.fix(a => Arrow.any([

Arrow.seq([

getQueryAndPage ,

page

]). noemit(),

a

]). onkeyup('#filter '));

filter.run ();

Composition Time

Execution Time

8 / 13

Arrows - Async Machine Composition

ajax

explode

nth(3)

handle

onclick onclick

nth(1) nth(2)

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

[Item] ; >(α, β, [Item]) ; (α, β, [Item])

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

(Str ,Num) ; >

(µ, ν, θ) ; µ \ {µ ≤ (Str ,Num)} and(µ, ν, θ) ; ν \ {ν ≤ (Str ,Num)}

(Str ,Num) ≤ µ ≤ (Str ,Num)(Str ,Num) ≤ ν ≤ (Str ,Num)> 6≤ (µ, ν, θ)

let page = Arrow.fix(a => Arrow.seq([

ajax ,

explode ,

Arrow.seq([

new NthArrow (3),

handle

]). remember(),

Arrow.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). onclick('#next'),

]),

a,

]));

let filter = Arrow.fix(a => Arrow.any([

Arrow.seq([

getQueryAndPage ,

page

]). noemit(),

a

]). onkeyup('#filter '));

filter.run ();

8 / 13

Arrows - Async Machine Composition

ajax

explode

nth(3)

handle

onclick onclick

nth(1) nth(2)

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

[Item] ; >(α, β, [Item]) ; (α, β, [Item])

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

(Str ,Num) ; >

(µ, ν, θ) ; µ \ {µ ≤ (Str ,Num)} and(µ, ν, θ) ; ν \ {ν ≤ (Str ,Num)}

(Str ,Num) ≤ µ ≤ (Str ,Num)(Str ,Num) ≤ ν ≤ (Str ,Num)> 6≤ (µ, ν, θ)

let page = Arrow.fix(a => Arrow.seq([

ajax ,

explode ,

Arrow.seq([

new NthArrow (3),

handle

]). remember(),

Arrow.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). onclick('#next'),

]),

a,

]));

let filter = Arrow.fix(a => Arrow.any([

Arrow.seq([

getQueryAndPage ,

page

]). noemit(),

a

]). onkeyup('#filter '));

filter.run ();

8 / 13

Arrows - Async Machine Composition

ajax

explode

nth(3)

handle

onclick onclick

nth(1) nth(2)

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

[Item] ; >(α, β, [Item]) ; (α, β, [Item])

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

(Str ,Num) ; >

(µ, ν, θ) ; µ \ {µ ≤ (Str ,Num)} and(µ, ν, θ) ; ν \ {ν ≤ (Str ,Num)}

(Str ,Num) ≤ µ ≤ (Str ,Num)(Str ,Num) ≤ ν ≤ (Str ,Num)> 6≤ (µ, ν, θ)

let page = Arrow.fix(a => Arrow.seq([

ajax ,

explode ,

Arrow.seq([

new NthArrow (3),

handle

]). remember(),

Arrow.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). onclick('#next'),

]),

a,

]));

let filter = Arrow.fix(a => Arrow.any([

Arrow.seq([

getQueryAndPage ,

page

]). noemit(),

a

]). onkeyup('#filter '));

filter.run ();

8 / 13

Arrows - Async Machine Composition

ajax

explode

nth(3)

handle

onclick onclick

nth(1) nth(2)

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

[Item] ; >(α, β, [Item]) ; (α, β, [Item])

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

(Str ,Num) ; >

(µ, ν, θ) ; µ \ {µ ≤ (Str ,Num)} and(µ, ν, θ) ; ν \ {ν ≤ (Str ,Num)}

(Str ,Num) ≤ µ ≤ (Str ,Num)(Str ,Num) ≤ ν ≤ (Str ,Num)> 6≤ (µ, ν, θ)

let page = Arrow.fix(a => Arrow.seq([

ajax ,

explode ,

Arrow.seq([

new NthArrow (3),

handle

]). remember(),

Arrow.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). onclick('#next'),

]),

a,

]));

let filter = Arrow.fix(a => Arrow.any([

Arrow.seq([

getQueryAndPage ,

page

]). noemit(),

a

]). onkeyup('#filter '));

filter.run ();

8 / 13

Arrows - Async Machine Composition

ajax

explode

nth(3)

handle

onclick onclick

nth(1) nth(2)

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

[Item] ; >(α, β, [Item]) ; (α, β, [Item])

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

(Str ,Num) ; >

(µ, ν, θ) ; µ \ {µ ≤ (Str ,Num)} and(µ, ν, θ) ; ν \ {ν ≤ (Str ,Num)}

(Str ,Num) ≤ µ ≤ (Str ,Num)(Str ,Num) ≤ ν ≤ (Str ,Num)> 6≤ (µ, ν, θ)

let page = Arrow.fix(a => Arrow.seq([

ajax ,

explode ,

Arrow.seq([

new NthArrow (3),

handle

]). remember(),

Arrow.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). onclick('#next'),

]),

a,

]));

let filter = Arrow.fix(a => Arrow.any([

Arrow.seq([

getQueryAndPage ,

page

]). noemit(),

a

]). onkeyup('#filter '));

filter.run ();

8 / 13

Arrows - Async Machine Composition

ajax

explode

nth(3)

handle

onclick onclick

nth(1) nth(2)

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

[Item] ; >(α, β, [Item]) ; (α, β, [Item])

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

(Str ,Num) ; >

(µ, ν, θ) ; µ \ {µ ≤ (Str ,Num)} and(µ, ν, θ) ; ν \ {ν ≤ (Str ,Num)}

(Str ,Num) ≤ µ ≤ (Str ,Num)(Str ,Num) ≤ ν ≤ (Str ,Num)> 6≤ (µ, ν, θ)

let page = Arrow.fix(a => Arrow.seq([

ajax ,

explode ,

Arrow.seq([

new NthArrow (3),

handle

]). remember(),

Arrow.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). onclick('#next'),

]),

a,

]));

let filter = Arrow.fix(a => Arrow.any([

Arrow.seq([

getQueryAndPage ,

page

]). noemit(),

a

]). onkeyup('#filter '));

filter.run ();

8 / 13

Composition-Time Inference

ajax

explode

nth(3)

handle

onclick onclick

nth(1) nth(2)

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

[Item] ; >

(α, β, [Item]) ; (α, β, [Item])

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

(Str ,Num) ; >

(µ, ν, θ) ; µ \ {µ ≤ (Str ,Num)} and(µ, ν, θ) ; ν \ {ν ≤ (Str ,Num)}

(Str ,Num) ≤ µ ≤ (Str ,Num)(Str ,Num) ≤ ν ≤ (Str ,Num)> 6≤ (µ, ν, θ)

let page = Arrow.fix(a => Arrow.seq([

ajax ,

explode ,

Arrow.seq([

new NthArrow (3),

handle

]). remember(),

Arrow.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). onclick('#next'),

]),

a,

]));

let filter = Arrow.fix(a => Arrow.any([

Arrow.seq([

getQueryAndPage ,

page

]). noemit(),

a

]). onkeyup('#filter '));

filter.run ();

8 / 13

Composition-Time Inference

ajax

explode

nth(3)

handle

onclick onclick

nth(1) nth(2)

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

[Item] ; >

(α, β, [Item]) ; (α, β, [Item])

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

(Str ,Num) ; >

(µ, ν, θ) ; µ \ {µ ≤ (Str ,Num)} and(µ, ν, θ) ; ν \ {ν ≤ (Str ,Num)}

(Str ,Num) ≤ µ ≤ (Str ,Num)(Str ,Num) ≤ ν ≤ (Str ,Num)> 6≤ (µ, ν, θ)

let page = Arrow.fix(a => Arrow.seq([

ajax ,

explode ,

Arrow.seq([

new NthArrow (3),

handle

]). remember(),

Arrow.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). onclick('#next'),

]),

a,

]));

let filter = Arrow.fix(a => Arrow.any([

Arrow.seq([

getQueryAndPage ,

page

]). noemit(),

a

]). onkeyup('#filter '));

filter.run ();

8 / 13

Composition-Time Inference

ajax

explode

nth(3)

handle

onclick onclick

nth(1) nth(2)

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

[Item] ; >(α, β, [Item]) ; (α, β, [Item])

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

(Str ,Num) ; >

(µ, ν, θ) ; µ \ {µ ≤ (Str ,Num)} and(µ, ν, θ) ; ν \ {ν ≤ (Str ,Num)}

(Str ,Num) ≤ µ ≤ (Str ,Num)(Str ,Num) ≤ ν ≤ (Str ,Num)> 6≤ (µ, ν, θ)

let page = Arrow.fix(a => Arrow.seq([

ajax ,

explode ,

Arrow.seq([

new NthArrow (3),

handle

]). remember(),

Arrow.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). onclick('#next'),

]),

a,

]));

let filter = Arrow.fix(a => Arrow.any([

Arrow.seq([

getQueryAndPage ,

page

]). noemit(),

a

]). onkeyup('#filter '));

filter.run ();

8 / 13

Composition-Time Inference

ajax

explode

nth(3)

handle

onclick onclick

nth(1) nth(2)

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

[Item] ; >(α, β, [Item]) ; (α, β, [Item])

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

(Str ,Num) ; >

(µ, ν, θ) ; µ \ {µ ≤ (Str ,Num)} and(µ, ν, θ) ; ν \ {ν ≤ (Str ,Num)}

(Str ,Num) ≤ µ ≤ (Str ,Num)(Str ,Num) ≤ ν ≤ (Str ,Num)

> 6≤ (µ, ν, θ)

let page = Arrow.fix(a => Arrow.seq([

ajax ,

explode ,

Arrow.seq([

new NthArrow (3),

handle

]). remember(),

Arrow.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). onclick('#next'),

]),

a,

]));

let filter = Arrow.fix(a => Arrow.any([

Arrow.seq([

getQueryAndPage ,

page

]). noemit(),

a

]). onkeyup('#filter '));

filter.run ();

8 / 13

Type Clash Detection

ajax

explode

nth(3)

handle

onclick onclick

nth(1) nth(2)

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

[Item] ; >(α, β, [Item]) ; (α, β, [Item])

(Str ,Num) ;((Str ,Num), (Str ,Num), [Item])

(Str ,Num) ; >

(µ, ν, θ) ; µ \ {µ ≤ (Str ,Num)} and(µ, ν, θ) ; ν \ {ν ≤ (Str ,Num)}

(Str ,Num) ≤ µ ≤ (Str ,Num)(Str ,Num) ≤ ν ≤ (Str ,Num)

> 6≤ (µ, ν, θ)

let page = Arrow.fix(a => Arrow.seq([

ajax ,

explode ,

Arrow.seq([

new NthArrow (3),

handle

]). remember(),

Arrow.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). onclick('#next'),

]),

a,

]));

let filter = Arrow.fix(a => Arrow.any([

Arrow.seq([

getQueryAndPage ,

page

]). noemit(),

a

]). onkeyup('#filter '));

filter.run ();

8 / 13

Adding Optimizations

9 / 13

Cached Ajax Responses

lookup

ajax store nth(2)

cache

const cachedAjax = Arrow.catch(lookup , Arrow.seq([

ajax.carry(),

store.remember(),

new NthArrow (2)

]));

function lookup(key) {

if (key in cache) return cache[key];

else throw key;

}

10 / 13

Cached Ajax Responses

lookup

ajax store nth(2)

cache

const cachedAjax = Arrow.catch(lookup , Arrow.seq([

ajax.carry(),

store.remember(),

new NthArrow (2)

]));

function lookup(key) {

if (key in cache) return cache[key];

else throw key;

}

10 / 13

Cached Ajax Responses

lookup

ajax store nth(2)

cache

const cachedAjax = Arrow.catch(lookup , Arrow.seq([

ajax.carry(),

store.remember(),

new NthArrow (2)

]));

function lookup(key) {

if (key in cache) return cache[key];

else throw key;

}

10 / 13

Pre-Fetching Next Page

handle

onclick

nth(1)

nth(2)

cachedAjax onclick

Arrows.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). seq(Arrow.fanout ([

ajaxOrCached ,

onclick('#next')]). noemit (). remember ())

]);

11 / 13

Pre-Fetching Next Page

handle

onclick

nth(1)

nth(2)

cachedAjax onclick

Arrows.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). seq(Arrow.fanout ([

ajaxOrCached ,

onclick('#next')]). noemit (). remember ())

]);

11 / 13

Pre-Fetching Next Page

handle

onclick

nth(1)

nth(2)

cachedAjax onclick

Arrows.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). seq(Arrow.fanout ([

ajaxOrCached ,

onclick('#next')]). noemit (). remember ())

]);

11 / 13

Pre-Fetching Next Page

handle

onclick

nth(1)

nth(2)

cachedAjax onclick

Arrows.any([

new NthArrow (1). onclick('#prev'),new NthArrow (2). seq(Arrow.fanout ([

ajaxOrCached ,

onclick('#next')]). noemit (). remember ())

]);

11 / 13

No In-Flight Requests as User Types

#filter

keyup

Q and P

wait

page

let filter = Arrow.fix(a => Arrow.any([

a,

Arrow.seq([

getQueryAndPage ,

new DelayArrow (400),

page

]). noemit(),

]). onkeyup('#filter '));

12 / 13

No In-Flight Requests as User Types

#filter

keyup

Q and P wait page

let filter = Arrow.fix(a => Arrow.any([

a,

Arrow.seq([

getQueryAndPage ,

new DelayArrow (400),

page

]). noemit(),

]). onkeyup('#filter '));

12 / 13

No In-Flight Requests as User Types

#filter

keyup

Q and P wait page

#filter

keyup

ad infinitum

let filter = Arrow.fix(a => Arrow.any([

a,

Arrow.seq([

getQueryAndPage ,

new DelayArrow (400),

page

]). noemit(),

]). onkeyup('#filter '));

12 / 13

Conclusion

Composing an interaction machine is a powerful shift in abstractionOpens up opportunity for ‘static’ benefits

Examples, papers, and code available athttp://arrows.eric-fritz.com

Questions?

13 / 13

Conclusion

Composing an interaction machine is a powerful shift in abstractionOpens up opportunity for ‘static’ benefits

Examples, papers, and code available athttp://arrows.eric-fritz.com

Questions?

13 / 13

Conclusion

Composing an interaction machine is a powerful shift in abstractionOpens up opportunity for ‘static’ benefits

Examples, papers, and code available athttp://arrows.eric-fritz.com

Questions?

13 / 13