Programowanie funkcyjne w JS
Mateusz Pokora
Programowanie funkcyjne
- Rozdzielenie na dane i funkcje operujące na danych
- Immutable data
- Izolacja side effect
- Currying / Partial application
- Kompozycja funkcji
Programowanie funkcyjne
- Bardziej czytelny kod
- Bardziej reużywalny kod
- Łatwiejsze testowanie
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!!!!!
Immutable data
- React local state
- Redux store
Immutable data - Libs
- Immutable.js - https://facebook.github.io/immutable-js/
- Immer - https://github.com/mweststrate/immer
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>
)
Izolacja side effect
Przykładowe side effecty
- Zapis do bazy danych
- Zapisanie obrazka w S3
- Wysłanie zapytania HTTP
Izolacja side effect
Problemy z side effectami
- Ciężkie do testowania, wymagają dużej ilości mocków
- Utrudniają kompozycję funkcji
Izolacja side effect
Brak mechanizmów w samym JS
Mamy dostęp do zbioru bibliotek które pomagają w tym ale o tym innym razem :)
Kompozycja funkcji
Function - first class citizen
- Przypisanie do zmiennej
- Przekazanie jako argumenty funkcji
- Zwrócenie funkcji jako wynik innej 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
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ę
Programowanie funkcyjne w JS
By vrael560
Programowanie funkcyjne w JS
- 459