Функциональное программирование JS, объясненное ленивыми людьми

JavaScript
Функциональное программирование JS, объясненное ленивыми людьми

Эта статья включена в:Путешествие по построению фундамента с нуля (просто говоря, постоянное обновление~)

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

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

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

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

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

Особенности функционального программирования

Функции являются гражданами первого класса.

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

// 函数式编程-函数作为返回参数
const add = (x) => {
  return plus = (y) => {
    return x + y;
  }
};
let plus1 = add(1);
let plus2 = add(2);

console.log(plus1(1)); // 2
console.log(plus2(1)); // 3

Декларативное программирование

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

Возьмем, к примеру, SQL, в нем нет команды «сделай это, затем сделай это», это просто выражение, указывающее, какие данные мы хотим получить из базы данных. Что касается того, как получить данные, решать ему. В будущем, независимо от того, будет ли обновлена ​​база данных или оптимизирован механизм SQL, вообще не потребуется изменять оператор запроса.

Безгражданство и неизменяемые данные

Вот основные концепции функционального программирования:

  • Данные неизменны:Он требует, чтобы все ваши данные были неизменяемыми, а это означает, что если вы хотите изменить объект, вы должны создать новый объект для изменения, а не изменять существующий.
  • нет статуса:Суть в том, чтобы подчеркнуть, что для функции, независимо от того, когда вы ее запускаете, она должна давать те же входные данные и те же выходные данные, что и при первом запуске, полностью независимые от внешних изменений состояния.
// 比较 Array 中的 slice 和 splice
let test = [1, 2, 3, 4, 5];

// slice 为纯函数,返回一个新的数组
console.log(test.slice(0, 3)); // [1, 2, 3]
console.log(test); // [1, 2, 3, 4, 5]

// splice则会修改参数数组
console.log(test.splice(0, 3)); // [1, 2, 3]
console.log(test); // [4, 5]

Функции должны быть естественными и не иметь побочных эффектов

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

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

Побочные эффекты могут включать, но не ограничиваются:

  • изменить файловую систему
  • вставить запись в базу
  • отправить http запрос
  • переменные данные
  • печать/журнал
  • Получить пользовательский ввод
  • DOM-запрос
  • статус системы доступа

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

  1. Кэшируемость.

    Чистые функции могут кэшироваться на основе их ввода.

  2. Портативность/Самодокументация.

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

    ****

  3. Тестируемый

    Чистые функции упрощают тестирование. Нам не нужно подделывать «настоящий» платежный шлюз или настраивать и подтверждать состояние после каждого теста. Просто дайте функции ввод и утвердите вывод.

  4. Разумный

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

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

  5. параллельный код

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

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

Ленивая оценка

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

Есть два типа операций, которые необходимы в функциональном программировании:карриа такжефункциональная композиция(Составить)

карри

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

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

// 给 list 中每个元素先加 1,再加 5,再减 1
let list = [1, 2, 3, 4, 5];

//正常做法
let list1 = list.map((value) => {
  return value + 1;
});
let list2 = list1.map((value) => {
  return value + 5;
});
let list3 = list2.map((value) => {
  return value - 1;
});
console.log(list3); // [6, 7, 8, 9, 10]

// 柯里化
const changeList = (num) => {
  return (data) => {
    return data + num
  }
};
let list1 = list.map(changeList(1)).map(changeList(5)).map(changeList(-1));
console.log(list1); // [6, 7, 8, 9, 10]

Возвращаемая функция запоминает первый параметр, переданный с помощью замыкания.

Немного утомительно вызывать его снова и снова, мы можем использовать специальныйcurryВспомогательные функции упрощают определение и вызов таких функций.

var curry = require('lodash').curry;

var match = curry(function(what, str) {
  return str.match(what);
});

var replace = curry(function(what, replacement, str) {
  return str.replace(what, replacement);
});

var filter = curry(function(f, ary) {
  return ary.filter(f);
});

var map = curry(function(f, ary) {
  return ary.map(f);
});

Приведенный выше код следует простому, но очень важному шаблону. То есть стратегически поместите данные, с которыми нужно работать (строка, массив), в последний параметр.

Вы можете вызвать функцию curry один раз или несколько раз только с одним параметром за раз.

match(/\s+/g, "hello world");
// [ ' ' ]

match(/\s+/g)("hello world");
// [ ' ' ]

var hasSpaces = match(/\s+/g);
// function(x) { return x.match(/\s+/g) }

hasSpaces("hello world");
// [ ' ' ]

hasSpaces("spaceless");
// null

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

карри очень полезно, как вhasSpaces,findSpacesа такжеcensoredКак видите, вы можете получить новую функцию, просто передав ей некоторые параметры.

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

