66
ZNAKI MOCY DLA LAIKÓW Wiktor Toporek

UszanowankoProgramowanko 5 - Znaki Mocy Dla Laikow - Programowanie Funkcyjne w Javascript

Embed Size (px)

Citation preview

ZNAKI MOCYDLA LAIKÓW

Wiktor Toporek

O MNIEBlog: Na co dzień PHP i JSInspirują mnie inne języki programowania i podejścia

http://wiktortoporek.name/blog

Jak wyobrażam sobie nasze codzienne programowanie?

Jak wyobrażam sobie programowanie funkcyjne?

PURE FUNCTIONSfunction f(x) { return x * 2;}

IMMUTABILITYfunction f(x) { x[0] = 1;}✗

Źródło: http://thecodinglove.com/post/120526844813

FIRST CLASS CITIZENFUNCTIONS

function f(x) { ...}

var g = f;

(λ)FUNKCJE WYŻSZYCH RZĘDÓW

FOREACH

var doubled = [];

[1, 2, 3].forEach( function(v) { doubled.push(v * 2); });

FOREACH

MAP[1, 2, 3].map( function(v) { return v * 2; }); //-> [2, 4, 6]

[1,2,3] → [2,4,6]

FILTER[5, 6, 2, 1].filter( function(v) { return v % 2 == 0; }); //-> [6, 2]

[5,6,2,1] → [6,2]

REDUCE

var grades = [5, 5, 4, 3, 4, 2];var sum = 0;for (var i in grades) { sum += grades[i];}var avg = sum / grades.length;

var grades = [5, 5, 4, 3, 4, 2];var sum = grades.reduce( function(currentSum, currentGrade) { return currentSum + currentGrade; });var avg = sum / grades.length;

[5,5,4,3,4,2] → 23

[1, 2, 3]

