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

JavaScript ECMAScript 6

оригинал:medium.com/better-pro Страна…

Переводчик: Front-end Xiaozhi

Ставь лайк и смотри, поиск в WeChat【Переезд в мир】Обратите внимание на этого человека, который не имеет большого фабричного прошлого, но имеет восходящий и позитивный настрой. эта статьяGitHub GitHub.com/QQ449245884…Он был включен, статьи были классифицированы, и многие мои документы и учебные материалы были систематизированы.

Все говорили, что нет проекта для написания резюме, поэтому я помог вам найти проект, и это было с бонусом.【Учебник по строительству】.

После длительного изучения и использования объектно-ориентированного программирования давайте сделаем шаг назад и рассмотрим сложность системы.

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

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

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

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

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

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

Как узнать, является ли функция чистой? Вот очень строгое определение:

  • Возвращает тот же результат, если заданы одни и те же аргументы (также известные какуверенность).

  • Он не вызывает никаких побочных эффектов.

Я получаю тот же результат, если заданы те же параметры

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

Не чистая функция будет это делать, получаяradiusв качестве параметра, затем вычислитьradius * radius * PI:

let PI = 3.14;

const calculateArea = (radius) => radius * radius * PI;

calculateArea(10); // returns 314.0

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

А теперь представьте, что некоторые математики думают, что число пи на самом деле42И значение глобального объекта изменено.

Нечистая функция получает10 * 10 * 42 = 4200. по тем же параметрам (radius = 10), мы получаем разные результаты.

починить это:

let PI = 3.14;

const calculateArea = (radius, pi) => radius * radius * pi;

calculateArea(10, PI); // returns 314.0

теперь поставьPIЗначение передается в качестве параметра функции, так что внешние объекты не вводятся.

  • для параметровradius = 10а такжеPI = 3.14, всегда получайте один и тот же результат:314.0.
  • дляradius = 10а такжеPI = 42, всегда получайте один и тот же результат:4200

прочитать файл

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

const charactersCounter = (text) => `Character count: ${text.length}`;

function analyzeFile(filename) {
  let fileContent = open(filename);
  return charactersCounter(fileContent);
}

генерация случайных чисел

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

function yearEndEvaluation() {
  if (Math.random() > 0.5) {
    return "You get a raise!";
  } else {
    return "Better luck next year!";
  }
}

Нет очевидных побочных эффектов

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

Теперь мы хотим реализовать функцию, которая принимает целое число и возвращает это целое число.1работать и возвращаться.

let counter = 1;

function increaseCounter(value) {
  counter = value + 1;
}

increaseCounter(counter);
console.log(counter); // 2

Нечистая функция получает значение и переназначает егоcounter, его значение увеличивается1.

Функциональное программирование препятствует изменчивости. Мы модифицируем глобальный объект, но как сделать его чистой функцией? просто вернуть приращение1ценность .

let counter = 1;

const increaseCounter = (value) => value + 1;

increaseCounter(counter); // 2
console.log(counter); // 1

чистая функцияincreaseCounterвернуть2,ноcounterЗначение остается прежним. Функция возвращает увеличенное значение без изменения значения переменной.

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

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

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

Преимущества чистых функций

Чистый функциональный код определенно легче тестировать и не требует моков, поэтому мы можем проводить модульное тестирование чистых функций с другим контекстом:

  • задан параметрA, ожидайте, что функция вернет значениеB
  • задан параметрC, ожидайте, что функция вернет значениеD

Простой пример — взять набор чисел и добавить каждое число1Эта операция скульптуры из песка.

let list = [1, 2, 3, 4, 5];

const incrementNumbers = (list) => list.map(number => number + 1);

перениматьnumbersмассив, использоватьmapУвеличивайте каждое число и возвращайте новый список увеличенных чисел.

incrementNumbers(list); // [2, 3, 4, 5, 6]

для ввода[1,2,3,4,5], ожидаемый результат[2,3,4,5,6].

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

Несмотря на изменение времени или то же самое, чистые функции тяжеловесов одинаковы.

Когда данные неизменяемы, их состояние не может быть изменено после их создания.

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

В JS мы обычно используемforцикл,forкаждое пересечениеiявляется переменной переменной.

var values = [1, 2, 3, 4, 5];
var sumOfValues = 0;

for (var i = 0; i < values.length; i++) {
  sumOfValues += values[i];
}

