Функциональное программирование JavaScript

внешний интерфейс JavaScript функциональное программирование

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

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

Например, я хочу вычислить значение числа плюс 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' }

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