Skip to content

Latest commit

 

History

History
334 lines (252 loc) · 8.96 KB

functional-programming.md

File metadata and controls

334 lines (252 loc) · 8.96 KB

Map

map() je metoda nad Array objektom koja izvršava zadanu transformaciju nad svakim elemetom u array-u i vraća novi array sa transformiranim elementima.

Primjer

Konvertiranje svih stringova u array-u u uppercase:

const pets = ['dog', 'snake', 'cat', 'hamster'];
const loudPets = pets.map(pet => pet.toUpperCase());
console.log(loudPets);

(Link na CodePen)

Za usporedbu, ista operacija koristeći for petlju:

const pets = ['dog', 'snake', 'cat', 'hamster'];
const loudPets = [];
for (let i = 0; i < pets.length; i++) {
  loudPets.push(pets[i].toUpperCase());
}
console.log(loudPets);

(Link na CodePen)

Prednosti:

  • kraća sintaksa
  • manje logike (za iteriranje po array-u se interno brine map() metoda)
  • jasniji kôd
  • ne modificira originalni array
  • dopušta method chaining sa drugim array metodama

Filter

filter() je metoda nad Array objektom koja vraća novi array sastavljen samo od elemenata originalnog array-a koji zadovoljavaju test koji implementira zadana funkcija.

Primjer

Želimo filtrirati samo mačke iz polja kućnih ljubimaca:

const pets = [
  { name: 'Doug', type: 'dog' },
  { name: 'Keanu', type: 'cat' },
  { name: 'Ghost', type: 'direwolf' },
  { name: 'Stevens', type: 'cat' },
];
const cats = pets.filter(pet => pet.type === 'cat');
console.log(cats);

(Link na CodePen)

Implementacija iste funkcionalnosti koristeći for petlju:

const pets = [
  { name: 'Doug', type: 'dog' },
  { name: 'Keanu', type: 'cat' },
  { name: 'Ghost', type: 'direwolf' },
  { name: 'Stevens', type: 'cat' },
];
const cats = [];
for (let i = 0; i < pets.length; i++) {
  if (pets[i].type === 'cat') {
    cats.push(pets[i]);
  }
}
console.log(cats);

(Link na CodePen)

Novi array ima samo one elemente za koje test funkcija vraća true.

Prednosti: iste kao i za map() metodu.

Reduce

Swiss army knife array funkcija. Izvršava tzv. reducer funkciju nad svakim elementom zadanog array-a i vraća rezultat.

Ulazni parametri

Reducer - funkcija koja prima do četiri vrijednosti, redom:

  1. Accumulator
  2. Trenutnu vrijednost
  3. Trenutni index
  4. Izvorni array

Vrijednost koju reducer vraća postaje novi Accumulator koji se prosljeđuje u reducer u idućoj iteraciji. Vrijednost accumulatora koju reducer vrati u zadnjoj iteraciji postaje povratna vrijednost reduce() metode.

Početna vrijednost accumulatora - vrijednost koja se šalje kao accumulator u reducer funkciju u prvoj iteraciji. Ako nije zadana, koristi se prvi element array-a.

Primjer 1. Zbroj svih brojeva u array-u

const numbers = [1, 2, 3, 4, 5];
const sumOfNumbers = numbers.reduce(
  (accumulator, value) => accumulator + value,
  0,
);
console.log(sumOfNumbers); // 15

(Link na CodePen)

Primjer 2. Implementacije map() i filter() metoda

reduce() je moćan alat s kojim možemo napraviti i naše implementacije map() i filter() metoda.

Map

const pets = ['dog', 'snake', 'cat', 'hamster'];
function map(array, callback) {
  return array.reduce((accumulator, value) => {
    accumulator.push(callback(value));
    return accumulator;
  }, []);
}
const loudPets = map(pets, value => value.toUpperCase());

console.log(loudPets);

(Link na CodePen)

Filter

const pets = [
  { name: 'Doug', type: 'dog' },
  { name: 'Keanu', type: 'cat' },
  { name: 'Ghost', type: 'direwolf' },
  { name: 'Stevens', type: 'cat' },
];
function filter(array, testCallback) {
  return array.reduce((accumulator, value) => {
    if (testCallback(value)) {
      accumulator.push(value);
    }
    return accumulator;
  }, []);
}
const cats = filter(pets, pet => pet.type === 'cat');
console.log(cats);

(Link na CodePen)

Primjer 3. Uklanjanje duplikata iz array-a