sumOfValues // 15

При каждом обходе меняетсяiа такжеsumOfValueсостояние, но как нам справиться с изменчивостью при обходе?рекурсия.

let list = [1, 2, 3, 4, 5];
let accumulator = 0;

function sum(list, accumulator) {
  if (list.length == 0) {
    return accumulator;
  }

  return sum(list.slice(1), accumulator + list[0]);
}

sum(list, accumulator); // 15
list; // [1, 2, 3, 4, 5]
accumulator; // 0

Приведенный выше код имеетsumфункция, которая принимает числовой вектор. функция вызывает себя до тех пор, покаlistПустая выходная рекурсия. За каждую «прогулку» мы будем прибавлять к общей стоимостиaccumulatorсередина.

С рекурсией мы сохраняем переменную постоянной. не изменитсяlistа такжеaccumulatorПеременная. Он сохраняет то же значение.

Наблюдение: мы можем использоватьreduceдля достижения этой функции. Это обсуждается в следующем разделе о функциях высшего порядка.

Строительство конечного состояния объекта также очень распространено. Предположим, у нас есть строка, пытаясь преобразовать строкуurl slug.

В объектно-ориентированном программировании на Ruby мы можем создать классUrlSlugify, этот класс имеетslugifyметод преобразования строкового ввода вurl slug.

class UrlSlugify
  attr_reader :text
  
  def initialize(text)
    @text = text
  end

  def slugify!
    text.downcase!
    text.strip!
    text.gsub!(' ', '-')
  end
end

UrlSlugify.new(' I will be a url slug   ').slugify! # "i-will-be-a-url-slug"

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

Таким образом, изменяется входное состояние во всем процессе, что явно не соответствует концепции чистой функции.

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

const string = " I will be a url slug   ";

const slugify = string =>
  string
    .toLowerCase()
    .trim()
    .split(" ")
    .join("-");

slugify(string); // i-will-be-a-url-slug

Приведенный выше код в основном выполняет следующие действия:

  • toLowerCase: преобразовать строку во все строчные буквы.

  • обрезка: удаляет пробелы с обоих концов строки.

  • splitа такжеjoin: заменить все совпадающие экземпляры заменой в данной строке

ссылочная прозрачность

Затем реализуйтеsquareфункция:

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

При одних и тех же входных данных эта чистая функция всегда имеет одинаковые выходные данные.

square(2); // 4
square(2); // 4
square(2); // 4
// ...

Буду2Передача в качестве аргумента квадратной функции всегда возвращает4. Таким образом, мы можем поставитьsquare(2)заменить4, наша функция ссылочно прозрачна.

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

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

const sum = (a, b) => a + b;

вызовите его с этими параметрами

sum(3, sum(5, 8));

sum(5, 8)всегда равно13, так что вы можете сделать некоторые трюки:

sum(3, 13);

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

Функции — это первоклассные граждане в JS

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

  • Ссылайтесь на него из констант и переменных.
  • Передайте его в качестве аргумента другим функциям.
  • Верните его как результат других функций.

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

Предположим, у нас есть функция, которая суммирует два значения, а затем удваивает значение следующим образом:

const doubleSum = (a, b) => (a + b) * 2;

Возьмите разницу между двумя значениями, затем удвойте значения:

const doubleSubtraction = (a, b) => (a - b) * 2;

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

const sum = (a, b) => a + b;
const subtraction = (a, b) => a - b;

const doubleOperator = (f, a, b) => f(a, b) * 2;

doubleOperator(sum, 3, 1); // 8
doubleOperator(subtraction, 3, 1); // 4

fпараметр и использовать его для обработкиaа такжеb, который передается здесьsumфункция иsubtractionи использоватьdoubleOperatorфункции для объединения и создания новых моделей поведения.

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

Когда мы обсуждаем функции более высокого порядка, мы обычно включаем следующее:

  • Возьмите одну или несколько функций в качестве аргументов

  • возвращает функцию в результате

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

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

Filter

Для данной коллекции мы хотим фильтровать на основе атрибутов.filterфункция ожидаетtrueилиfalseзначение, чтобы определить, должен ли элемент быть включен в набор результатов.

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

Простой пример: у нас есть набор целых чисел, и нам нужны только четные числа.

императив