[keyDown, keyDown, keyDown, ...

RxJS

github.com/Reactive-Extensions/RxJS

REAGOWANIE NA KLAWISZ ENTERvar keydown = Rx.Observable.fromEvent(document, 'keydown');keydown.forEach( function(event) { if (event.which === 13) { // do something } });

Elastyczniej:var keydown = Rx.Observable.fromEvent(document, 'keydown');var enterPresses = keydown.filter( function(event) { return event.which === 13; });enterPresses.forEach(function() {/* do something */});

RXMARBLES.COM

DRAG & DROP

M↓ M↓ M⇝ M⇝ M⇝ M⇝ M⇝ M⇝ M⇝ M↑ M↑var mousedown = Rx.Observable.fromEvent(dragTarget, 'mousedown');var mousemove = Rx.Observable.fromEvent(document, 'mousemove');var mouseup = Rx.Observable.fromEvent(dragTarget, 'mouseup' );

var mousedrag = mousedown.flatMap( function (md) { // calculate offsets when mouse down var startX = md.offsetX, startY = md.offsetY;

// Calculate delta with mousemove until mouseup return mousemove.map(function (mm) { mm.preventDefault();

return { left: mm.clientX - startX, top: mm.clientY - startY }; }).takeUntil(mouseup); });

mousedrag.forEach(function(newPosition) { // change element position});

(())KOMPOZYCJA

Przykład startowyvar name = "Doge"console.log("Hello " + name);

Trochę elastyczniej...

...ale nie do końca

var name = "Doge"function printIt(name) { console.log("Hello " + name);}

printIt(name);

printIt("Bye Doge"); // Hello Bye Doge

Osobna funkcja do witania:function greeting(name) { return "Hello " + name;}

Użycie:var name = "Doge";printIt(greeting("Doge")); // Hello Doge

POŁĄCZMY DWIE FUNKCJE W JEDNĄKlasyczną drogą:

var name = "Doge";function greet(name) { printIt(greeting(name));}greet(name);

Drogą FP:var name = "Doge";var greet = compose(printIt, greeting);greet(name);

CO ROBI COMPOSE?

A co z parametrem innego typu?var user = { id: 123, name: "Doge"};

To:greet(user); // Hello [object Object]

Dopiszmy kolejną funkcję:function printIt(name) { console.log(name);}function greeting(name) { return "Hello " + name;}function getName(user) { return user.name;}

Skomponujmy nową ze wszystkich trzech:var greetUser = compose(printIt, greeting, getName);var user = { id: 123, name: "Doge"};greetUser(user);

Nie musimy ograniczać się do własnych funkcji:function getFullname(person) { return [person.firstName, person.lastName].join(' ');}

var getFullnameFromJson = compose(getFullname, JSON.parse);

()()()CURRYING

Przykład startowy

Użycie:

function sendMessage(from, to, message) { console.log([from, ' -> ', to, ': ', message].join(""));}

sendMessage('John', 'Alice', 'Hello!'); //John -> Alice: Hello!

Jako John:sendMessage('John', 'Alice', 'Hello!');sendMessage('John', 'Alice', 'How are you?');sendMessage('John', 'Bob', 'Hello!');

Ułatwmy Johnowi:function sendMessageFromJohn(to, message){ sendMessage('John', to, message);}

sendMessageFromMe('Alice', 'Hello!');sendMessageFromMe('Alice', 'How are you?');sendMessageFromMe('Bob', 'Hello!');

Ręczny Curryingfunction sendMessage(from) { return function(to) { return function(message) { console.log([from, ' -> ', to, ': ', message].join("")); } }}

Użycie:var sendMessageFromMe = sendMessage(currentUser);sendMessageFromMe('Alice')('Hey');

Currying z Lodashvar sendMessage = _.curry( function(from, to, message) { console.log([from, ' -> ', to, ': ', message].join("")); });

Użycie:var sendMessageFromMe = sendMessage(currentUser);sendMessageFromMe('Alice', 'Hey');

v←λMONADA

Źródło: https://en.wikipedia.org/wiki/Monad_(category_theory)

Źródło: http://thecodinglove.com/post/119589628797/

Źródło: http://pl.wikipedia.org/wiki/Monada_(programowanie)

Źródło: https://twitter.com/jlouis666/status/569465010759573505

[TASK].[ASSIGNED_COMPANY_LOGO_URL]

[TASK] → [ASIGNEE] → [COMPANY] → [LOGO] → [URL]

function getTaskCompanyLogoUrl(task){ return task.asignee.company.logo.url;}

Problem:var task = { 'asignee': null};

getTaskCompanyLogoUrl(task);

Źródło: http://www.practical-programming.org/articles/love_null/love_null.html

Klasycznie:function getTaskCompanyLogo(task){ if (task.asignee !== null) { if (task.asignee.company !== null) { if (task.asignee.company.logo !== null) { return task.asignee.company.logo.url; } } }

return null;}

task → getAsignee → getCompany → getLogo → getUrl

task → stop if null → getAsignee → stop if null → ... → getUrl

MAYBE

MONADOWY PLAN:Value - Bieżemy wartość[ Value ] - Opakowujemy ją[ Value ].map(f).map(f)... - Dowiązujemy pewne operacje[ Value ].getValue() - Odpakowujemy nową wartość

Przykład z pomocą Monet.js*:function getTaskCompanyLogo(task){ var maybeCompanyLogoUrl = Maybe.of(task) .map(getAsignee) .map(getCompany) .map(getLogo) .map(getUrl);

return maybeCompanyLogoUrl.val;}

* fork vViktorPL/monet.js z drobną zmianą na potrzeby prezentacji

Gettery:function getCompany(object) { return object.company;}function getLogo(object) { return object.logo;}function getUrl(object) { return object.url;}

ES6:function getTaskCompanyLogo(task){ var maybeCompanyLogoUrl = Maybe.of(task) .map(task => task.asignee) .map(asignee => asignee.company) .map(company => company.logo) .map(logo => logo.url);

return maybeCompanyLogoUrl.val;}

JAK TO DZIAŁA?Maybe = Some | None

Some.map(f) => { var result = f(this.val); if (result !== null) { return Some(result); } else { return None; }}

None.map(f) => None

PROMISE

Angularowe $q

Źródło: https://docs.angularjs.org/api/ng/service/$q

function asyncGreet(name) { // perform some asynchronous operation, resolve or reject the promise when appropriate. return $q(function(resolve, reject) { setTimeout(function() { if (okToGreet(name)) { resolve('Hello, ' + name + '!'); } else { reject('Greeting ' + name + ' is not allowed.'); } }, 1000); });}

function asyncGreet(name) { ...}

var promise = asyncGreet('Robin Hood');promise.then(function(greeting) { alert('Success: ' + greeting);}, function(reason) { alert('Failed: ' + reason);});

var promise = asyncGreet(name); - Opakowanie wartościpromise.then() - .map()?greeting - wyłuskana nowa wartość

PODSUMOWANIEFunkcje wyższych rzędów takie jak map czy filterułatwiają przetworzenie serii danychKompozycja umożliwia w łatwy sposób składanie kilkufunkcji w jednąCurrying może przydać się do funkcji które przyjmująwiele argumentówMonada to warstwa abstrakcji która sama decyduje w jakisposób wykona przekazane jej operacje

MOJE WNIOSKIWzorce z FP da się odnaleźć w OOFP rzuca inne światło na niektóre problemyProgramowanie czysto funkcyjne jest szaleństwem dlaaplikacji z życia wziętych (potrzebujemy stanu prędzej czypóźniej)FP jest źle "sprzedawane"FP można łączyć z OO

DZIĘKUJE ZA UWAGĘPYTANIA?

KOMENTARZE?