Функциональное программирование в JavaScript. Введение

Руслан Хуснетдинов

Функциональное программирование
в JavaScript

Введение

Функция

Функция в математике — соответствие между элементами двух множеств, установленное по такому правилу, что каждому элементу одного множества ставится в соответствие некоторый элемент из другого множества.

Википедия
Cats in box in box.

Функция

Функция в программировании — фрагмент программного кода, к которому можно обратиться из другого места программы.

Википедия

Функция — это программа, которая описывает связь между одними данными и другими.

Пример

			
const square = function (x) {
  return x * x;
}
			
		

Пример

			
const square = (x) => x * x;
			
		

Пример

			
const square = (x) => x * x;

const x0 = 0;
const x1 = 1;
const x2 = 2;
const x3 = 3;
			
		

Пример

			
const square = (x) => x * x;

const x0 = 0;
const x1 = 1;
const x2 = 2;
const x3 = 3;
			
		
			


const y0 = square(x0); // 0
const y1 = square(x1); // 1
const y2 = square(x2); // 4
const y3 = square(x3); // 9
			
		

Пример

			
const square = (x) => x * x;

const data1 = [0, 1, 2, 3]
const data2 = data1.map(square);

data2;     // [0, 1, 4, 9]
			
		

Объект первого класса

В JavaScript функции являются объектами первого класса (First Class Citizen). То есть мы можем создавать их в любом месте, передавать в качетсве аргументов в другие функции и т.д.

Функция есть значение.

Чистая функция

Чистая функция (pure function) — функция, которая не имеет побочных эффектов и является детерминированной.

Побочный эффект (side effect) — возможность функции читать и модифицировать значения глобальных переменных и осуществлять операции ввода-вывода.

Детерминированная функция — функция, которая всегда возвращают одно и то же значение для одних и тех же аргументов.

Thinking

Пример

			
// Нечистая функция
const sum = (x) => num + x;

let num = 5;
let result = sum(2); // 7

num = result; // 7
result = sum(result); // 14
			
		

Пример

			
// Чистая функция
const sum = (a, b) => a + b;

const num = 5;
const result = sum(num, 2); // 7

const newNum = result; // 7
const newResult = sum(newNum, result); // 14
			
		

Неизменяемость

Неизменяемые данные (immutable) — данные, которые нельзя изменить.

Thinking

Пример

			
// Изменяемые данные
const arr = [1, 2, 3];
arr.push(4);

arr; // [1, 2, 3, 4]
			
		

Пример

			
// Изменяемые данные (плохой пример)
const arr = [1, 2, 3];
const mutate = (arr) => arr.push(4);

arr; // [1, 2, 3]
const newArr = mutate(arr);
newArr // [1, 2, 3, 4]
			
		
Thinking

Пример

			
// Изменяемые данные, которые не изменяют
const arr = [1, 2, 3];
const newArr = [...arr, 4];

arr; // [1, 2, 3]
newArr; // [1, 2, 3, 4]
			
		

Пример

			
// Неизменяемые данные
const arr = Object.freeze([1, 2, 3]);
const newArr = Object.freeze([...arr, 4]);

arr; // [1, 2, 3]
newArr; // [1, 2, 3, 4]
			
		

Пример

			
// Неизменяемые данные (Immutable.js)
const arr = Immutable.List([1, 2, 3]);
const newArr = array.push(4);

arr; // [1, 2, 3]
newArr; // [1, 2, 3, 4]
			
		
Immutable

Функция высшего порядка

Функция высшего порядка (higher-order function) — функция, которая принимает в качестве аргументов или возвращает другие фунции.

Пример

			
const apply = (f) => (x) => f(x);

const dubble = (x) => 2 * x;
const number = 6;
const newNumber = apply(dubble)(number);

newNumber; // 12
			
		

Функции map, filter и reduce

Цикл – это жёсткая управляющая конструкция, которую нелегко использовать повторно и сложно состыковать с другими операциями. Кроме того, использование циклов означает появление кода, который меняется с каждой итерацией.

Луис Атенцио. Функциональное программирование в JavaScript.

Функции map, filter и reduce являются альтернативой циклам.

Функция map