Чтобы получить все четные числа в массиве с помощью императивного подхода, это обычно делается так:

  • Создать пустой массивevenNumbers

  • перебирать массивnumbers

  • подтолкнуть четные числа кevenNumbersв массиве

    var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; var evenNumbers = [];

    for (var i = 0; i < numbers.length; i++) { if (numbers[i] % 2 == 0) { evenNumbers.push(numbers[i]); } }

    console.log(evenNumbers); // (6) [0, 2, 4, 6, 8, 10]

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

const even = n => n % 2 == 0; const listOfNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; listOfNumbers.filter(even); // [0, 2, 4, 6, 8, 10]

я здесьHacker Rank FPИнтересная задача решенаПроблема с массивом фильтров. Проблема состоит в том, чтобы отфильтровать заданный массив целых чисел и вывести только меньше указанного значения.Xэти значения.

Императивный подход обычно выглядит так:

var filterArray = function(x, coll) {
  var resultArray = [];

  for (var i = 0; i < coll.length; i++) {
    if (coll[i] < x) {
      resultArray.push(coll[i]);
    }
  }

  return resultArray;
}

console.log(filterArray(3, [10, 9, 8, 2, 7, 5, 1, 3, 0])); // (3) [2, 1, 0]

Декларативный способ

Для всегда выше мы бы предпочли более декларативный подход к этой проблеме, например:

function smaller(number) {
  return number < this;
}

function filterArray(x, listOfNumbers) {
  return listOfNumbers.filter(smaller, x);
}

let numbers = [10, 9, 8, 2, 7, 5, 1, 3, 0];

filterArray(3, numbers); // [2, 1, 0]

существуетsmallerиспользуется в функцииthis, сначала выглядит немного странно, но это легко понять.

filterВторой параметр в функции означает вышеуказанноеthis, то есть,xстоимость.

Мы также можем использоватьmapспособ сделать это. Представьте, что у вас есть набор информации

let people = [
  { name: "TK", age: 26 },
  { name: "Kaio", age: 10 },
  { name: "Kazumi", age: 30 }
]

мы хотим фильтроватьageДля лиц старше 21 года используйтеfilterСпособ

const olderThan21 = person => person.age > 21;
const overAge = people => people.filter(olderThan21);
overAge(people); // [{ name: 'TK', age: 26 }, { name: 'Kazumi', age: 30 }]

map

mapОсновная идея функций — преобразовывать коллекции.

mapМетод преобразует коллекцию, применяя функцию ко всем ее элементам и создавая новую коллекцию из возвращенных значений.

Если мы не хотим фильтровать людей старше 21 года, мы хотим отобразить что-то вроде этого:TK is 26 years old.

Используя императивный стиль, мы обычно делаем:

var people = [
  { name: "TK", age: 26 },
  { name: "Kaio", age: 10 },
  { name: "Kazumi", age: 30 }
];

var peopleSentences = [];

for (var i = 0; i < people.length; i++) {
  var sentence = people[i].name + " is " + people[i].age + " years old";
  peopleSentences.push(sentence);
}

console.log(peopleSentences); // ['TK is 26 years old', 'Kaio is 10 years old', 'Kazumi is 30 years old']

Декларатив сделает это:

const makeSentence = (person) => `${person.name} is ${person.age} years old`;

const peopleSentences = (people) => people.map(makeSentence);
  
peopleSentences(people);
// ['TK is 26 years old', 'Kaio is 10 years old', 'Kazumi is 30 years old']

Вся идея состоит в том, чтобы преобразовать данный массив в новый массив.

Еще одна интересная проблема HackerRank:проблема со списком обновлений. Мы хотим обновить значение массива его абсолютным значением.

Например, введите[1,2,3,- 4,5]Выход должен быть[1,2,3,4,5],-4Абсолютное значение4.

Простое решение — обновление значений в каждой коллекции на месте, опасная практика

var values = [1, 2, 3, -4, 5];

for (var i = 0; i < values.length; i++) {
  values[i] = Math.abs(values[i]);
}

console.log(values); // [1, 2, 3, 4, 5]

Мы используем функцию Math.abs, чтобы преобразовать значение в его абсолютное значение и выполнить обновление на месте.

Этот путь не лучшее решение.

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

Во-вторых, почему бы не использовать здесьmap"преобразовать" все данные

Первая мысль была проверитьMath.absФункция работает только с одним значением.

Math.abs(-1); // 1
Math.abs(1); // 1
Math.abs(-2); // 2
Math.abs(2); // 2