const pets = ['dog', 'snake', 'dog', 'cat', 'hamster', 'cat', 'cat', 'snake'];
const oneOfEachPet = pets.reduce((accumulator, pet) => {
  if (accumulator.indexOf(pet) === -1) {
    accumulator.push(pet);
  }
  return accumulator;
}, []);
console.log(oneOfEachPet);

(Link na CodePen)

Currying

Currying je tehnika pozivanja funkcije gdje funkcija ne prima sve argumente odjednom. Umjesto toga prima prvi argument i vraća novu funkciju koja prima drugi argument i vraća novu funkciju (ili rezultat ako se radi o funkciji sa dva argumenta), itd...

Na primjer, gdje običnu add funkciju pozivamo ovako:

add(1, 2, 3);

curried verzija bi se pozivala ovako:

add(1)(2)(3);

Primjer implementacije:

function add(a, b) {
  return a + b;
}

function curriedAdd(a) {
  return function(b) {
    return a + b;
  };
}

console.log(add(5, 3));
console.log(curriedAdd(5)(3));

(Link na CodePen)

Funkcija curriedAdd() se može kraće napisati koristeći arrow funkcije:

const curriedAdd = a => b => a + b;
console.log(curriedAdd(5)(3));

(Link na CodePen)

Zašto koristiti currying?

Currying je koristan za pripremu argumenata unaprijed za npr. map, filter, reduce, event handlere i sl.

const pets = [
  { name: 'Doug', type: 'dog' },
  { name: 'Keanu', type: 'cat' },
  { name: 'Ghost', type: 'direwolf' },
  { name: 'Stevens', type: 'cat' },
];

const isType = type => pet => pet.type === type;
const cats = pets.filter(isType('cat'));
const direwolfs = pets.filter(isType('direwolf'));
console.log('cats', cats);
console.log('direwolfs', direwolfs);

(Link na CodePen)

Dependency injection (sa funkcijama)

Dependency injection je jednostavna tehnika koju koristimo u objektno orijentiranom programiranju gdje objektu kroz konstruktor dajemo druge objekte (tzv. ovisnosti) koji mu trebaju. Ovo radimo kako bi naš kôd učinili lakšim za održavanje, reuse i testiranje.

Dependency injection u funkcionalnom programiranju nam olakšava bind() metoda. S ovom tehnikom možemo neke argumente funkciji zadati unaprijed bez da ju pozovemo.

Primjer

Recimo da želimo implementirati metodu koja vraća ime korisnika za zadani id. Ova metoda vrši ajax poziv na server i dohvaća ime korisnika iz server response-a. U primjerima koristimo jQuery .get() metodu za izvršavanje ajax poziva.

Primjer bez dependecy injection:

function getUserName(id) {
  return $.get(`https://www.example.com/users/${id}`).then(user => user.name);
}

getUserName(42);

Gornji primjer nije testabilan, jer u unit testovima ne želimo zaista vršiti ajax pozive. Kako bi rješili ovaj problem, promijeniti ćemo getUserName() funkciju da prima dva parametra, gdje je prvi ajax funkcija koju će koristiti:

function getUserName($get, id) {
  return $get(`https://www.example.com/users/${id}`).then(user => user.name);
}

getUserName($.get, 42);

Unit test sada može funkciji getUserName() dati svoju implementaciju $.get koja ne vrši ajax poziv.

Naravno, ne želimo svaki put kada koristimo ovu funkciju u produkciji slati i $.get parametar, pa ćemo za produkciju definirati novu funkciju makeGetUserName() koja vraća getUserName sa unaprijed zadanim $get parametrom:

// Ovu funkciju koristimo u produkciji
export default function makeGetUserName($get) {
  // Prvi argument bind funkcije definira vrijednost od this (nije bitan jer ga
  // getUserName() ne koristi)
  return getUserName.bind(null, $get);
}

// Ovu funkciju koristimo u testovima
export function getUserName($get, id) {
  return $get(`https://www.example.com/users/${id}`).then(user => user.name);
}

Ako smo sigurni da ćemo uvijek koristiti $.get, možemo definirati wrapper funkciju koja nekonfiguriranoj funkciji proslijeđuje $.get kao prvi parametar.

// Ovu funkciju koristimo u produkciji
export default function getUserName(id) {
  return getUserNameUnconfigured($.get, id);
}

// Ovu funkciju koristimo u testovima
export function getUserNameUnconfigured($get, id) {
  return $get(`https://www.example.com/users/${id}`).then(user => user.name);
}