После длительного обучения и работы с объектно-ориентированным программированием я сделал шаг назад, чтобы подумать о сложности системы.
Делать некоторые исследования, я нашел функциональные концепции программирования, такие как неподумность и чистые функции. Эти концепции позволяют создавать бесплатные функции без эффекта, поэтому легче поддерживать системы – с некоторыми другими преимущества .
В этом посте я расскажу вам больше о функциональном программировании, а некоторые важные концепции, с множеством примеров кода в JavaScript.
Что такое функциональное программирование?
Чистые функции
Первая фундаментальная концепция мы узнаем, когда мы хотим понять функциональное программирование, это Чистые функции Отказ Но что это действительно значит? Что делает функцию чистой?
Так как мы знаем, если функция чистый или не? Вот очень строгое определение чистоты:
- Он возвращает тот же результат, если учесть те же аргументы (он также называется как
детерминированные) - Это не вызывает наблюдаемых побочных эффектов
Это возвращает тот же результат, если учесть те же аргументы
Представьте, что мы хотим реализовать функцию, которая рассчитывает область круга. Нечистая функция получила бы Радиус Как параметр, а затем рассчитать RADIUS * RADIUS * PI :
let PI = 3.14; const calculateArea = (radius) => radius * radius * PI; calculateArea(10); // returns 314.0
Почему эта нечистая функция? Просто потому, что он использует глобальный объект, который не был передан в качестве параметра к функции.
Теперь представьте, что некоторые математики утверждают, что PI Значение на самом деле 42 и изменить значение глобального объекта.
Наша нечая функция теперь приведет к 10 * 10 * 42 = 4200 Отказ Для того же параметра ( RADIUS ), у нас есть другой результат.
Давайте исправим это!
let PI = 3.14; const calculateArea = (radius, pi) => radius * radius * pi; calculateArea(10, PI); // returns 314.0
Теперь мы всегда будем проходить ценность PI в качестве параметра к функции. Итак, теперь мы просто доступ к параметрам, передаваемым к функции. Нет Внешний объект Отказ
- Для параметров
РадиусиPI.14.У нас всегда будет тот же результат:314.0 - Для параметров
РадиусиПИУ нас всегда будет тот же результат: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У нас есть счетчик ценить. Наша нечая функция получает эту ценность и повторно назначает счетчик со значением, увеличенным на 1.
let counter = 1; const increaseCounter = (value) => value + 1; increaseCounter(counter); // 2 console.log(counter); // 1
Наблюдение : Мутурмамность обескуражена в функциональном программировании.
Мы изменяем глобальный объект. Но как бы мы сделали это чистый ? Просто верните значение увеличилось на 1.
Увидеть, что наша чистая функция inwareasecounter Возвращает 2, но счетчик Значение все еще то же самое. Функция возвращает увеличенное значение, не изменяя значение переменной.
Если мы будем следовать этим двум простым правилам, становится легче понять наши программы. Теперь каждая функция изолирована и не может повлиять на другие части нашей системы.
Чистые функции стабильны, последовательны и предсказуемыми. Учитывая одинаковые параметры, чистые функции всегда будут возвращать один и тот же результат. Нам не нужно думать о ситуациях, когда тот же параметр имеет разные результаты – потому что это никогда не произойдет.
Преимущества чистых функций
Код определенно проще проверить. Нам не нужно ничего поднимать. Таким образом, мы можем установить тестовые чистые функции с различными контекстами:
- Учитывая параметр
А→ Ожидайте, что функция возвращать значениеB - Учитывая параметр
C→ Ожидайте, что функция возвращать значениеD
Простой пример будет функцией для получения набора номеров и ожидает, что он увеличивает каждый элемент этой коллекции.
let list = [1, 2, 3, 4, 5]; const incrementNumbers = (list) => list.map(number => number + 1);
Мы получаем Числа Массив, используйте карта Чтобы увеличить каждое число и вернуть новый список увеличенных чисел.
incrementNumbers(list); // [2, 3, 4, 5, 6]
Для вход [1, 2, 3, 4, 5] ожидаемый вывод будет [2, 3, 4, 5, 6] .
Неподумность
Когда данные неизменяются, его Государство не может изменить После его создания. Если вы хотите изменить неизменный объект, вы не можете. Вместо этого Вы создаете новый объект с новым значением.
В JavaScript мы обычно используем для петля. Это следующее для Заявление имеет некоторые мусорные переменные.
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Так что здесь у нас есть сумма Функция, которая получает вектор численных значений. Функция вызывает себя, пока мы не получим список пустых ( наша рекурсия Базовый случай ). Для каждой «итерации» мы добавим значение для Всего Аккумулятор.
С рекурсией мы держим наши переменные неизменный. Список и Аккумулятор Переменные не изменяются. Он сохраняет то же значение.
Наблюдение : Мы можем использовать Уменьшить Для реализации этой функции. Мы рассмотрим это в теме функций высшего порядка.
Также очень распространено, чтобы создать окончательное состояние объекта. Представьте, что у нас есть строка, и мы хотим преобразовать эту строку в URL Slug Отказ
В объекте ориентировано на программирование в Ruby, мы создадим класс, скажем, URLSugfify Отказ И этот класс будет иметь проще говоря Способ преобразования ввода строки в 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"Это реализовано!
Здесь мы имеем обязательное программирование, которое именно мы хотим сделать в каждом проще говоря Процесс – первый в нижнем регистре, затем удалить бесполезные белые пробелы и, наконец, заменить оставшиеся белые пробелы с дефисами.
Но мы мутируем входное состояние в этом процессе.
Мы можем обрабатывать эту мутацию, выполняя функциональную композицию или цепочку функций. Другими словами, результат функции будет использоваться в качестве входа для следующей функции, без изменения исходной входной строки.
const string = " I will be a url slug ";
const slugify = string =>
string
.toLowerCase()
.trim()
.split(" ")
.join("-");
slugify(string); // i-will-be-a-url-slugЗдесь у нас есть:
Толкание: преобразует строку на все строчные буквыОбрезать: удаляет белое пространство с обоих концов строкиСплитиПрисоединяйтесь к: заменяет все экземпляры совпадения с заменой в данной строке
Мы объединяем все эти 4 функции, и мы можем "Slugify" Наша строка.
Ссылочная прозрачность
Давайте реализовать Функция квадрата :
const square = (n) => n * n;
Эта чистая функция всегда будет иметь тот же выход, учитывая тот же вход.
square(2); // 4 square(2); // 4 square(2); // 4 // ...
Прохождение 2 как параметр Функция квадрата всегда будет возвращаться 4. Так что теперь мы можем заменить Квадрат (2) с 4. Наша функция это переданный прозрачный Отказ
В основном, если функция последовательно дает тот же результат для одного и того же входа, он является референтно прозрачным.
Чистые функции + неизменная прозрачность
С этой концепцией классная вещь, которую мы можем сделать, это Memoize функция. Представьте, что у нас есть эта функция:
const sum = (a, b) => a + b;
И мы называем это с этими параметрами:
sum(3, sum(5, 8));
Сумма (5, 8) равен 13 Отказ Эта функция всегда будет привести к 13 Отказ Таким образом, мы можем сделать это:
sum(3, 13);
И это выражение всегда приведет к 16 Отказ Мы можем заменить все выражение с помощью численной постоянной и Memoize Это.
Функционирует как первоклассные объекты
Идея функций в качестве первоклассных объектов является то, что функции также рассматриваются как значения и используются в качестве данных.
Функции в качестве первоклассных объектов могут:
- Обратитесь к этому из констант и переменных
- пропустите его как параметр другим функциям
- вернуть его в результате других функций
Идея состоит в том, чтобы лечить функции как значения и передавать функции, такие как данные. Таким образом, мы можем объединить различные функции для создания новых функций с новым поведением.
Представьте, что у нас есть функция, которая суммирует два значения, а затем удваивает значение. Что-то вроде этого:
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 правда или ложь Значение, чтобы определить, должен ли элемент или не должен быть включен в коллекцию результата. В основном, если выражение обратного вызова это правда Функция фильтра будет включать элемент в коллекции результата. В противном случае это не будет.
Простой пример, когда у нас есть коллекция целых чисел, и мы хотим только четные номера.
Императивный подход
Императивный способ сделать это с JavaScript – это:
- Создайте пустой массив
четные числа - Итерация за
Числамножество - Нажмите даже номера для
Уважениемножество
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]Мы также можем использовать Фильтр Функция более высокого порядка, чтобы получить Даже Функция и вернуть список четных чисел:
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]
Одна интересная проблема, которую я решил на Хакерский ранг FP. Путь был Проблема массива фильтра Отказ Проблема идеи состоит в том, чтобы отфильтровывать данный массив целых чисел и выводить только те значения, которые меньше указанного значения X .
Императивное решение для JavaScript к этой проблеме является чем-то вроде:
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]Мы точно говорим, что нужно сделать нашу функцию – итерации за коллекцию, сравните текущий элемент сбора с х и нажмите этот элемент к Регистрация Если это пройдет условие.
Декларативный подход
Но мы хотим более декларативный способ решить эту проблему и использовать Фильтр Функция более высокого порядка.
Декларативное решение JavaScript было бы что-то вроде этого:
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]Использование Это в меньший Функция кажется немного странным в первую очередь, но легко понять.
Это будет второй параметр в фильтр функция. В этом случае 3 ( x ) представлен Это Отказ Вот и все.
Мы также можем сделать это с картами. Представьте, что у нас есть карта людей со своими имя и возраст Отказ
let people = [
{ name: "TK", age: 26 },
{ name: "Kaio", age: 10 },
{ name: "Kazumi", age: 30 }
];И мы хотим фильтровать только людей в течение определенного значения возраста, в этом примере людей более 21 года.
const olderThan21 = person => person.age > 21;
const overAge = people => people.filter(olderThan21);
overAge(people); // [{ name: 'TK', age: 26 }, { name: 'Kazumi', age: 30 }]Сводка кода:
- У нас есть список людей (с
имяивозраст). - У нас есть функция
STELTTHAN21Отказ В этом случае для каждого человека в людских массивах мы хотим получить доступ квозрасти посмотри, если это старше 21. - Мы фильтруем всех людей на основе этой функции.
карта
Идея карты состоит в том, чтобы преобразовать коллекцию.
Давайте получим то же самое люди Коллекция выше. Мы не хотим фильтровать «чрезмерным возрастом» сейчас. Мы просто хотим список строк, что-то вроде TK 26 лет Отказ Так что окончательная строка может быть : Имя: возраст лет где : Имя и : возраст атрибуты из каждого элемента в люди коллекция.
В обязательном состоянии JavaScript это было бы:
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']
В декларативном состоянии JavaScript это было бы:
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']Вся идея состоит в том, чтобы преобразовать данный массив в новый массив.
Еще один интересный рейтинг хакера был Обновить список проблем Отказ Мы просто хотим обновить значения данного массива со своими абсолютными значениями.
Например, вход [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 Функция для преобразования значения в его абсолютное значение и выполнить обновление на месте.
Это не Функциональный способ реализации этого решения.
Во-первых, мы узнали о неизменности. Мы знаем, насколько важна неизменность, чтобы сделать наши функции более последовательными и предсказуемыми. Идея состоит в том, чтобы создать новую коллекцию со всеми абсолютными значениями.
Во-вторых, почему бы не использовать карта Здесь, чтобы «преобразовать» все данные?
Моя первая идея была проверить Math.abs Функция для обработки только одного значения.
Math.abs(-1); // 1 Math.abs(1); // 1 Math.abs(-2); // 2 Math.abs(2); // 2
Мы хотим преобразовать каждое значение в положительное значение (абсолютное значение).
Теперь, когда мы знаем, как сделать Абсолют Для одного значения мы можем использовать эту функцию для пропускания в качестве аргумента для карта функция. Ты помнишь, что Функция высшего порядка может получить функцию как аргумент и использовать его? Да, карта может сделать это!
let values = [1, 2, 3, -4, 5]; const updateListMap = (values) => values.map(Math.abs); updateListMap(values); // [1, 2, 3, 4, 5]
Вот это да. Так прекрасно!
Уменьшать
Идея уменьшения состоит в том, чтобы получить функцию и коллекцию и вернуть значение, созданное путем объединения элементов.
Общим примером, о которых люди говорят, – это получить общую сумму заказа. Представьте, что вы были на сайте покупок. Вы добавили Продукт 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Использование Уменьшить мы можем построить функцию для обработки сумма суммы и пропустите его как аргумент для Уменьшить функция.
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Здесь у нас есть Корзинар , функция сумасмить это получает текущий CENTECTTOTALAMOUNT. и Заказать объект к сумма их.
gettotalamount Функция используется для Уменьшить Корзинар Используя сумасшедший и начать с 0 Отказ
Другой способ получить общую сумму составлять карта и Уменьшить Отказ Что я имею в виду под этим? Мы можем использовать карта трансформировать Корзинар в коллекцию сумма Значения, а затем просто используйте Уменьшить Функция с сумасмить функция.
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 Получает объект продукта и возвращает только сумма ценить. Так что же мы имеем здесь [10, 30, 20, 60] . А потом Уменьшить Сочетает в себе все предметы, добавляя вверх. Красивый!
Мы посмотрели на то, как работает каждый функция высшего порядка. Я хочу показать вам пример того, как мы можем составить все три функции в простом примере.
Говорить о Корзина Представьте, что у нас есть этот список продуктов в нашем порядке:
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 }
]Мы хотим общую сумму всех книг в нашей корзине. Просто как тот. Алгоритм?
- Фильтр по типу книги
- Превратите корзину в коллекцию суммы с использованием карты
- Объедините все предметы, добавив их с уменьшением
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Выполнено!
Ресурсы
Я организовал некоторые ресурсы, которые я прочитал и изучал. Я делюсь теми, которые я нашел действительно интересным. Для большего количества ресурсов посетите мой Функциональное программирование Github Repository
- Ecmascript 6 курс WES BOS
- JavaScript от OneMonth
- Ruby Special Resources
- Специфические ресурсы для JavaScript
- Clojure Special Resources
- Узнать реагировать, создавая приложение
Интрас
- Изучение FP в JS
- INTRO DO FP с Python
- Обзор ФП
- Быстрое введение в функциональные JS
- Что такое FP?
- Функциональный программирующий жаргон
Чистые функции
- Что такое чистая функция?
- Чистое функциональное программирование 1
- Чистое функциональное программирование 2
Неизменные данные
- Неизменные DS для функционального программирования
- Почему общий муравьего государства является корнем всего зла
Функции высшего порядка
- Красноречивый JS: функции высшего порядка
- Fun Fun Function Find Filter
- Fun Fun Function Map
- Fun Fun Function Основное уменьшение
- Fun Fun Function Advanced Уменьшить
- Функции более высокого порядка Clojure
- Чисто функциональный фильтр
- Чисто функциональная карта
- Чисто функционал уменьшить
Декларативное программирование
Вот и все!
Эй, люди, я надеюсь, что вы весело прочитали этот пост, и я надеюсь, что вы многое узнали здесь! Это была моя попытка поделиться тем, что я учусь.
Вот репозиторий со всеми кодами Из этой статьи.
Приди учишься со мной. Я разделяю ресурсы и мой код в этом Обучение функционального программирования хранилища Отказ
Я также написал FP Post, но используя в основном Clojure
Я надеюсь, вы увидели что-то полезное для вас здесь. И увидимся в следующий раз!:)
Тк
Оригинал: “https://www.freecodecamp.org/news/functional-programming-principles-in-javascript-1b8fc6c3563f/”