Map — функция высшего порядка, которая применяет передаваемую ей функцию ко всем элементам передаваемого ей массива и возвращает новый массив, состоящий из результатов этого применения.

Это функция трансформации.

Пример

			
// Цикл
const arr = [1, 2, 3];
const dubble = (x) => 2 * x;
const newArr = [];

for (let i = 0; i < arr.length; i++) {
  newArr.push(dubble(arr[i]));
}

newArr; // [2, 4, 6]
			
		

Пример

			
// Map
const arr = [1, 2, 3];
const dubble = (x) => 2 * x;
const newArr = arr.map(dubble);

newArr; // [2, 4, 6]
			
		

Функция filter

Filter — функция высшего порядка, которая применяет передаваемую ей функцию ко всем элементам передаваемого ей массива и возвращает новый массив, состоящий из элементов первоначального массива, для которых передаваемая функция вернула истинное значение.

Это функция фильтрации.

Пример

			
// Цикл
const arr = [1, 2, 3];
const newArr = [];

for (let i = 0; i < arr.length; i++) {
  let value = arr[i];
  if (value % 2 === 0) {
    newArr.push(value);
  }
}

newArr; // [2]
			
		

Пример

			
// Filter
const arr = [1, 2, 3];
const newArr = arr.filter((value) => value % 2 === 0);

newArr; // [2]
			
		

Функция reduce

Reduce — функция высшего порядка, которая применяет передаваемую ей функцию к передаваемому ей аккумулятору и ко всем элементам передаваемого ей массива и возвращает значение итогового аккумулятора.

Это функция свёртки.

Пример 1

			
// Цикл
const arr = [1, 2, 3];
let acc = 0;

for (let i = 0; i < arr.length; i++) {
  acc = acc + arr[i];
}

acc; // 6
			
		

Пример 1

			
// Reduce
const arr = [1, 2, 3];
const acc = arr.reduce((acc, value) => acc + value);

acc; // 6
			
		

Пример 2

			
// Цикл
const arr = [1, 2, 3];
let max;

for (let i = 0; i < arr.length; i++) {
  let value = arr[i];
  if (!max || max < value) {
    max = value;
  }
}

max; // 3
			
		

Пример 2

			
// Reduce
const arr = [1, 2, 3];
const max = arr.reduce((acc, value) =>
	acc > value ? acc : value
);

max; // 3
			
		

Рекурсия

Рекурсия (recursion) — процесс вызова функции самой себя.

При рекурсивном процессе программа откладывает вычисления до возвращения функцией её значения, сохраняя на каждом шаге эти вычисления в памяти.

При итеративном процессе (хвостовая рекурсия) программа производит все вычисления сразу и передает их дальше, незаписывая их в память.

Recursion

Пример 1.1

			
// Рекурсивный процесс
const factorial = (n) => {
  if (n === 1) {
    return 1;
  }

  return n * factorial(n - 1);
};

const num = factorial(5); // 120
			
		
Recursion

Пример 1.2

			
// Итеративный процесс
const factorial = (n, acc = 1) => {
  if (n === 1) {
    return acc;
  }
  const newAcc = acc ? acc * n : n;
  return factorial(n - 1, newAcc);
};

const num = factorial(5); // 120
			
		

Пример 2

			
// Реализация map
const map = (arr, f, acc = []) => {
  if (!arr.length) {
    return acc;
  }
  const [head, ...tail] = arr;
  const newAcc = [...acc, f(head)];

  return map(tail, f, newAcc);
};
			
		

Пример 3

			
// Реализация filter
const filter = (arr, f, acc = []) => {
  if (!arr.length) {
    return acc;
  }
  const [head, ...tail] = arr;
  const newAcc = f(head) ? [...acc, head] : acc;

  return filter(tail, f, newAcc);
};
			
		

Пример 4

			
// Реализация reduce
const reduce = (arr, f, acc) => {
  if (!arr.length) {
    if (acc) { return acc } else { throw new Error() }
  }
  const [head, ...tail] = arr;
  const newAcc = acc ? f(acc, head) : head;

  return reduce(tail, f, newAcc);
};
			
		

Спасибо за внимание!