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ę

Made with Slides.com