Conquering Time with FRP A practical introduction to Functional - - PowerPoint PPT Presentation

conquering time with frp
SMART_READER_LITE
LIVE PREVIEW

Conquering Time with FRP A practical introduction to Functional - - PowerPoint PPT Presentation

Conquering Time with FRP A practical introduction to Functional Reactive Programming by sergi mansilla | @sergimansilla @sergimansilla http:/ /github.com/sergi Alumni Shameless linkbaiting Conquering Time with FRP A practical introduction


slide-1
SLIDE 1

Conquering Time with FRP

A practical introduction to Functional Reactive Programming

by sergi mansilla | @sergimansilla

slide-2
SLIDE 2

@sergimansilla

slide-3
SLIDE 3

http:/ /github.com/sergi

slide-4
SLIDE 4

Alumni

slide-5
SLIDE 5
slide-6
SLIDE 6

Shameless linkbaiting

slide-7
SLIDE 7

Conquering Time with FRP

A practical introduction to Functional Reactive Programming

by sergi mansilla | @sergimansilla

slide-8
SLIDE 8

Tame your async code with this one weird trick!

A practical introduction to Functional Reactive Programming

by sergi mansilla | @sergimansilla

slide-9
SLIDE 9
  • Linkbaiting
  • Time
slide-10
SLIDE 10

Human beings have hard-wired time in their brain

slide-11
SLIDE 11

JS developers have hard-wired async in their brain

slide-12
SLIDE 12

Callbacks Promises Generators Events

slide-13
SLIDE 13

We use events to deal with asynchronous tasks

slide-14
SLIDE 14

var clicks = 0; document.addEventListener('click', function register(e) { if (clicks < 10) { if (e.clientX > innerWidth / 2) { console.log(e.clientX, e.clientY); clicks += 1; } } else { document.removeEventListener('click', register); } });

slide-15
SLIDE 15

Why are we still micromanaging code?

slide-16
SLIDE 16

var clicks = 0; document.addEventListener('click', function register(e) { if (clicks < 10) { if (e.clientX > innerWidth / 2) { console.log(e.clientX, e.clientY); clicks += 1; } } else { document.removeEventListener('click', register); } });

slide-17
SLIDE 17

var clicks = 0; document.addEventListener('click', function register(e) { if (clicks < 10) { if (e.clientX > innerWidth / 2) { console.log(e.clientX, e.clientY); clicks += 1; } } else { document.removeEventListener('click', register); } });

slide-18
SLIDE 18

var clicks = 0; document.addEventListener('click', function register(e) { if (clicks < 10) { if (e.clientX > innerWidth / 2) { console.log(e.clientX, e.clientY); clicks += 1; } } else { document.removeEventListener('click', register); } });

slide-19
SLIDE 19

var clicks = 0; document.addEventListener('click', function register(e) { if (clicks < 10) { if (e.clientX > innerWidth / 2) { console.log(e.clientX, e.clientY); clicks += 1; } } else { document.removeEventListener('click', register); } });

slide-20
SLIDE 20

var clicks = 0; document.addEventListener('click', function register(e) { if (clicks < 10) { if (e.clientX > innerWidth / 2 && isAPressed) { console.log(e.clientX, e.clientY); clicks += 1; } } else { document.removeEventListener('click', register); } });

  • var isAPressed = false;

document.addEventListener('keydown', e => { isAPressed = e.keyCode === 65; }, false);

  • document.addEventListener('keyup', e => {

isAPressed = false; }, false);

slide-21
SLIDE 21

We still code the how instead of the what

slide-22
SLIDE 22

Programming should be more about the what

slide-23
SLIDE 23
slide-24
SLIDE 24

State is dangerous

slide-25
SLIDE 25

var clicks = 0; document.addEventListener('click', function register(e) { if (clicks < 10) { if (e.clientX > innerWidth / 2 && isAPressed) { console.log(e.clientX, e.clientY); clicks += 1; } } else { document.removeEventListener('click', register); } });

  • var isAPressed = false;

document.addEventListener('keydown', e => { isAPressed = e.keyCode === 65; }, false);

  • document.addEventListener('keyup', e => {

isAPressed = false; }, false);

slide-26
SLIDE 26

Event limbo

slide-27
SLIDE 27

