ZNAKI MOCY
DLA LAIKÓW
Wiktor Toporek
O MNIE
Blog:
Na co dzień PHP i JS
Inspirują 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 FUNCTIONS
function f(x) {
return x * 2;
}
IMMUTABILITY
function f(x) {
x[0] = 1;
}
✗
Źródło: http://thecodinglove.com/post/120526844813
FIRST CLASS CITIZEN
FUNCTIONS
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 ENTER
var 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 startowy
var 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 Currying
function 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 Lodash
var 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
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ści
promise.then() - .map()?
greeting - wyłuskana nowa wartość
PODSUMOWANIE
Funkcje wyższych rzędów takie jak map czy filter
ułatwiają przetworzenie serii danych
Kompozycja umożliwia w łatwy sposób składanie kilku
funkcji w jedną
Currying może przydać się do funkcji które przyjmują
wiele argumentów
Monada to warstwa abstrakcji która sama decyduje w jaki
sposób wykona przekazane jej operacje
MOJE WNIOSKI
Wzorce z FP da się odnaleźć w OO
FP rzuca inne światło na niektóre problemy
Programowanie czysto funkcyjne jest szaleństwem dla
aplikacji z życia wziętych (potrzebujemy stanu prędzej czy
później)
FP jest źle "sprzedawane"
FP można łączyć z OO
DZIĘKUJE ZA UWAGĘ
PYTANIA?
KOMENTARZE?

Znaki mocy dla laików – Programowanie funkcyjne w JavaScript