Что такое функциональное программирование
Перед статьей расскажу о функциональном программировании (Functional Programming, aka. FP) (ниже я буду использовать FP для обозначения функционального программирования):
- FP должен гарантировать, что функции являются чистыми, не полагаясь на внешние переменные состояния и не вызывая побочных эффектов. Исходя из этой посылки, комбинация и вызов чистых функций не будут иметь зависимостей во временном порядке, и не нужно беспокоиться о проблемах при изменении порядка вызова нескольких функций, поэтому также будут устранены многие потенциальные ошибки.
- Функции должны иметь вход и выход. Если функции не хватает ввода или вывода, это на самом деле обработчикprocedureВот и все.
- Функция максимально функционирует, чтобы поддерживать одну функцию, если функция делает более одной вещи, она должна быть теоретически разделена на несколько функций.
- Одно из значений FP заключается в том, что декларативное программирование используется в нужный момент для абстрагирования контроля и производительности потока программы, что лучше, чем императивное программирование с точки зрения понимания и обслуживания.
- ФП — это парадигма, но это не значит, что она конфликтует с ООП (объектно-ориентированным программированием), конечно, они могут гармонично сосуществовать. по моему мнениюReactНа самом деле, это очень хороший каштан~
- Первоклассные функции Javascript и характеристики замыканий определяют, что Javascript действительно является подходящей стадией для FP.
Понимание замыканий
закрытие дляJavascriptКонечно, это очень важно. Однако для функционального программирования это еще более важно и должно быть освоено.Определение замыкания выглядит следующим образом:
Closure is when a function remembers and accesses variables from outside of its own scope, even when that function is executed in a different scope.
Я считаю, что большинство студентов хорошо разбираются в замыканиях, но изучение FP очень важно. Далее я проведу вас через это в многословной манере.Замыкания — это функции, которые могут читать переменные внутри других функций.
Простой пример выглядит следующим образом
// Closure demo
function cube(x) {
let z = 1;
return function larger(y) {
return x * y * z++;
};
}
const makeCube = cube(10);
console.log(makeCube(5)); // 50
console.log(makeCube(5)); // 100
Задумывались ли вы когда-нибудь о том, как функция makeCube или более крупная функция запоминает переменные x и z, не принадлежащие ее собственной области видимости? Посмотреть в консолиmakeCube.prototype, нажмите на собрание и обнаружите, что есть[[Scopes]]в этом встроенном домеClosure(cube)Запомните переменные x и z, когда функция больше вернется. Если вы вложите несколько слоев функций, вы также найдете несколько Closure(name) в[[Scopes]]изScopes[]В массиве найдите переменные по порядку.
Посмотрите на тестовый код ниже:
function cube(x) {
return function wrapper(y) {
let z = 1;
return function larger() {
return x * y * z++;
};
}
}
const makeCubeY = cube(10);
const makeCube = makeCubeY(5);
const $__VAR1__ = '1. This var is just for test.';
let $__VAR2__ = '2. This var is just for test.';
var $__VAR3__ = '3. This var is just for test.';
console.log(makeCubeY.prototype, makeCube.prototype);
console.log(makeCube()); // 50
console.log(makeCube()); // 100
РаспечататьmakeCubeY.prototype:
РаспечататьmakeCube.prototype:
С помощью этих экспериментов мы можем понять замыкания в Javascript с другой точки зрения: как замыкание находит переменные, которые не входят в его собственную область видимости? makeCube соответственно из[[Scopes]]Переменные y и z находятся в замыкании (оболочке) в , а переменная x находится в замыкании (кубе). Что касается переменных, объявленных глобальными параметрами let и const, то они размещаютсяScript, переменные, объявленные глобальной переменной var, помещаются вGlobalвнутри.
Прежде чем изучать ФП, особенно важно понять замыкания~ Потому что на самом деле большое количество служебных функций ФП используют функцию замыканий.
Вспомогательная функция
unary
const unary = fn => arg => fn(arg);
унарная функция, используемая только тогда, когда вы хотитеПередать параметр в функциюиспользовать в случае. Попробуйте рассмотреть следующие сценарии:
console.log(['1', '2', '3'].map(parseInt)); // [1, NaN, NaN]
console.log(['1', '2', '3'].map(unary(parseInt))); // [1, 2, 3]
parseInt(string, radix)Получает два параметра и функцию обратного вызова, полученную в функции картыcallback(currentValue[, index[, array]]), вторым параметром является индекс, если использование parseInt в данный момент является неправильным. Кроме конечноArray.prototype.map, функции обратного вызова в большом количестве встроенных методов массива не передают более одного параметра.Если есть применимый сценарий, требующий только первого параметра, унарная функция будет играть его значение, и это будет элегантно и лаконично подключен без изменения функции. (Для унарных функций fn — это переменные данные замыкающей памяти)
identity
const identity = v => v;
Некоторые студенты найдут функцию тождества необъяснимой? Для чего это? Я был сбит с толку с первого взгляда? Но рассмотрим следующий сценарий:
console.log([false, 1, 2, 0, '5', true].filter( identity )); // [1, 2, "5", true]
console.log([false, 0].some( identity )); // false
console.log([-2, 1, '3'].every( identity )); // true
как насчет этого? Ярко, не ожидалidentityИзначально функция была скрытой, но на самом деле, несмотря на то, что идентификатор возвращал исходное значение, в этих функциях Javascript преобразует тип возвращаемого значения в логическое значение. Например, функция фильтра. Мы видим, что MDN определяет описание фильтра следующим образом (см. предложение, выделенное жирным шрифтом).
filter() calls a provided callback function once for each element in an array, and constructs a new array of all the values for which callback returns a value that coerces to true.
constant
const constant = v => () => v;
Опять же, эта функция... На первый взгляд, я не знаю, что именно она делает. Но рассмотрим следующие сценарии:
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello!');
}, 200);
});
p1.then(() => 'Hi').then(console.log); // Hi!
p1.then(constant('Hi')).then(console.log); // Hi!
p1.then('Hi').then(console.log); // Hello!
из-заPromise.prototype.thenПринимает только функции, если мне нужно только передать значение, тоconstantобеспечит это удобство. Конечно, это не функциональное улучшение, но оно улучшает читабельность, что также является преимуществом функционального программирования.
spreadArgs & gatherArgs
const spreadArgs = fn => argsArr => fn( ...argsArr );
const gatherArgs = fn => (...argsArr) => fn( argsArr );
Эти две функции известны по именам. Они используются для раскрытия всех параметров функции и сбора всех параметров функции.Эти две функции явно противоположны, так каковы же их сценарии применения?
Пример функции spreadArgs выглядит следующим образом:
function cube(x, y, z) {
return x * y * z;
}
function make(fn, points) {
return fn(points);
}
console.log(make(cube, [3, 4, 5])); // NaN
console.log(make(spreadArgs(cube), [3, 4, 5])); // 60
Пример функции collectArgs выглядит следующим образом:
function combineFirstTwo([v1, v2]) {
return v1 + v2;
}
console.log([1, 2, 3, 4, 5].reduce(combineFirstTwo)); // Uncaught TypeError
console.log([1, 2, 3, 4, 5].reduce(gatherArgs(combineFirstTwo))); // 15
После прочтения вышеуказанного кода два простых функция инструментов могут легко преобразовать одну функцию, чтобы сделать ее подходящей для другой сцены. Если это должно дать вам представление о очаровании функционального программирования, то следующие две функции приведут вам больше сюрпризов.
partial & curry
const partial = (fn, ...presetArgs) => (...laterArgs) =>
fn(...presetArgs, ...laterArgs);
const curry = (fn, arity = fn.length, nextCurried) =>
(nextCurried = prevArgs => nextArg => {
const args = [...prevArgs, nextArg];
if (args.length >= arity) {
return fn(...args);
} else {
return nextCurried(args);
}
})([]);
Я считаю, что каждый должен знать более или менее о каррировании функций. Определение в Википедии:
В компьютерных науках каррирование (англ. Currying), также переводимое как каррирование или гарринг, представляет собой преобразование функции, которая принимает несколько параметров, в функцию, которая принимает один параметр (первый параметр исходной функции). возвращает новую функцию, которая принимает оставшиеся аргументы и возвращает результат.
Конечно, благодаря мощной силе замыканий в мире Javascript родилось оружие каррирования. Сначала прочтите приведенный выше код о функциях partiel и curry.
выпей кофе~
Сначала смоделируйте функцию ajax следующим образом:
function ajax(url, params, callback) {
setTimeout(() => {
callback(
`GET ${url} \nparams: ${params} \ndata: Hello! ${params} `
);
});
}
Рассмотрим сценарий частичного использования следующим образом:
const fetchPerson = partial( ajax, "http://some.api/person" );
fetchPerson('Teddy Bear', console.log);
/*
GET http://some.api/person
params: Teddy Bear
data: Hello! Teddy Bear
*/
Рассмотрим сценарий использования карри следующим образом:
const fetchPerson = curry(ajax)('http://some.api/person');
const fetchUncleBarney = fetchPerson('Uncle Barney');
fetchUncleBarney(console.log);
/*
GET http://some.api/person
params: Uncle Barney
data: Hello! Uncle Barney
*/
Функции partial и curry похожи по функциям, но имеют разные конкретные сценарии применения, но в целом curry немного более автоматизирован, чем partial.
но! Я полагаю, что у студентов, прочитавших пример, будет ряд вопросительных знаков? Почему лучше не передавать параметры сразу, вместо того, чтобы передавать их несколько раз по отдельности? Причины следующие:
- Первая и основная причина заключается в том, что как частичные, так и карри-функции позволяют нам разделить время и пространство вызова функции посредством управления параметрами. Традиционные функции должны собирать параметры за один раз для вызова, но иногда мы можем заранее установить некоторые параметры и передать оставшиеся параметры только тогда, когда функция должна быть запущена окончательно. Вот где частичное и карри становятся очень полезными.
- Наличие партиала и карри позволяет функциям составлять (compose) было бы удобнее. (Комбинацией функций также планируется поделиться с вами позже, поэтому я не буду здесь вдаваться в подробности).
- Конечно, самое главное — улучшить читаемость! Сначала вы можете так не думать, но если вы будете практиковать этот опыт, он может измениться.
P.S. Что касается практики функционального программирования, вы можете использоватьlodash/fpмодуль для вводной практики.
некоторые размышления
Поскольку я тоже новичок в функциональном программировании, если что-то не так, поправьте меня~
Далее я продолжу организовывать учебные материалы FP, изучать практику и сериализовать некоторые из моих знаний и размышлений о функциональном программировании.Я надеюсь добиться прогресса со всеми~
Спасибо (●´∀`●)~