var clicks = 0; document.addEventListener('click', function register(e) { if (clicks < 10) { if (e.clientX > innerWidth / 2) { console.log(e.clientX, e.clientY); clicks += 1; } } else { document.removeEventListener('click', register); } });

slide-28
SLIDE 28

Isn’t that the problem promises try to solve?

slide-29
SLIDE 29

var y = f(x); var z = g(y); fAsync(x).then(...); gAsync(x).then(...); res = stocks .filter(q => q.symbol == 'FB') .map(q => q.quote)

  • res.forEach(x =>

... res = stocks //async retrieval .filter(q => q.symbol == 'FB') .map(q => q.quote)

  • res.subscribe(x =>

...

Sync Sync Promises RxJS

slide-30
SLIDE 30

Click!

… …

Click! Click!

slide-31
SLIDE 31

Click!

, ,

Click! Click!

[ ]

slide-32
SLIDE 32
slide-33
SLIDE 33

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(n => n % 2) .map(n => 'item ' + n) .forEach(n => console.log(n))

  • // "item 1"

// "item 3" // "item 5" // "item 7" // "item 9"

slide-34
SLIDE 34

F R P

slide-35
SLIDE 35

Final Resting Place

slide-36
SLIDE 36

Fantasy Role Playing

slide-37
SLIDE 37

Functional Reactive Programming

slide-38
SLIDE 38
slide-39
SLIDE 39
slide-40
SLIDE 40

Deal with values that change

  • ver time
slide-41
SLIDE 41
slide-42
SLIDE 42

RxJS helps us compose asynchronous and event-based programs

slide-43
SLIDE 43

var clicks = 0; document.addEventListener('click', function register(e) { if (clicks < 10) { if (e.clientX > innerWidth / 2) { console.log(e.clientX, e.clientY); clicks += 1; } } else { document.removeEventListener('click', register); } });

Filter Limit to 10 Print the coordinates

slide-44
SLIDE 44

fromEvent(document, 'click') .filter(c => c.clientX > innerWidth / 2 }) .take(10) .subscribe(c => console.log(c.clientX, c.clientY) })

slide-45
SLIDE 45

fromEvent(document, 'click') .filter(c => c.clientX > innerWidth / 2 }) .take(10) .subscribe(c => console.log(c.clientX, c.clientY) })

Create Observable

slide-46
SLIDE 46

fromEvent(document, 'click') .filter(c => c.clientX > innerWidth / 2 }) .take(10) .subscribe(c => console.log(c.clientX, c.clientY) })

Create filtered Observable
 from the first one

slide-47
SLIDE 47

fromEvent(document, 'click') .filter(c => c.clientX > innerWidth / 2 }) .take(10) .subscribe(c => console.log(c.clientX, c.clientY) })

Create final Observable
 taking only first 10 results

slide-48
SLIDE 48

fromEvent(document, 'click') .filter(c => c.clientX > innerWidth / 2 }) .take(10) .subscribe(c => console.log(c.clientX, c.clientY) })

Actually kick off computation

slide-49
SLIDE 49

Rx.Observable Rx.Observer

slide-50
SLIDE 50

Rx.Observer

  • OnNext()
  • OnError()
  • OnComplete()
slide-51
SLIDE 51

Observable Observer pattern Iterator pattern

slide-52
SLIDE 52

// Creates an observable sequence of 5 integers var source = Rx.Observable.range(1, 5)

  • // Prints out each item

var subscription = source.subscribe( x => { console.log('onNext: ' + x) }, e => { console.log('onError: ' + e.message) }, () => { console.log('onCompleted') })

  • // => onNext: 1

// => onNext: 2 // => onNext: 3 // => onNext: 4 // => onNext: 5 // => onCompleted

slide-53
SLIDE 53

var mousemove = fromEvent(document, 'mousemove');

  • var mouseCoords = mousemove.map(e => ({

left: e.clientX, top: e.clientY }))

  • var mouseSide = mousemove.map(e =>

(e.clientX > window.innerWidth / 2 ? 'right' : 'left'))

  • mouseCoords.subscribe(pos =>

coords.innerHTML = pos.top + 'px ' + pos.left + 'px') mouseSide.subscribe(s => side.innerHTML = s);

