Статьи

Что такое функциональное программирование?

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

Функциональное программирование — это парадигма или стиль, который ценит неизменность, первоклассные функции, ссылочную прозрачность и чистые функции. Если ни одно из этих слов не имеет смысла для вас, не волнуйтесь! Мы разберем всю эту терминологию в этой статье.

Функциональное программирование развилось из лямбда-исчисления, математической системы, построенной вокруг абстракции и обобщения функций. В результате многие функциональные языки программирования выглядят очень математически. Хорошая новость: вам не нужно использовать функциональный язык программирования для реализации принципов функционального программирования в вашем коде. В этом посте мы будем использовать JavaScript, который имеет множество функций, которые делают его пригодным для функционального программирования, не привязываясь к этой парадигме.

Основные принципы функционального программирования

Теперь, когда мы обсудили, что такое функциональное программирование, давайте поговорим об основных принципах, лежащих в основе FP.

Чистые функции

Мне нравится думать о функциях как о машинах — они принимают входные данные или аргументы, а затем выводят что-то, возвращаемое значение. Чистые функции не имеют «побочных эффектов» или действий, которые не связаны с выходом функции. Некоторыми потенциальными побочными эффектами могут быть печать значения или выход из него с помощью console.log

Вот пример нечистой функции:

 let number = 2;

function squareNumber() {
  number = number * number; // impure action: manipulating variable outside function
  console.log(number); // impure action: console log-ing values
  return number;
}

squareNumber();

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

 // pure function
function squareNumber(number) {
  return number * number;
}

squareNumber(2);

Чистые функции работают независимо от состояния вне функции, поэтому они не должны полагаться на глобальное состояние или переменные вне себя. В первом примере мы используем number Это нарушает принцип. Если вы сильно полагаетесь на постоянно меняющиеся глобальные переменные, ваш код будет непредсказуемым и его будет сложно отследить. Будет сложнее выяснить, где происходят ошибки и почему значения меняются. Вместо этого использование только входов, выходов и переменных, локальных для функций, облегчает отладку.

Кроме того, функции должны следовать ссылочной прозрачности , что означает, что при определенном вводе их вывод всегда будет одинаковым. В приведенной выше функции, если я передам 24 То же самое не относится к вызовам API или генерации случайных чисел, как два примера. При одинаковом входе выходные данные могут возвращаться или не возвращаться.

 // Not referentially transparent
Math.random();
// 0.1406399143589343
Math.random();
// 0.26768924082159495ß

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

Функциональное программирование также отдает приоритет неизменности или не напрямую изменяет данные. Неизменность ведет к предсказуемости — вы знаете значения ваших данных, и они не меняются. Это делает код простым, тестируемым и способным работать в распределенных и многопоточных системах.

Неизменность часто играет роль, когда мы работаем со структурами данных. Многие методы массива в JavaScript напрямую модифицируют массив. Например, .pop().splice() Вместо этого, в рамках функциональной парадигмы, мы должны скопировать массив и в этом процессе удалить элемент, который мы хотим исключить.

 // We are mutating myArr directly
const myArr = [1, 2, 3];
myArr.pop();
// [1, 2]
 // We are copying the array without the last element and storing it to a variable
let myArr = [1, 2, 3];
let myNewArr = myArr.slice(0, 2);
// [1, 2]
console.log(myArr);

Первоклассные функции

В функциональном программировании наши функции являются первоклассными, что означает, что мы можем использовать их как любое другое значение. Мы можем создавать массивы функций, передавать их в качестве аргументов другим функциям и сохранять их в переменных.

 let myFunctionArr = [() => 1 + 2, () => console.log("hi"), x => 3 * x];
myFunctionArr[2](2); // 6

const myFunction = anotherFunction => anotherFunction(20);
const secondFunction = x => x * 10;
myFunction(secondFunction); // 200

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

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

filter используется для возврата нового массива из старого, который содержит только те значения, которые соответствуют условию, которое мы предоставляем.

 const myArr = [1, 2, 3, 4, 5];

const evens = myArr.filter(x => x % 2 === 0); // [2, 4]

map используется для перебора элементов в массиве, изменения каждого элемента в соответствии с предоставленной логикой. В приведенном ниже примере мы удваиваем каждый элемент в массиве, передавая функцию map, которая умножает наше значение на два.

 const myArr = [1, 2, 3, 4, 5];

const doubled = myArr.map(i => i * 2); // [2, 4, 6, 8, 10]

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

 const myArr = [1, 2, 3, 4, 5];

const sum = myArr.reduce((i, runningSum) => i + runningSum); // 15

Вы также можете реализовать любой из них самостоятельно! Например, вы можете создать функцию фильтра следующим образом:

 const filter = (arr, condition) => {
  const filteredArr = [];

  for (let i = 0; i < arr.length; i++) {
    if (condition(arr[i])) {
      filteredArr.push(arr[i]);
    }
  }

  return filteredArr;
};

Второй тип функций высшего порядка, функции, которые возвращают другие функции, также является относительно частой моделью. Например:

 const createGreeting = greeting => person => `${greeting} ${person}`

const sayHi = createGreeting("Hi")
console.log(sayHi("Ali")) // "Hi Ali"

const sayHello = createGreeting("Hello")
console.log(sayHi("Ali")) // "Hello Ali"

Карринг — это связанная техника, о которой вам может быть интересно почитать!

Композиция функций

Композиция функций — это когда вы объединяете несколько простых функций для создания более сложных. Таким образом, у вас может быть функция averageArrayaveragesum Отдельные функции невелики и могут использоваться повторно для других целей, а в сочетании они выполняют более полную задачу.

 const sum = arr => arr.reduce((i, runningSum) => i + runningSum);
const average = (sum, count) => sum / count;
const averageArr = arr => average(sum(arr), arr.length);

Преимущества

Функциональное программирование приводит к модульному коду. У вас есть небольшие функции, которые вы можете использовать снова и снова. Знание конкретной функциональности каждой функции означает, что выявление ошибок и написание тестов должны быть простыми, тем более что выходные данные функции должны быть предсказуемыми.

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

Как вы можете использовать функциональное программирование?

Вам не нужно полностью переходить к функциональному программированию, чтобы включить все эти идеи. Вы даже можете использовать многие идеи в сочетании с объектно-ориентированным программированием, которое часто считается его оппонентом.

React, например, включает в себя множество функциональных принципов, таких как неизменяемое состояние, но также использует синтаксис класса главным образом годами. Он также может быть реализован практически на любом языке программирования — вам не нужно писать Clojure или Haskell, если вы действительно этого не хотите.

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