напишите в начале
Эта статья длинная и разделена на три части: (Детская обувь, имеющая определенное представление о функциональном программировании и его преимуществах, может напрямую скачатьВторая частьначать читать)
Часть 1. Сначала я расскажу, что такое функциональное программирование и что значит использовать его в реальном коде.
Часть 2. Я сосредоточусь на двух наиболее важных методах функционального программирования: каррировании и композиции функций, а также на том, как их использовать, и на практическом опыте.
Третья часть: фактическая боевая глава, главным образом, через фактический бой, чтобы у всех более глубокое понимание этой парадигмы программирования.
Он завершается кратким изложением преимуществ и ограничений функционального программирования, а также некоторыми рекомендациями.
Что такое функциональное программирование
Уже в 1950-х годах, с созданием языка Лисп, функциональное программирование (ФП) начало появляться в поле зрения каждого.
В последние годы функциональный стиль снова стал популярен во всем мире программирования благодаря своим элегантным и простым функциям, а основные языки разрабатываются с большей отсылкой к функциональным функциям (лямбда-выражения, встроенная поддержка map, сокращение… … ), Java 8 начала поддерживать функциональное программирование.
Во внешнем интерфейсе мы также можем увидеть много теней функционального программирования: стрелочные функции были добавлены в ES6, Redux представил идею Elm для уменьшения сложности Flux, а React16.6 начал запускать React.memo(), что делает чистые функциональные компоненты Вероятно, 16.8 начал пихать хуки, рекомендуется использовать чистые функции для написания компонентов...
Все они без исключения показывают, что функциональное программирование, древняя парадигма программирования, с годами не утратило своего блеска, а стало более живым.
Вот еще несколько примеров того, как функциональное программирование также подходит для написания крупномасштабного программного обеспечения:
WhatsApp: с помощью Erlang WhatsApp может поддерживать 900 миллионов пользователей, всего лишь50 инженеров.
Дискорд: использоватьElixir, подобным образомDiscord каждую минутусправиться с более чем одниммиллион запросов.
Лично для меня функциональное программирование похоже на третью промышленную революцию, первыми двумя были императивное программирование и объектно-ориентированное программирование.
первый взгляд
Слишком много понятий, мало примеров, интуитивно понятно
Talk is cheap, show me the code
Предположим, у нас есть такое требование, мы прописали ряд имен в массиве, и теперь нам нужно внести некоторые изменения в эту структуру, нам нужно превратить массив строк в массив объектов, чтобы облегчить последующее расширение, и нам нужно сделать некоторые преобразования имен:
['john-reese', 'harold-finch', 'sameen-shaw']
// 转换成
[{name: 'John Reese'}, {name: 'Harold Finch'}, {name: 'Sameen Shaw'}]
императивное программирование
Используя традиционные идеи программирования, мы можем запускать код, временные переменные и циклы, как только доберемся до них:
const arr = ['john-reese', 'harold-finch', 'sameen-shaw'];
const newArr = [];
for (let i = 0, len = arr.length; i < len ; i++) {
let name = arr[i];
let names = name.split('-');
let newName = [];
for (let j = 0, naemLen = names.length; j < naemLen; j++) {
let nameItem = names[j][0].toUpperCase() + names[j].slice(1);
newName.push(nameItem);
}
newArr.push({ name : newName.join(' ') });
}
return newArr;
Полный, это почти все идеи подсознательного программирования, полностью ориентированные на процесс. Вы подумаете, что мне нужно сделать это последовательно:
- Определите временную переменную newArr.
- Мне нужно сделать цикл.
- Цикл должен выполнять arr.length раз.
- Каждый раз, когда вы занимаете первое место имени, вы будете склеивать оставшуюся часть.
- ...
- Наконец вернуть результат.
Это, конечно, решает задачу, и конечный результаткуча промежуточных временных переменных, одна только мысль об именах переменных заставляет меня съеживаться. При этом в процессе намешано много логики, обычно функции нужноПрочтите его от начала до конца, чтобы точно знать, что он делает, и если проблема возникает, ее трудно обнаружить.
функциональный
Я не думал, что с таким программированием что-то не так, пока не столкнулся с функциональным программированием. Давайте посмотрим, как бы об этом подумал FPer:
- Мне просто нужна функция для реализации из
String 数组
прибытьObject 数组
преобразование:
convertNames :: [String] -> [Object]
- Это включает в себя
String -> Object
Преобразование, то мне нужно иметь такую функцию для достижения этого преобразования:
convert2Obj :: String -> Object
-
Что касается этого преобразования, нетрудно представить, что нужны две функции:
-
capitalizeName
: преобразовать имя в указанную форму -
genObj
: Преобразование любого типа в объект
-
-
Если ты подумаешь об этом снова,
capitalizeName
По сути, это комбинация нескольких методов (split
,join
,capitalize
), остальные функции реализовать очень просто.
Хорошо, наша задача выполнена, мы можемзапустить код
const capitalize = x => x[0].toUpperCase() + x.slice(1).toLowerCase();
const genObj = curry((key, x) => {
let obj = {};
obj[key] = x;
return obj;
})
const capitalizeName = compose(join(' '), map(capitalize), split('-'));
const convert2Obj = compose(genObj('name'), capitalizeName)
const convertName = map(convert2Obj);
convertName(['john-reese', 'harold-finch', 'sameen-shaw'])
вы можете игнорироватьcurry
а такжеcompose
функция(Позадибудет представлен). Просто взглянув на эту идею программирования, можно ясно увидеть, что мыслительный процесс функционального программирования совершенно другой.функция, вместоОбработать, он подчеркивает, как решить проблему с помощью комбинации и преобразования функций, а не то, какое утверждение я пишу для решения проблемы Когда ваш код становится все больше и больше, разделение и комбинация таких функций будут генерировать мощную силу.
Почему это называется функциональным программированием?
Мы впервые увидели функциональное программирование раньше и знаем его прелесть, теперь давайте продолжим углубляться в функциональное программирование.
На самом деле функции мы изучили с детства, что такое первичная функция, квадратичная функция… Согласно академическому определению функции, функция — это описание между набором и набором.конверсионные отношения, ввод через функцию вернетздесь только одинвыходное значение.
так,функцияна самом делесвязь, или отображение, и это отображение компонуемо.Как только мы знаем, что тип вывода одной функции может соответствовать входу другой функции, их можно комбинировать. Помните, что я писал раньшеconvert2Obj
Эта функция:
const convert2Obj = compose(genObj('name'), capitalizeName)
Он фактически завершает комбинацию отношений отображения, преобразовывая данные изString
конвертировано вString
а затем преобразовать вObject
. Дети, хорошо разбирающиеся в математике, знают, что это сложная математическая операция:g°f = g(f(x))
В нашем мире программирования все, с чем нам нужно иметь дело, — это «данные» и «отношения», а отношения — это функции. что мы называемработа по программированиюПросто ищуСопоставление отношений, как только связь найдена, проблема решена, а остальное — пропустить данные через связь, а затем преобразовать их в другие данные.
особенно люблю использоватьсборочная линияЧтобы описать этот вид работы, примите входные данные как сырье, а выходные данные как продукт.Данные могут непрерывно перетекать с выхода одной функции на вход другой функции., и, наконец, вывести результат. Разве это не набор конвейеров?
Итак, теперь вы знаете, что такое функциональное программирование? Это на самом деле подчеркивает, как больше сосредоточиться на том, как программировать в процессе программирования.Стройте отношения. Решите все проблемы сразу, построив эффективный конвейер строительства. Вместо того, чтобы распределять энергию между различными перерабатывающими предприятиями, которые мчатся туда-сюда для передачи данных.
Особенности функционального программирования
Функции — это «первоклассные функции».
Вот что позволяет функциональное программированиепомещениеПотому что наши основные операции находятся в операционных функциях. Эта функция означает, что функция такая же, как и другие типы данных, которые могут присваивать значение другим переменным, либо в качестве параметра оно передается в другую функцию, либо в качестве возвращаемого значения другой функции, например front
const convert2Obj = compose(genObj('name'), capitalizeName)
Декларативное программирование
Как вы можете видеть из приведенного выше примера, функциональное программирование в основном связано с объявлением того, что мне нужно сделать, а не как это сделать. Этот стиль программирования называетсядекларативное программирование. Преимущество этого в том, что читабельность кода особенно высока, потому что большая часть декларативного кода близка к естественному языку, и в то же время высвобождается много рабочей силы, потому что не заботится о конкретной реализации, так что это может дать возможности оптимизации Конкретная реализация также облегчает наше разделение труда и сотрудничество.
Операторы SQL являются декларативными, вам не нужно заботиться о том, как реализован оператор Select, разные базы данных реализуют свои собственные методы и оптимизируют их. React также является декларативным, вам нужно только описать свой пользовательский интерфейс и то, как пользовательский интерфейс обновляется после изменения состояния, React обрабатывает это за вас во время выполнения, вместо того, чтобы самостоятельно рендерить и оптимизировать алгоритм сравнения.
Ленивая оценка
Так называемое ленивое выполнение означает, что функция выполняется только тогда, когда это необходимо, то есть никакие бессмысленные промежуточные переменные не генерируются. Как и в приведенном выше примере, самая большая разница между функциональным программированием и императивным программированием заключается в том, что в нем почти нет промежуточных переменных, а функции пишутся от начала до конца, а вызываются только в конце.convertName
дают реальные результаты.
Безгражданство и неизменяемые данные
Вот основные концепции функционального программирования:
- Данные неизменны:Он требует, чтобы все ваши данные были неизменяемыми, а это означает, что если вы хотите изменить объект, вы должны создать новый объект для изменения, а не изменять существующий.
- нет статуса:Суть в том, чтобы подчеркнуть, что для функции, независимо от того, когда вы ее запускаете, она должна давать те же входные данные и те же выходные данные, что и при первом запуске, полностью независимые от внешних изменений состояния.
Для достижения этой цели функциональное программирование предлагает, какие функции должны иметь функции: никаких побочных эффектов и чистые функции.
Нет побочных эффектов
Мы много слышали о слове «побочный эффект», и его значение таково: выполнение других вторичных функций в дополнение к основной функции функции. Основная функция в нашей функции, конечно, основана на вводевернуть результат, и наши наиболее распространенные побочные эффекты в функцияхУправление внешними переменными по желанию. Поскольку объекты в JS передают ссылочные адреса, даже если мы используемconst
Ключевое слово объявляет объект, который все еще может быть изменен. И именно эта «лазейка» дает нам возможность изменять объекты по своему желанию.
Например:map
Исходная функция функции заключается в преобразовании входного массива в соответствии с функцией для создания нового массива:
map :: [a] -> [b]
В JS часто можно увидеть следующую паруmap
"неправильное" использование, поставьтеmap
Как оператор цикла, а затем напрямую изменить значение в массиве.
const list = [...];
// 修改 list 中的 type 和 age
list.map(item => {
item.type = 1;
item.age++;
})
Таким образом, основная функция вывода функции исчезает, и она становится прямой модификацией внешних переменных, что является ее побочным эффектом. И способ записи без побочных эффектов должен быть:
const list = [...];
// 修改 list 中的 type 和 age
const newList = list.map(item => ({...item, type: 1, age:item.age + 1}));
Гарантия того, что функция не имеет побочных эффектов, с одной стороны, может обеспечить неизменность данных, а с другой стороны, позволяет избежать многих проблем, вызванных разделяемым состоянием. Это может быть неочевидно, когда вы поддерживаете код самостоятельно, но по мере итерации проекта количество участников проекта увеличивается, и у всех становится все больше и больше зависимостей и ссылок на одну и ту же переменную, и эта проблема будет становиться все больше и больше. серьезный. В конце концов, даже сам сопровождающий может не знать, где именно переменная была изменена и вызвала ошибку.
Передача по ссылке — это круто, крематорий рефакторинга кода
чистые функции
Чистые функции продвигают требование «отсутствия побочных эффектов» на шаг дальше. Я полагаю, вы встречали это слово во многих местах, вТри принципа Redux, мы видим, что требуется, чтобы все модификации использовали чистые функции.
Changes are made with pure functions
На самом деле концепция чистой функции очень проста:
-
Не зависит от внешнего состояния (без гражданства):Результат работы функции не зависит от глобальных переменных, этого указателя, операций ввода-вывода и т. д.
-
Нет побочных эффектов (данные без изменений):Не изменяйте глобальные переменные, не изменяйте входные параметры.
Итак, чистые функции на самом деле являются «функциями», что означаетОдин и тот же ввод, всегда один и тот же результат.
Следующие функции нечисты, потому что все они зависят от внешних переменных.Представьте, что кто-то вызвалchangeName
правильноcurUser
модифицировал, а потом звонишь куда-то ещеsaySth
, что может привести к неожиданным результатам.
const curUser = {
name: 'Peter'
}
const saySth = str => curUser.name + ': ' + str; // 引用了全局变量
const changeName = (obj, name) => obj.name = name; // 修改了输入参数
changeName(curUser, 'Jay'); // { name: 'Jay' }
saySth('hello!'); // Jay: hello!
Что, если бы это было написано как чистая функция?
const curUser = {
name: 'Peter'
}
const saySth = (user, str) => user.name + ': ' + str; // 不依赖外部变量
const changeName = (user, name) => ({...user, name }); // 未修改外部变量
const newUser = changeName(curUser, 'Jay'); // { name: 'Jay' }
saySth(curUser, 'hello!'); // Peter: hello!
Это не означает, что проблема перед.
Подчеркнем, в чем польза чистых функций, в чем смысл чистой функции?
-
Легко тестировать и оптимизировать: это значение очень важно при реальной разработке проекта.Поскольку чистые функции всегда будут возвращать один и тот же результат для одних и тех же входных данных, мы можем легко утверждать результат выполнения функции, а также гарантировать, что оптимизация функции не повлияет на выполнение. других кодов. Это идеально подходитРазработка через тестирование TDD (Разработка через тестирование)Идея состоит в том, что полученный код часто более надежен.
-
кешируемость: Поскольку один и тот же ввод может всегда возвращать один и тот же вывод, следовательно, мы можем заранее кэшировать результат выполнения функции, существует множество библиотек, которые имеют так называемые
memoize
функции, ниже приведена упрощенная версияmemoize
Например, эта функция может кэшировать результат функции для таких вещей, какfibonacci
Этот расчет может сыграть хороший эффект кэширования.
function memoize(fn) {
const cache = {};
return function() {
const key = JSON.stringify(arguments);
var value = cache[key];
if(!value) {
value = [fn.apply(null, arguments)]; // 放在一个数组中,方便应对 undefined,null 等异常情况
cache[key] = value;
}
return value[0];
}
}
const fibonacci = memoize(n => n < 2 ? n: fibonacci(n - 1) + fibonacci(n - 2));
console.log(fibonacci(4)) // 执行后缓存了 fibonacci(2), fibonacci(3), fibonacci(4)
console.log(fibonacci(10)) // fibonacci(2), fibonacci(3), fibonacci(4) 的结果直接从缓存中取出,同时缓存其他的
-
самодокументирующийся: поскольку чистые функции не имеют побочных эффектов, их зависимости ясны, что упрощает их наблюдение и понимание (лучше с помощью [подписи типа] (подпись типа #hindly-milner), описанной ниже).
-
меньше ошибок: использование чистых функций означает, что в вашей функцииЭто не указывает на неизвестное, нет ссылки на глобальную переменную, нет изменения параметра, эти общие состояния, как правило, являются источником подавляющего большинства ошибок.
Ладно, хватит разговоров, давайте посмотрим, как работает функциональное программирование в JS.
строительство трубопровода
Если есть две операции, которые необходимы в функциональном программировании, оникарриа такжеКомпозиция функций (Compose),каррирование на самом деле на конвейерестанция обработки, композиция функций нашасборочная линия, который состоит из нескольких станций обработки.
Далее давайте посмотрим, как использовать идеи функционального программирования в JS для сборки набора эффективных конвейеров.
Станция обработки - каррирование
Каррирование означает преобразование многомерной функции в последовательный вызов.единичная функция.
f(a,b,c) → f(a)(b)(c)
Давайте попробуем написатьcurry
версияadd
функция
var add = function(x) {
return function(y) {
return x + y;
};
};
const increment = add(1);
increment(10); // 11
Почему эта функция устройства важна? Помните, что мы говорили ранее, возвращаемое значение функции,Есть и только один?Если нам нужна бесперебойная сборочная линия, я должен убедиться, что продукция каждой из моих станций обработки просто поступает на вход следующей станции.Следовательно, все обрабатывающие станции на сборочной линии должны быть единичными функциями.
Теперь легко понять, почему каррирование подходящей композиции функций творит чудеса, потому что результат каррирования простоодин входиз.
Применение частичной функции против каррирования
Часто люди путают карри иПрименение частичных функций(Приложение частичной функции), их часто путают. На самом деле это неправильно. В Википедии есть четкое определение. Некоторые приложения-функции подчеркивают, что определенные параметры фиксированы и возвращаютменьшая функция. Это видно из следующего выражения:
// 柯里化
f(a,b,c) → f(a)(b)(c)
// 部分函数调用
f(a,b,c) → f(a)(b,c) / f(a,b)(c)
карриподчеркнул, чтоГенерация единичных функций,Применение частичных функцийАкцентфиксированные произвольные метапараметры, и то, что мы обычно используем в повседневной жизни, на самом делеПрименение частичных функций, преимущество этого заключается в том, что параметры могут быть фиксированными, общность функции уменьшается, а пригодность функции повышается.
// 假设一个通用的请求 API
const request = (type, url, options) => ...
// GET 请求
request('GET', 'http://....')
// POST 请求
request('POST', 'http://....')
// 但是通过部分调用后,我们可以抽出特定 type 的 request
const get = request('GET');
get('http://', {..})
Продвинутое каррирование
Обычно мы не пишем это самиcurry
функции, большинство готовых библиотек предоставляютcurry
Реализация функции, но у тех, кто ей пользовался, наверняка есть сомнения.Мы используем Lodash, Ramda и эти библиотеки.curry
Поведение функций не похоже на каррирование, похоже, они реализуют приложения с частичными функциями?
const add = R.curry((x, y, z) => x + y + z);
const add7 = add(7);
add7(1,2) // 10
const add1_2 = add(1,2);
add1_2(7) // 10
На самом деле в этих библиотекахcurry
Функции подверглись значительной оптимизации, в результате чего каррирование, реализованное в этих библиотеках, не является чистым каррированием, мы можем понимать их как «расширенное каррирование». Реализации этих версий могут, в зависимости от количества введенных вами параметров,Возвращает каррированную функцию/значение результата. который,Если количество заданных вами параметров удовлетворяет условиям функции, возвращаемое значение. Это решает проблему, заключающуюся в том, что если функция имеет несколько входов, ее можно избежать, используя(a)(b)(c)
Эта форма прошла.
Итак, вышеadd7(1, 2)
Может напрямую вывести результат не потому чтоadd(7)
Возвращается функция, которая принимает 2 параметра, но вы только что передали 2 параметра, все параметры устраивают, поэтому результат вычисляется за вас, следующий код очевиден:
const add = R.curry((x, y, z) => x + y + z);
const add7 = add(7);
add(7)(1) // function
еслиadd7
это функция, которая принимает 2 аргумента, тогдаadd7(1)
Должен возвращать не функцию, а значение.
Итак, запомните эту фразу:Мы можем использовать расширенное каррирование для достижения частичного применения функции, но каррирование не равно частичному применению функции..
Применение каррирования
Обычно на практике мы используем каррирование, чтобы превратить функцию в одно значение, что может увеличить разнообразие функции и сделать ее более применимой:
const replace = curry((a, b, str) => str.replace(a, b));
const replaceSpaceWith = replace(/\s*/);
const replaceSpaceWithComma = replaceSpaceWith(',');
const replaceSpaceWithDash = replaceSpaceWith('-');
Таким образом, мы получаем изreplace
В функции генерируется много новых функций, которые можно использовать в различных случаях.
Что еще более важно, однозначные функции — это то, о чем мы собираемся говорить.Основы функциональной композиции.
Конвейерная обработка — композиция функций
Выше мы используемcurry
, очень легко построить станцию обработки, и теперь пришло время объединить ее в сборочную линию.
концепция функциональной композиции
Цель композиции функций состоит в том, чтобы объединить несколько функций в одну функцию. Давайте посмотрим на упрощенную версию реализации:
const compose = (f, g) => x => f(g(x))
const f = x => x + 1;
const g = x => x * 2;
const fg = compose(f, g);
fg(1) //3
Мы видим, чтоcompose
Реализуется простая функция: формируется новая функция, и эта функция являетсяg -> f
трубопровод. В то же время мы можем легко найтиcompose
На самом деле он удовлетворяет ассоциативности
compose(f, compose(g, t)) = compose(compose(f, g), t) = f(g(t(x)))
Пока порядок непротиворечив, конечный результат непротиворечив, поэтому мы можем написать более сложныйcompose
, который поддерживает несколько комбинаций функций:
compose(f, g, t) => x => f(g(t(x))
Простая реализация выглядит следующим образом:
const compose = (...fns) => (...args) => fns.reduceRight((val, fn) => fn.apply(null, [].concat(val)), args);
const f = x => x + 1;
const g = x => x * 2;
const t = (x, y) => x + y;
let fgt = compose(f, g, t);
fgt(1, 2); // 3 -> 6 -> 7
Применение композиции функций
Рассмотрим небольшую функцию: заглавные буквы в последнем элементе массива, скажемlog
, head
,reverse
,toUpperCase
функция существует (мы передаемcurry
можно легко написать)
Императивное письмо:
log(toUpperCase(head(reverse(arr))))
Объектно-ориентированное письмо:
arr.reverse()
.head()
.toUpperCase()
.log()
Сцепленные вызовы кажутся гораздо более приятными для глаз, но проблема в том, что функции в цепочке прототипов, которые мы можем вызывать по цепочке, ограничены, а спрос не ограничен, что ограничивает нашу логическую выразительность.
Давайте посмотрим, теперь через композицию, как мы можем реализовать предыдущую функцию:
const upperLastItem = compose(log, toUpperCase, head, reverse);
По параметрам хорошо видно, что произошло с uppderLastItem, он завершает конвейер, и все параметры, проходящие через этот конвейер, будут проходить через:reverse
-> head
-> toUpperCase
-> log
Обработка этих функций в конечном итоге дает результаты.
Самое прекрасное, что эти функции очень простые чистые функции, вы можете комбинировать и использовать их по своему желанию без каких-либо угрызений совести.
На самом деле некоторые опытные программисты видели нечто странное, это не так называемый пайплайн(pipe
) концепция? Он часто используется в командах Linux, подобноps
grep
Комбинация
ps -ef | grep nginx
Просто направление выполнения конвейера и композиции (комбинация справа налево) кажутся прямо противоположными, поэтому многие библиотеки функций (Lodash, Ramda) также предоставляют другой метод комбинации:pipe
(комбинация слева направо)
const upperLastItem = R.pipe(reverse, head, toUppderCase, log);
На самом деле концепция функционального программирования очень похожа на философию дизайна Linux:
Существует множество небольших программ с одной целью, одна программа реализует только одну функцию, а несколько программ объединяются для выполнения сложных задач.
Преимущества композиции функций
Преимущества композиции функций очевидны: она делает код простым и читабельным, в то же время, используя различные методы композиции, мы можем легко комбинировать другие часто используемые функции, чтобы сделать наш код более выразительным.
// 组合方式 1
const last = compose(head, reverse);
const shout = compose(log, toUpperCase);
const shoutLast = compose(shout, last);
// 组合方式 2
const lastUppder = compose(toUpperCase, head, reverse);
const logLastUpper = compose(log, lastUppder);
Этот процесс похож на строительство блоков LEGO.
Видно, что с помощью такого пошагового разделения и комбинирования можно реализовать масштабные программы, и все, что остается сделать, это построить достаточное количество строительных блоков (функций).
Опыт
При использовании каррирования и композиции функций следует извлечь несколько уроков:
Поместите данные для обработки в конец каррирования
Поскольку наш вывод обычно представляет собой данные, которыми нужно манипулировать, поэтому, когда мы фиксируем предыдущие параметры (мы можем назвать этонастроить), его можно превратить в единичную функцию, которую можно непосредственнофункциональная композицияИспользуйте, что является нормой, которой следуют другие функциональные языки:
const split = curry((x, str) => str.split(x));
const join = curry((x, arr) => arr.join(x));
const replaceSpaceWithComma = compose(join(','), split(' '));
const replaceCommaWithDash = compose(join('-'), split(','));
Но если некоторые функции не следуют этому соглашению, как должны быть составлены наши функции? Конечно, это не невозможно. Многие библиотеки предоставляют концепцию заполнителей. Например, Ramda предоставляет заполнитель (R.__
). Предположим, нашsplit
Пучокstr
ставить на первое место
const split = curry((str, x) => str.split(x));
const replaceSpaceWithComma = compose(join(','), split(R.__, ' '));
Функция в составе функций требует одного входа
Композиция функций имеет точку использования, то есть функция в середине должна бытьодин входДа, это легко понять, и я уже говорил об этом, потому что вывод функции один (массив состоит только из одного элемента).
Отладка для композиции функций
Что делать при обнаружении ошибки функции? Мы хотим знать, какая ссылка пошла не так, сейчас мы можем использовать вспомогательную функциюtrace
, который временно выводит результаты текущего этапа.
const trace = curry((tip, x) => { console.log(tip, x); return x; });
const lastUppder = compose(toUpperCase, head, trace('after reverse'), reverse);
Больше ссылок Рамда
Существует много существующих библиотек инструментов функционального программирования, и Lodash/fp их тоже предоставляет, но не рекомендуется использовать библиотеку функций Lodash/fp, потому что многие ее функции ставят параметры, которые необходимо обрабатывать, на первое место ( Напримерmap
), что не соответствует рекомендациям, о которых мы говорили ранее.
Рекомендуется здесьRamda, это должна быть библиотека инструментов, наиболее соответствующая функциональному программированию в настоящее время, все функции в нейcurry
, а параметры, с которыми необходимо работать, помещаются в конце. вышеупомянутыйsplit
,join
,replace
Эти основные можно использовать непосредственно в Ramda.Он предоставляет в общей сложности более 200 суперпрактичных функций.Разумное использование может значительно повысить эффективность вашего программирования (по моему личному опыту, 90% функций, которые мне нужны, предоставляются. ).
Давай сражаться
Теперь, когда вы в основном изучили все основные понятия, давайте попробуем!
Предположим, теперь у меня есть набор данных:
const data = [
{
name: 'Peter',
sex: 'M',
age: 18,
grade: 99
},
……
]
Реализуйте следующие общие функции:
- Получает все объекты моложе 18 лет и возвращает их имя и возраст.
// 对象操作(最后一个参数是对象),均会返回新的对象拷贝
R.prop('name') // 获取对象 name 字段的值
R.propEq('name', '123') // 判断对象 name 字段是否等于‘123’
R.assoc('name', '123') // 更新对象的'name'的值为'123'
R.pick(['a', 'd']); //=> {a: 1, d: 4} // 获取对象某些属性,如果对应属性不存在则不返回
R.pickAll(['a', 'd']); //=> {a: 1, d: 4} // 获取对象某些属性,如果对应属性不存在则返回`key : undefined`
// 数组操作
R.map(func) // 传统的 map 操作
R.filter(func) // 传统的 filter 操作
R.reject(func) // filter 的补集
R.take(n) // 取出数组前 n 个元素
// 比较操作
R.equals(a, b) // 判断 b 是否等于 a
R.gt(2, 1) => true // 判断第一个参数是否大于第二个参数
R.lt(2, 1) => false // 判断第一个参数是否小于第二个参数
// 排序操作
R.sort(func) // 根据某个排序函数排序
R.ascend(func) // 根据 func 转换后的值,生成一个升序比较函数
R.descend(func) // 根据 func 转换后的值,生成一个降序比较函数
// 例子:
R.sort(R.ascend(R.prop('age'))) // 根据 age 进行升序排序
// 必备函数
R.pipe() //compose 的反向,从前往后组合
R.compose() // 从后到前组合
R.curry() // 柯里化
:: String -> Object
const replace = reg => sub => str => str.replace(reg, sub);
正则表达
String
String
// replace :: Regex -> String -> String -> String
const replace = reg => sub => str => str.replace(reg, sub);
正则表达式
String
String
String
// replace :: Regex -> (String -> (String -> String))
String 数组
String
// join :: String -> [String] -> String
const join = curry((sep, arr) => arr.join(sep));
String
Number
// strLen :: String -> Number
const strLen = str => str.length();
String 数组
Number
const joinDash = join('-');
const lengthWithDash = compose(strLen, joinDash);
lengthWithDash(['abc', 'def']); // 7
map
a 数组
b 数组
// map :: (a -> b) -> [a] -> [b]
var map = curry(function(f, xs){
return xs.map(f);
});
// head :: [a] -> a
var head = function(xs){ return xs[0]; }
// Pointfree 没有出现需要操作的参数
const upperLastItem = compose(toUpperCase, head, reverse);
// 非 Pointfree 出现了需要操作的参数
const upperLastItem = arr => {
const reverseArr = arr.reverse();
const head = reverseArr[0];
return head.toUpperCase();
}
Помните, вы изучаете различные парадигмы программированияКонечная цель — сделать ваш код более эффективным и простым для понимания, при этом снизив вероятность ошибок., вы не можете просто потому, что вы изучили парадигму программирования, ваши собственные затраты на программирование будут значительно увеличены, а это значит ставить телегу впереди лошади.
Когда вы закончите писать функцию, вы можете посмотреть на нее, достаточно ли универсальна написанная вами функция? Если мне теперь нужно перейти от получения пользователей мужского пола к привлечению всех пользователей женского пола, если я хочу получить всех 10 лучших пользователей по возрасту, можно ли повторно использовать вашу функцию? ответкодовый адрес, мой ответ здесь не обязательно оптимален, просто чтобы дать представление (например,update
, можно обойтись безmap
, при использованииR.update
напрямую обновлять элементы массива).
Если вы можете написать все эти операции, не читая ответ, то вы отлично справляетесь с объединением функций!
Суммировать
Многие концепции функционального программирования были представлены ранее, чтобы обобщить преимущества функционального программирования:
- Простой код, быстрая разработка: в функциональном программировании используется большое количество комбинаций функций, высока частота повторного использования функций, а повторение кода уменьшено, поэтому программа короче, а скорость разработки выше. Пол Грэм в книге «Хакеры и художники» писал, что для одной и той же функциональной программы в крайнем случае длина кода на Лиспе может составлять одну двадцатую длины кода на Си.
- Близкий к естественному язык, легкий для понимания: функциональное программирование использует много декларативного кода, который в основном близок к естественному языку, кроме того, в нем нет запутанных циклов и вложенных суждений, поэтому его особенно легко понять.
- Простота «параллельного программирования»: Функциональное программирование не имеет побочных эффектов, поэтому в функциональном программировании нет необходимости учитывать «взаимную блокировку» (Deadlock), поэтому проблемы с «блокировкой» потоков вообще не возникает.
- меньше вероятность ошибки: Поскольку каждая функция небольшая, и одни и те же входные данные всегда могут дать один и тот же результат, тестирование простое, а функциональное программирование делает акцент на использовании чистых функций без побочных эффектов, поэтому странные ошибки случаются редко.
Поэтому, если есть одно предложение для описания функционального программирования, оно должно быть:Less code, fewer bugs
. Потому что чем меньше кода вы пишете, тем меньше у вас шансов совершить ошибку. Люди — самые ненадежные люди, и мы должны изо всех сил стараться дать работу компьютерам.
На первый взгляд может показаться, что функциональное программирование может решить все проблемы, но на самом деле функциональное программирование не является панацеей. Из-за вышеперечисленных характеристик функционального программирования ему присущи следующие недостатки:
-
представление: По сравнению с императивным программированием, функциональное программирование, безусловно, имеет недостаток в производительности, потому что оно имеет тенденцию переупаковывать метод, что приводит к снижению производительности при переключении контекста. В то же время, в нефункциональном языке, таком как JS, функциональный метод должен быть медленнее, чем непосредственное написание операторных инструкций (движок будет специально оптимизирован для многих инструкций). взять нативный метод
map
Другими словами, это примерно в 8 раз медленнее, чем чистый оператор цикла для реализации итерации. -
занятость ресурсов: В JS для достижения неизменности состояния объекта часто создаются новые объекты, поэтому на сборку мусора (Garbage Collection) оказывается больше нагрузки, чем на другие методы программирования. В некоторых случаях это может вызвать серьезные проблемы.
-
ловушка рекурсии: В функциональном программировании для достижения итерации обычно используются рекурсивные операции.Чтобы уменьшить накладные расходы на производительность рекурсии, мы часто пишем рекурсию как хвостовую рекурсию, чтобы позволить синтаксическому анализатору оптимизировать. Но, как мы все знаем, JS не поддерживает оптимизацию хвостовой рекурсии (хотя оптимизация хвостовой рекурсии является спецификацией ES6, реальных реализаций очень мало,портал)
-
...
Поэтому функциональное программирование — не очень подходящий выбор, когда требования к производительности очень строгие.
Но с другой точки зрения, сообщество инженеров-программистов никогда не останавливало так называемую битву за серебряную пулю, но настоящей серебряной пули никогда не было.Различные языки программирования возникают бесконечным потоком, различные фреймворки меняются с каждым проходом. день, и появляются различные парадигмы программирования, в результате чего реальной замены никому нет.
Истинный смысл изучения функционального программирования состоит в том, чтобы заставить вас осознать, что в дополнение к императивному программированию и объектно-ориентированному программированию существует совершенно новая идея программирования, функциональная перспектива.АннотацияМысли о проблеме. Изучение программирования надписей может значительно обогатить ваше оружие, в противном случае,Когда у тебя в руках только молоток, что ты видишь, как гвоздь.
Мы можем полностью использовать функциональное программирование как вспомогательное средство в нашей повседневной работе.Если позволяют условия, мы можем учиться на идеях функционального программирования, таких как:
- Используйте больше чистых функций, чтобы уменьшить влияние побочных эффектов.
- Используйте каррирование для повышения пригодности функций.
- Используйте стиль программирования Pointfree, чтобы уменьшить количество бессмысленных промежуточных переменных и сделать код более читабельным.
- ...
Наконец, есть старая поговорка:
Нет лучшего, есть только лучшее
Я надеюсь, что в реальных проектах вы сможете выбрать наиболее подходящую парадигму программирования в соответствии с вашими потребностями, и надеюсь, что, изучив эту новую парадигму программирования, мы сможем легче ориентироваться в бинарном мире.
Справочная статья
Энциклопедия Baidu: функциональное программирование
Энциклопедия Baidu: исследования категорий
En. Wikipedia.org/wiki/curry i…
En. Wikipedia.org/wiki/part IA…
why you should learn functional programming
Будущее за декларативным программированием
Эта статья была опубликована сКоманда внешнего интерфейса NetEase Cloud Music, Любое несанкционированное воспроизведение статьи запрещено. Мы жаждем талантов, давайПрисоединяйтесь к нам!