slide-54
SLIDE 54

// Search Wikipedia for a given term function searchWikipedia(term) { var cleanTerm = global.encodeURIComponent(term); var url = ‘http://en.wikipedia.org/w/api.php...' + cleanTerm + '&callback=JSONPCallback'; return Rx.Observable.getJSONPRequest(url); }

  • var input = document.querySelector('#searchtext'),

results = document.querySelector('#results');

  • // Get all distinct key up events from the input and

var keyup = fromEvent(input, 'keyup') .map(e => e.target.value) .where(text => text.length > 2) // Longer than 2 chars .throttle(200) // Pause for 200ms .distinctUntilChanged(); // Only if the value has changed

slide-55
SLIDE 55

var searcher = keyup .map(text => searchWikipedia(text)) // Search wikipedia .switchLatest() // Ensure no out of order results .where(data => (data.length === 2)); // Where we have data

  • searcher.subscribe(data => {

// Append the results (data[1]) }, error => { // Handle any errors });

slide-56
SLIDE 56
slide-57
SLIDE 57

// Search Wikipedia for a given term function searchWikipedia(term) { var cleanTerm = global.encodeURIComponent(term); var url = 'http://en.wikipedia.org/w/api.php? action=opensearch&format=json&search=' + cleanTerm + '&callback=JSONPCallback'; return Rx.Observable.getJSONPRequest(url); }

  • var input = document.querySelector('#searchtext'),

results = document.querySelector('#results');

  • // Get all distinct key up events from the input and

var keyup = fromEvent(input, 'keyup') .map(e => e.target.value) .where(text => text.length > 2) // Longer than 2 chars .throttle(200) // Pause for 200ms .distinctUntilChanged(); // Only if the value has changed

slide-58
SLIDE 58

// Search Wikipedia for a given term function searchWikipedia(term) { return fromArray(['JavaScript', 'JavaServer Pages', 'JavaSoft', 'JavaScript library', 'JavaScript Object Notation', 'JavaScript engine', 'JavaScriptCore']); }

  • var input = document.querySelector('#searchtext'),

results = document.querySelector('#results');

  • // Get all distinct key up events from the input and

var keyup = fromEvent(input, 'keyup') .map(e => e.target.value) .where(text => text.length > 2) // Longer than 2 chars .throttle(200) // Pause for 200ms .distinctUntilChanged(); // Only if the value has changed

slide-59
SLIDE 59

fromArray fromCallback fromEvent fromEventPattern fromIterable fromNodeCallback fromPromise

slide-60
SLIDE 60

var mouseup = fromEvent(dragTarget, 'mouseup'); var mousemove = fromEvent(document, 'mousemove'); var mousedown = fromEvent(dragTarget, 'mousedown');

  • var mousedrag = mousedown.selectMany(md => {

var startX = md.clientX + window.scrollX, startY = md.clientY + window.scrollY, startLeft = parseInt(md.target.style.left, 10) || 0, startTop = parseInt(md.target.style.top, 10) || 0;

  • // Calculate delta with mousemove until mouseup

return mousemove.map(mm => { mm.preventDefault();

  • return {

left: startLeft + mm.clientX - startX, top: startTop + mm.clientY - startY }; }).takeUntil(mouseup); });

  • subscription = mousedrag.subscribe(pos => {

dragTarget.style.top = pos.top + 'px'; dragTarget.style.left = pos.left + 'px'; });

slide-61
SLIDE 61

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(n => n % 2) .map(n => 'item ' + n) .forEach(n => console.log(n))

  • // "item 1"

// "item 3" // "item 5" // "item 7" // "item 9"

slide-62
SLIDE 62

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(n => n % 2) .map(n => 'item ' + n) .forEach(n => console.log(n))

  • // "item 1"

// "item 3" // "item 5" // "item 7" // "item 9"

loop loop loop

slide-63
SLIDE 63
slide-64
SLIDE 64

fromArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) .filter(n => n % 2) .map(n => n * 100) .map(n => 'item ' + n) .subscribe(n => console.log(n))

slide-65
SLIDE 65

First Class events Composability Encapsulation

slide-66
SLIDE 66
slide-67
SLIDE 67

Thanks! @sergimansilla

slide-68
SLIDE 68