var getChildren = function(x) {
  return x.childNodes;
};

var allTheChildren = map(getChildren);

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

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

функциональная композиция

Цель композиции функций состоит в том, чтобы объединить несколько функций в одну функцию.

const compose = (f, g) => {
  return (x) => {
    return f(g(x));
  };
};

существуетcomposeВ определении ,gбудет предшествоватьfвыполняется, таким образом создавая поток данных справа налево. Концепция комбинации взята непосредственно из учебников по математике, а выполнение справа налево может лучше отражать смысл математики.

Все комбинации имеют свойство

// 结合律(associativity)
var associative = compose(f, compose(g, h)) == compose(compose(f, g), h);
// true

Итак, если мы хотим сделать строку прописной (скажем,head,reverse,toUpperCaseфункция существует), которую можно записать так:

compose(toUpperCase, compose(head, reverse));

// 或者
compose(compose(toUpperCase, head), reverse);

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

pointfree

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

// 非 pointfree,因为提到了数据:word
var snakeCase = function (word) {
  return word.toLowerCase().replace(/\s+/ig, '_');
};

// pointfree
var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);

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

debug

Если вы столкнулись с трудностями при отладке комбинации, вы можете использовать следующую практичную, но нечистуюtraceФункция для отслеживания выполнения кода.

var trace = curry(function(tag, x){
  console.log(tag, x);
  return x;
});

Преимущество

  1. лучшее управление состоянием. Потому что его цель состоит в том, чтобы быть без гражданства или с меньшим состоянием. При обычной разработке DOM, поскольку визуальное представление DOM зависит от изменений состояния, неизбежно генерируется множество состояний, и разные компоненты могут зависеть друг от друга. Программирование в FP может минимизировать эти неизвестные, оптимизировать код и уменьшить количество ошибок.
  2. более легкое повторное использование. Экстремальный код FP должен заключаться в том, что каждая строка кода является функцией, конечно, нам не нужно быть настолько экстремальными. Мы пытаемся реализовать логику процесса как более чистую функцию, с фиксированным входом -> фиксированным выходом, без влияния других внешних переменных и без побочных эффектов. Таким образом, при повторном использовании кода совершенно необязательно учитывать его внутреннюю реализацию и внешнее влияние.
  3. более элегантное сочетание. Вообще говоря, веб-страницы состоят из отдельных компонентов. В самом маленьком случае функция может также состоять из нескольких небольших функций. Ссылаясь на второй пункт выше, более сильное повторное использование обеспечивает более сильную композиционность.
  4. Скрытые преимущества. Сократите объем кода и улучшите ремонтопригодность.

недостаток

  1. представление: Функциональное программирование имеет тенденцию перекрывать метод, что приводит к снижению производительности при переключении контекста. В то же время, в нефункциональном языке, таком как JS, функциональный метод должен быть медленнее, чем непосредственное написание операторных инструкций (движок будет специально оптимизирован для многих инструкций).
  2. занятость ресурса: В JS для достижения неизменности состояния объекта часто создаются новые объекты, поэтому на сборку мусора (Garbage Collection) оказывается больше нагрузки, чем на другие методы программирования. В некоторых случаях это может вызвать серьезные проблемы.
  3. ловушка рекурсии: В функциональном программировании для достижения итерации обычно используются рекурсивные операции.Чтобы уменьшить накладные расходы на производительность рекурсии, мы часто пишем рекурсию в форме хвостовой рекурсии, чтобы позволить синтаксическому анализатору оптимизировать. Но, как мы все знаем, JS не поддерживает оптимизацию хвостовой рекурсии.
  4. Код не легко читать. Люди, хорошо знакомые с FP, могут найти этот код не требующим пояснений. Незнакомые люди, встречая написанный малопонятный код, и разбираясь в коде, вынуждены вычислять его в уме в течение получаса.

В области внешнего интерфейса мы можем увидеть много теней функционального программирования: ES6 добавил стрелочные функции, Redux представил идею Elm для уменьшения сложности Flux, React16.6 начал запускать React.memo(), который сделал чисто функциональные компоненты. 16.8 Начиная пушить хук, рекомендуется использовать чистую функцию для записи компонента...

если вы получитеновые знания, или собранныйкрасивая картинка слева, Пожалуйста, нажмитеотличныйНу~

Один лайк и 100 прочтений, скажите, что вы были здесь, видели это, и это стоящая поездка сюда! !

Справочная документация:

  1. Функциональное программирование JavaScript
  2. Что такое функциональное программирование в JavaScript?
  3. руководство по функциональному программированию