Programowanie funkcyjne w JS
Mateusz Pokora
Mateusz
Pokora
Software engineer od 6 lat.
Aktualnie pracuje w firmie HTDevelopers.
Programowanie funkcyjne
- paradygmat programowania
- zbiór zasad i reguł które ograniczają nas dając jednocześnie inne korzyści
- programowanie deklaratywne
- oparte na matematycznych podstawach
Javascript
Javascript jest językiem wielodogmatowym, możemy programować w nim zarówno funkcyjnie jak i obiektowo.
Statement vs Expression
// Statement
if (/* warunek */) {
return getUser();
} else {
return null;
}
// Expression
const user = getUser()
Programowanie deklaratywne vs programowanie proceduralne
Opisywanie rzeczy w sposób "czym są" - funkcje matematyczne
W przeciwieństwie do programowania imperatywnego - wypisywanie instrukcji krok po kroku.
Programowanie funkcyjne
- początki w latach 60-70
- Mała popularność ze względu na wymagania obliczeniowe.
- Popularyzacja w ostatnim czasie przez rozwój procesorów wielowątkowych.
Programowanie funkcyjne
- Rozdzielenie na dane i funkcje operujące na danych
- Immutable data
- Currying / Partial application
- Kompozycja funkcji
- Izolacja side effect
Programowanie funkcyjne
- Bardziej czytelny kod
- Bardziej reużywalny kod
- Łatwiejsze testowanie
Function - first class citizen
- Przypisanie do zmiennej
- Przekazanie jako argumenty funkcji
- Zwrócenie funkcji jako wynik innej funkcji
Immutable data
- Nie możemy zmienić raz zadeklarowanych struktur (obiekty, listy)
- Nie możemy zmienić przypisania zmiennej
- Tworzymy nowe obiekty ze zmodyfikowanymi polami
Immutable data
class Car {
constructor(params) {
this.speed = params.speed;
this.name = params.name;
this.color = params.color;
}
accelerate = () => {
this.speed = this.speed + 10;
}
break = () => {
this.speed = 0;
}
}
1 const myCar = new Car({ name: "Toyota", color: "black", speed: 50})
...
50 myCar.accelerate() // what's the current speed??
Aby znać wartość "speed" w 50 linijce musimy prześledzić poprzednie 49
Immutable data
// Data container class
class Car {
constructor(params) {
this.speed = params.speed;
this.name = params.name;
this.color = params.color;
}
}
// Operations on data
class CarActions {
accelerate = (car) => {
return new Car({..., speed: car.speed + 10})
}
}
1 const myCar = new Car({ name: "Toyota", color: "black", speed: 50})
...
50 CarActions.accelerate(myCar) // new instance of object with updated fields
Immutable data
// Mutation
const toyota = { ... , name: 'Toyota' };
toyota.name = "Maserati"
// No mutation
const myCar = { ... , name: 'Toyota' };
const maserati = {...toyota, name: "Maserati" };
// Mutation
const carList = [{ ... , name: 'Toyota' }, { ... , name: 'Opel' }];
carList.push({... , name: 'Maserati' });
// No mutation
const carList = [{ ... , name: 'Toyota' }, { ... , name: 'Opel' }];
const updatedList = [...carList, {... , name: 'Maserati' }]
Immutable data
const arr1 = [{a: 10, b: 5}];
console.log(arr1) => [{a: 10, b: 5}]
const arr2 = arr1.slice();
console.log(arr2) => [{a: 10, b: 5}]
arr2[0].b = 15;
console.log(arr2) => [{a: 10, b: 15}]
console.log(arr1) => [{a: 10, b: 15}]
JS arrays and object are shallow copied by default!!!!!
Pure functions
- Otrzymując te same argumenty zawsze zwracamy ten sam wynik
# pure
const add = (a, b) => (a + b)
# not pure
const fetchCar(carId) => axios.get(...)
Pure functions
- React function components
const Button = ({label, onClick}) => (
<button onClick={onClick}>{label}</button>
)
Pure functions
Brak side effectów:
- Zapis do bazy danych
- Zapisanie obrazka w S3
- Wysłanie zapytania HTTP
Pure functions
http://github.com/pokorson/nodeschool-fp-js
declarative vs imperative
Array.map
Array.filter
Array.reduce
Array.flatMap
Kompozycja funkcji
Kompozycja funkcji
https://www.tibco.com/blog/wp-content/uploads/2015/05/lego.jpg
Kompozycja funkcji
Currying
- Zamiana funkcji przyjmującej x argumentów na funkcje przyjmujące po jednym argumencie aplikowanie argumentów do funkcji
add = (x, y) => ( x + y )
add(5, 10) => 15
-------------------------
# Currying
add = (x, y) => ( x + y )
createrAdder = curry(add)
add5 = createAdder(5)
add5(10) => 15
Kompozycja funkcji
- Łączenie funkcji przekazując wynik wykonania jednej jako argument kolejnej
- Skomplikowane rzeczy tworzymy dzieląc je na mniejsze problemy
const add5 = (x) => ( x + 5 )
const multiplyBy10 = (x) => ( x * 10 )
multiplyBy10( add5(3) ) === compose(multiplyBy10, add5)(3)
Kompozycja funkcji
multiplyBy10(
add5(
subtract2(
divideBy5(20)
)
)
)
vs
compose(
multiplyBy10,
add5,
subtract2,
divideBy5
)(3)
Kompozycja funkcji
- Brak funkcji compose w JS, jest ona jednak łatwa do zaimplementowania samemu ( 1 linijka). Dostępna również w wielu bibliotekach (min Redux)
const compose = (...fns) => {
return (args) => fns.reduceRight((result, currentFn) => currentFn(result), args)
}
Kompozycja funkcji
const getLastPremiumUserName = compose(
prop('fullName'),
map(user => {...user, fullName: `${user.firstName} ${user.lastName}` }),
last,
sortByRegistrationDate,
filter(user => user.isPremium)
)
Zbiory funkcji pozwalające na większą kompozycje logiki (ifElse, bezpieczne odczytywanie atrybutów w obiektach, map na tablicach i obiektach)
- Ramda - https://ramdajs.com/
- Lodash/fp - https://github.com/lodash/lodash/wiki/FP-Guide
Ramda
const R = require('ramda')
const getLastPremiumUserName = R.compose(
R.prop('fullName'),
R.map(user => {...user, fullName: `${user.firstName} ${user.lastName}` }),
R.last,
R.sortByRegistrationDate,
R.filter(R.prop('isPremium'))
)
npm install --save ramda
https://ramdajs.com/
Wady FP
- Początkowo większa czasochłonność w wykonywaniu zadań
- Wydajność (jeśli język nie jest funkcyjny)
- Duża alokacja pamięci
- Zbyt restrykcyjne trzymanie się zasad może w efekcie skomplikować kod
- Odrzucenie reguł powszechnie przyjętych przez community może utrudnić wejście w projekt nowym osobom
W pełni funkcyjne środowiska
- Elm
- Reason
- Purescript, ClojureScript, ScalaJs
Dobre materiały
- Eric Elliot
- Dr Boolean
- Fun fun function (yt channel)
- https://github.com/pokorson/fp-materials
- https://www.reactiflux.com/ (#functional-programming)
Dziękuję za uwagę
Nodeschool 12.01
By vrael560
Nodeschool 12.01
- 749