Мы хотим преобразовать каждое значение в положительное значение (абсолютное значение).

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

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

let values = [1, 2, 3, -4, 5];

const updateListMap = (values) => values.map(Math.abs);

updateListMap(values); // [1, 2, 3, 4, 5]

Reduce

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

Типичным примером является получение общей суммы заказа.

Предположим, вы находитесь на сайте магазина и добавили в корзину (заказ) Товар 1, Товар 2, Товар 3 и Товар 4. Теперь мы хотим рассчитать общее количество тележек:

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

var orders = [
  { productTitle: "Product 1", amount: 10 },
  { productTitle: "Product 2", amount: 30 },
  { productTitle: "Product 3", amount: 20 },
  { productTitle: "Product 4", amount: 60 }
];

var totalAmount = 0;

for (var i = 0; i < orders.length; i++) {
  totalAmount += orders[i].amount;
}

console.log(totalAmount); // 120

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

let shoppingCart = [
  { productTitle: "Product 1", amount: 10 },
  { productTitle: "Product 2", amount: 30 },
  { productTitle: "Product 3", amount: 20 },
  { productTitle: "Product 4", amount: 60 }
];

const sumAmount = (currentTotalAmount, order) => currentTotalAmount + order.amount;

const getTotalAmount = (shoppingCart) => shoppingCart.reduce(sumAmount, 0);

getTotalAmount(shoppingCart); // 120

здесь естьshoppingCart, чтобы получить текущийcurrentTotalAmountФункцияsumAmount, и суммируя ихorderобъект.

мы также можем использоватьmapБудуshoppingCartпреобразовать вamountсбор, а затем использоватьreduceфункция иsumAmountфункция.

const getAmount = (order) => order.amount; const sumAmount = (acc, amount) => acc + amount;

function getTotalAmount(shoppingCart) {
  return shoppingCart
    .map(getAmount)
    .reduce(sumAmount, 0);
}

getTotalAmount(shoppingCart); // 120

getAmountперениматьproductобъект и просто вернутьсяamountценность, то есть[10,30,20,60],Потом,reduceОбъедините все термины, сложив их вместе.

Пример трех функций

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

Говоря о корзинах, допустим, у нас есть этот список продуктов в нашем заказе.

let shoppingCart = [
  { productTitle: "Functional Programming", type: "books", amount: 10 },
  { productTitle: "Kindle", type: "eletronics", amount: 30 },
  { productTitle: "Shoes", type: "fashion", amount: 20 },
  { productTitle: "Clean Code", type: "books", amount: 60 }
]

Если вы хотите делать покупки с типом автомобиляbooksот суммы, что обычно делается так:

  • Отфильтровать тип, тип которого — книги

  • использоватьmapПреобразовать корзину вamountсобирать.

  • использоватьreduceСложите все предметы.

let shoppingCart = [
  { productTitle: "Functional Programming", type: "books", amount: 10 },
  { productTitle: "Kindle", type: "eletronics", amount: 30 },
  { productTitle: "Shoes", type: "fashion", amount: 20 },
  { productTitle: "Clean Code", type: "books", amount: 60 }
]

 
const byBooks = (order) => order.type == "books";
const getAmount = (order) => order.amount;
const sumAmount = (acc, amount) => acc + amount;

function getTotalAmount(shoppingCart) {
  return shoppingCart
    .filter(byBooks)
    .map(getAmount)
    .reduce(sumAmount, 0);
}

getTotalAmount(shoppingCart); // 70

Ошибки, которые могут существовать после развертывания кода, не могут быть известны в режиме реального времени.Чтобы решить эти ошибки впоследствии, много времени тратится на отладку журнала.Кстати, я рекомендую всем полезный инструмент мониторинга ошибок.Fundebug

общаться с

Статья постоянно обновляется каждую неделю. Вы можете выполнить поиск «Big Move to the World» в WeChat, чтобы прочитать и обновить ее как можно скорее (на одну или две статьи раньше, чем в блоге). Эта статья находится на GitHub.GitHub.com/QQ449245884…Он был включен, и многие мои документы были разобраны. Добро пожаловать в Звезду и совершенство. Вы можете обратиться в тестовый центр для ознакомления во время собеседования. Кроме того, обратите внимание на паблик-аккаунт и ответьте в фоновом режиме.Благосостояние, вы можете увидеть преимущества, вы знаете.