Многие дети должны были слышать о концепции функционального программирования.Возможно, некоторые дети слышали о функциональном программировании, но мало что о нем знают, но на самом деле в нашем процессе разработки оно более или менее применялось. функционального программирования.
По сравнению с объектно-ориентированным программированием, которое фокусируется на данных, функциональное программирование фокусируется на действиях, которые представляют собой процесс абстрактного мышления, заключающийся в абстрагировании текущего действия.
Например, я хочу вычислить значение числа плюс 4, а затем умножить его на 4. Согласно логике обычного написания кода, мы можем сделать это
function calculate(x){
return (x + 4) * 4;
}
console.log(calculate(1)) // 20
С этим проблем нет, в процессе обычной разработки мы часто инкапсулируем операции, которые нужно повторять, в функции, чтобы их можно было вызывать в разных местах. Но с точки зрения функционального программирования мы сосредоточимся на действиях этой серии операций, сначала «прибавить 4», а затем «умножить на 4».
Как лучше всего инкапсулировать функции? Как инкапсулировать, чтобы сделать функцию более общей и удобной в использовании? Функциональное программирование может вдохновить нас.
Функциональное программирование имеет две основные характеристики.
- Функции являются гражданами первого класса
- функции - это чистые функции
Функции являются гражданами первого класса
Граждане первого класса означают, что функции находятся наравне с другими типами данных и могут быть присвоены другим переменным, переданы в качестве параметров другой функции или использованы в качестве возвращаемого значения других функций.
// 赋值
var a = function fn1() { }
// 函数作为参数
function fn2(fn) {
fn()
}
// 函数作为返回值
function fn3() {
return function() {}
}
функции - это чистые функции
Чистая функция — это функция, которая всегда выдает один и тот же результат при одних и тех же входных данных и не имеет побочных эффектов.
Из концепции чистой функции мы можем знать, что чистая функция имеет две характеристики:
- тот же вход и выход
- Нет побочных эффектов
Отсутствие побочных эффектов означает, что операции внутри функции не повлияют на внешний мир (например, изменение значения глобальных переменных, изменение узлов dom и т. д.).
// 是纯函数
function add(x,y){
return x + y
}
// 输出不确定,不是纯函数
function random(x){
return Math.random() * x
}
// 有副作用,不是纯函数
function setColor(el,color){
el.style.color = color ;
}
// 输出不确定、有副作用,不是纯函数
var count = 0;
function addCount(x){
count+=x;
return count;
}
В функциональном программировании есть две основные операции: композиция и каррирование.
Композиция функций (compose)
Композиция функций относится к объединению нескольких функций, представляющих отдельные действия, в одну функцию.
Как упоминалось выше, функциональное программирование — это абстракция процесса, ориентированная на действия. Взяв в качестве примера приведенный выше расчет, сосредоточимся на его действии, сначала «прибавьте 4», а затем «умножьте на 4». Тогда наша реализация кода выглядит следующим образом
function add4(x) {
return x + 4
}
function multiply4(x) {
return x * 4
}
console.log(multiply4(add4(1))) // 20
Согласно определению функциональной композиции, мы можем скомпоновать две вышеупомянутые функции, представляющие два действия, в одну функцию. Мы абстрагируем синтетическое действие в компоновку функции, которую можно легко узнать здесь Код функции компоновки выглядит следующим образом
function compose(f,g) {
return function(x) {
return f(g(x));
};
}
Таким образом, мы можем получить составную функцию следующим образом.
var calculate=compose(multiply4,add4); //执行动作的顺序是从右往左
console.log(calculate(1)) // 20
Как видите, если мы передаем функции, представляющие каждое действие, в функцию составления, мы можем получить окончательную функцию композиции. Однако ограничение приведенной выше функции компоновки заключается в том, что она может синтезировать только две функции.Что делать, если необходимо синтезировать более двух функций, поэтому нам нужна общая функция компоновки.
Тут я прямо привожу код общей функции compose
function compose() {
var args = arguments;
var start = args.length - 1;
return function () {
var i = start - 1;
var result = args[start].apply(this, arguments);
while (i >= 0){
result = args[i].call(this, result);
i--;
}
return result;
};
}
Давайте попрактикуемся в приведенной выше общей функции создания ~
function addHello(str){
return 'hello '+str;
}
function toUpperCase(str) {
return str.toUpperCase();
}
function reverse(str){
return str.split('').reverse().join('');
}
var composeFn=compose(reverse,toUpperCase,addHello);
console.log(composeFn('ttsy')); // YSTT OLLEH
Вышеупомянутый процесс имеет три действия: «привет», «преобразовать заглавную» и «обратное». Можно видеть, что функции, представленные тремя вышеуказанными действиями, объединяются в одну через композицию, и, наконец, выводится правильный результат.
Каррирование функций
Определение каррирования в Википедии: В информатике каррирование, также переводимое как каризация или гарризация, представляет собой преобразование функции, которая принимает несколько параметров, в один параметр (первый параметр исходной функции, один аргумент) и возвращает результат. новая функция, которая принимает оставшиеся аргументы и возвращает результат.
Каррированная функция — это новая функция, полученная путем каррирования функции. Из приведенного выше определения видно, что функция каррирования имеет следующие две характеристики:
- принимает один параметр;
- Возвращает новую функцию, которая принимает оставшиеся аргументы и возвращает результат;
Например~
function add(a, b) {
return a + b;
}
console.log(add(1, 2)) // 3
Если предположить, что каррирующей функцией функции add является addCurry, то из приведенного выше определения addCurry(1)(2) должен достичь того же эффекта, что и приведенный выше код, выведя 3 . Здесь мы можем легко узнать, что код addCurry выглядит следующим образом
// addCurry 是 add 的柯里化函数
function addCurry(a) {
return function(b) {
return a + b;
}
}
console.log(addCurry(1)(2)); // 3
Но предположим, что если есть функция createCurry, которая может добиться каррирования, то мы можем получить тот же результат следующим образом.
// createCurry 返回一个柯里化函数
var addCurry=createCurry(add);
console.log(addCurry(1)(2)); // 3
Как видите, функция createCurry принимает в качестве параметра функцию add и возвращает каррированную функцию addCurry, которая может обрабатывать остальные параметры в add. Этот процесс называется каррированием функций, и мы называем addCurry каррирующей функцией add .
Итак, как же получить функцию createCurry, реализующую каррирование? Здесь я даю код createCurry напрямую
// 参数只能从左到右传递
function createCurry(func, arrArgs) {
var args=arguments;
var funcLength = func.length;
var arrArgs = arrArgs || [];
return function(param) {
var allArrArgs=arrArgs.concat([param])
// 如果参数个数小于最初的func.length,则递归调用,继续收集参数
if (allArrArgs.length < funcLength) {
return args.callee.call(this, func, allArrArgs);
}
// 参数收集完毕,则执行func
return func.apply(this, allArrArgs);
}
}
Мы можем назвать это следующим образом
// createCurry 返回一个柯里化函数
var addCurry=createCurry(function(a, b, c) {
return a + b + c;
});
console.log(addCurry(1)(2)(3)); // 6
Вышеупомянутая функция createCurry смогла выполнить процесс каррирования, но она не настолько совершенна, что, если я захочу вызвать ее способом addCurry(1, 2)(3)? Тогда приведенный выше код не дает нужных нам результатов, поэтому нам нужно оптимизировать createCurry.Оптимизированный код createCurry выглядит следующим образом.
// 参数只能从左到右传递
function createCurry(func, arrArgs) {
var args=arguments;
var funcLength = func.length;
var arrArgs = arrArgs || [];
return function() {
var _arrArgs = Array.prototype.slice.call(arguments);
var allArrArgs=arrArgs.concat(_arrArgs)
// 如果参数个数小于最初的func.length,则递归调用,继续收集参数
if (allArrArgs.length < funcLength) {
return args.callee.call(this, func, allArrArgs);
}
// 参数收集完毕,则执行func
return func.apply(this, allArrArgs);
}
}
Оптимизированная функция createCurry более мощная
// createCurry 返回一个柯里化函数
var addCurry=createCurry(function(a, b, c) {
return a + b + c;
});
console.log(addCurry(1)(2)(3)); // 6
console.log(addCurry(1, 2, 3)); // 6
console.log(addCurry(1, 2)(3)); // 6
console.log(addCurry(1)(2, 3)); // 6
Каррирование на самом деле усложняет короткий ответ, но в то же время у нас больше степеней свободы в использовании функций.
Итак, какая польза от карри? Например~
Теперь нам нужно реализовать функцию, которая преобразует числа в массиве чисел в проценты. Согласно нормальной логике, мы можем реализовать следующий код
function getNewArray(array) {
return array.map(function(item) {
return item * 100 + '%'
})
}
console.log(getNewArray([1, 0.2, 3, 0.4])); // ['100%', '20%', '300%', '40%']
И если это реализовано путем каррирования
function map(func, array) {
return array.map(func);
}
var mapCurry = createCurry(map);
var getNewArray = mapCurry(function(item) {
return item * 100 + '%'
})
console.log(getNewArray([1, 0.2, 3, 0.4])); // ['100%', '20%', '300%', '40%']
Приведенный выше пример может быть слишком простым, чтобы показать возможности каррирования. Использование определенного каррирования необходимо сочетать с конкретными сценариями. Лично я не думаю, что каррирование необходимо ради каррирования. Наша конечная цель — лучше решить проблему, не так ли?
Еще одним важным понятием в функциональном программировании являются функторы.
Функтор
В предыдущем примере синтеза функций выполняется действие «добавление 4», а затем «умножение на 4». Мы видим, что код реализован в виде умножить4(добавить4(1)). функция аналогична compose(multiply4,add4)(1) для реализации кода.
При мысли о функциональном программировании, в дополнение к абстрагированию действий, мы также надеемся, что порядок выполнения действий будет более ясным, поэтому для приведенного выше примера мы надеемся выполнить наши действия в следующей форме.
fn(1).add4().multiply4()
Здесь нам нужно использовать концепцию функторов.
function Functor(val){
this.val = val;
}
Functor.prototype.map=function(f){
return new Functor(f(this.val));
}
Функторы можно просто понимать как структуры данных, полезные для метода карты. Вышеприведенный экземпляр Functor является функтором.
Метод карты функтора принимает параметр функции и возвращает новый функтор, который содержит значение, возвращенное после обработки параметром функции. Этот метод отображает каждое значение функтора в другой функтор.
Через функторную функцию мы можем вызвать следующим образом
console.log((new Functor(1)).map(add4).map(multiply4)) // Functor { val: 20 }
Метод вышеприведенного вызова (new Calculate(1)).map(add4).map(multiply4), который является почти тем эффектом, который нам нужен, но нам не нужно существование нового, поэтому мы монтируем его в Функтор по методу
function Functor(val){
this.val = val;
}
Functor.prototype.map=function(f){
return new Functor(f(this.val));
}
Functor.of = function(val) {
return new Functor(val);
}
Наконец, мы можем вызвать следующим образом
console.log(Functor.of(1).map(add4).map(multiply4)) // Functor { val: 20 }
Далее мы вводим различные общие функторы.
Возможно, функтор
Возможно, функтор — это функтор, который добавляет к методу карты суждение о нулевом значении.
Поскольку параметр функции в способе карты в функторе будет обработать значение внутри функтора, когда значение, передаваемое в функтор, пустое (например, null), может возникнуть ошибка.
function toUpperCase(str) {
return str.toUpperCase();
}
console.log(Functor.of(null).map(toUpperCase)); // TypeError
Может быть, функтор увеличился путем определения процесса карты NULLS, если интерьер функтора пуст, процесс напрямую возвращает значение нулевого внутреннего функтора.
function Maybe(val){
this.val = val;
}
Maybe.prototype.map=function(f){
return this.val ? Maybe.of(f(this.val)) : Maybe.of(null);
}
Maybe.of = function(val) {
return new Maybe(val);
}
При передаче нулевого значения при использовании функтора Maybe не сообщается об ошибке.
console.log(Maybe.of(null).map(toUpperCase)); // Maybe { val: null }
Либо функтор
Любой функтор ссылается на функтор, внутри которого есть lvalue (слева) и rvalue (справа), обычно используется rvalue, а lvalue используется, когда rvalue не существует.
function Either(left,right){
this.left = left;
this.right = right;
}
Either.prototype.map=function(f){
return this.right ? Either.of(this.left, f(this.right)) : Either.of(f(this.left), this.right);
}
Either.of = function(left,right) {
return new Either(left,right);
}
Таким образом, когда существуют и левое, и правое значения, значение r используется как значение функтора по умолчанию, а когда значение r не существует, левое значение используется как значение функтора по умолчанию.
function addOne(x) {
return x+1;
}
console.log(Either.of(1,2).map(addOne)); // Either { left: 1, right: 3 }
console.log(Either.of(3,null).map(addOne)); // Either { left: 4, right: null }
Монадный функтор
Функторы-монады — это функторы, которые могут аннулировать вложенность функторов нескольких уровней.
Значения, которые мы передаем в функтор, могут быть не только обычными типами данных, но и другими функторами, при передаче других функторов в функтор будет несколько уровней вложенности функторов. следующим образом
var functor = Functor.of(Functor.of(Functor.of('ttsy')))
console.log(functor); // Functor { val: Functor { val: Functor { val: 'ttsy' } } }
console.log(functor.val); // Functor { val: Functor { val: 'ttsy' } }
console.log(functor.val.val); // Functor { val: 'ttsy' }
К функторам Monad добавлены методы join и flatMap.С помощью flatMap мы можем разблокировать функтор каждый раз, когда передаем его.
Monad.prototype.map=function(f){
return Monad.of(f(this.val))
}
Monad.prototype.join=function(){
return this.val;
}
Monad.prototype.flatMap=function(f){
return this.map(f).join();
}
Monad.of = function(val) {
return new Monad(val);
}
С монадными функторами мы получаем функторы только с одним уровнем.
console.log(Monad.of('ttsy').flatMap(Monad.of).flatMap(Monad.of)); // Monad { val: 'TTSY' }
В нашем обычном процессе разработки нам нужно реализовывать функции с разными функциями по разным сценариям, а функциональное программирование позволяет реализовать функции наилучшим образом с разных точек зрения, но функциональное программирование — это не Либо одно или другое, а выбор разных реализации в соответствии с различными сценариями применения.