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