Эта серия статей не предназначена для новичков во фронтенде, она требует некоторого опыта программирования и понимания таких концепций, как область действия и замыкание в JavaScript.
Комбинаторные функции
Композиция — это простой, элегантный и выразительный способ четкого моделирования поведения программного обеспечения. Процесс создания более крупных программных компонентов и функций путем объединения небольших детерминированных функций приводит к созданию программного обеспечения, которое легче организовывать, понимать, отлаживать, расширять, тестировать и обслуживать.
Для композиции, я думаю, это лучшее в функциональном программировании.СущностьОдно из мест, поэтому я не могу дождаться, чтобы взять эту концепцию и представить ее первой, потому что во всем изучении функционального программирования в основном вы сталкиваетесь с написанием кода композиционным способом, который также является изменением объекта. -ориентированный, или ключевой момент мышления структурированного программирования.
Я здесь не для того, чтобы доказывать, что композиция лучше наследования, и я не говорю, насколько хороша композиция для написания кода, надеюсь, вы это поймете после прочтения этой статьи.абстрактный код композиционным образом, который расширит ваши горизонты и даст представление о том, когда вы захотите провести рефакторинг своего кода или написать более удобный для сопровождения код.
Концепция композиции очень интуитивно понятна, она не уникальна для функционального программирования, и ее можно увидеть повсюду в нашей жизни или во фронтенд-разработке.
Например, у нас сейчас популярное SPA (одностраничное приложение), там будут компоненты концепции, концепция компонентов Зачем это нужно, ведь ее цель — позволить вамОбъедините некоторые универсальные функции или элементы, абстрагированные в повторно используемые компоненты., даже если он не универсален, при построении сложной страницы вы можете разбить ее на компоненты с простыми функциями, а затем объединить их в страницы, отвечающие вашим различным потребностям.
На самом деле композиция в нашем функциональном программировании тоже похожа, композиция функцийЭто процесс организации простых задач, которые были разложены на сложные целые.
Теперь у нас есть такое требование: дать вам строку, преобразовать строку в верхний регистр, а затем изменить порядок.
Вы можете написать так.
// 例 1.1
var str = 'function program'
// 一行代码搞定
function oneLine(str) {
var res = str.toUpperCase().split('').reverse().join('')
return res;
}
// 或者 按要求一步一步来,先转成大写,然后逆序
function multiLine(str) {
var upperStr = str.toUpperCase()
var res = upperStr.split('').reverse().join('')
return res;
}
console.log(oneLine(str)) // MARGORP NOITCNUF
console.log(multiLine(str)) // MARGORP NOITCNUF
Вы можете не видеть здесь ничего плохого, но теперь у продукта есть каприз и изменились требования.После капитализации строки каждый символ разбирается и собирается в массив.Например, 'aaa' в итоге станет [A,A,A ].
Затем в это время нам нужно изменить функцию, которую мы инкапсулировали ранее. Это модифицирует ранее инкапсулированный код и, по сути, разрушает принцип открытого-закрытого в шаблоне проектирования.
Затем, если мы напишем исходный код требования таким образом, напишите его в стиле функционального программирования.
// 例 1.2
var str = 'function program'
function stringToUpper(str) {
return str.toUpperCase()
}
function stringReverse(str) {
return str.split('').reverse().join('')
}
var toUpperAndReverse = 组合(stringReverse, stringToUpper)
var res = toUpperAndReverse(str)
Затем, когда наши потребности меняются, нам не нужно модифицировать то, что было инкапсулировано ранее.
// 例 2
var str = 'function program'
function stringToUpper(str) {
return str.toUpperCase()
}
function stringReverse(str) {
return str.split('').reverse().join('')
}
// var toUpperAndReverse = 组合(stringReverse, stringToUpper)
// var res = toUpperAndReverse(str)
function stringToArray(str) {
return str.split('')
}
var toUpperAndArray = 组合(stringToArray, stringToUpper)
toUpperAndArray(str)
Видно, что при изменении требований мы не ломали ранее инкапсулированный код, а только добавляли функции, а потом функции рекомбинировали.
Некоторые люди здесь могут сказать, что если вам нужно изменить код, вы должны изменить код, а вы не удалили предыдущий код и не нарушили принцип открытого-закрытого? Позвольте мне здесь заявить, что принцип открытого-закрытого означает, что объект программного обеспечения, такой как класс, модуль и функция, должен быть открыт для расширения и закрыт для модификации. Это для кода, который мы инкапсулируем и абстрагируем, а не для вызова логического кода. Поэтому такое написание не является нарушением принципа открытого-закрытого.
Вдруг продукт снова засветился, и я хотел изменить требования.Набрав строку с большой буквы, перевернуть ее, а затем преобразовать в массив.
Если вы не абстрагировались в соответствии с предыдущим мышлением, то в вашем уме должно быть десять тысяч травяных и грязных лошадей, но если вы абстрагируетесь, то можете вообще не паниковать.
// 例 3
var str = 'function program'
function stringToUpper(str) {
return str.toUpperCase()
}
function stringReverse(str) {
return str.split('').reverse().join('')
}
function stringToArray(str) {
return str.split('')
}
var strUpperAndReverseAndArray = 组合(stringToArray, stringReverse, stringToUpper)
strUpperAndReverseAndArray(str)
Обнаружено, что код, который вы инкапсулировали ранее, не был заменен, но была заменена комбинация функций. Можно видеть, что способ композиции на самом деле заключается в абстрагировании функций одной функции, а затем в составлении сложных функций. Этот метод не только тренирует ваши способности к абстрагированию, но и очень удобен в обслуживании.
Но я просто использовал китайские иероглифы, чтобы заменить приведенную выше комбинацию, как мы должны получить эту комбинацию? Во-первых, мы можем знать, что это функция, и параметры тоже функции, и возвращаемое значение тоже функция.
Смотрим пример 2, как объединить две функции, согласно вышеизложенному, параметры и возвращаемые значения являются функциями, далее мы можем определить базовую структуру функции следующим образом (кстати замените комбинацию на англ. compose) .
function twoFuntionCompose(fn1, fn2) {
return function() {
// code
}
}
Давайте подумаем еще раз, если мы не используем функцию компоновки, как мы можем объединить две функции в примере 2. Можем ли мы сделать то же самое для достижения цели композиции?
var res = stringReverse(stringToUpper(str))
Тогда по этой логике можно написатьtwoFuntonCompose
реализуется, т.
function twoFuntonCompose(fn1, fn2) {
return function(arg) {
return fn1(fn2(arg))
}
}
Точно так же мы можем также написать комбинированную функцию из трех функций и комбинированную функцию из четырех функций, что является не чем иным, как вложением нескольких слоев, становясь:
function multiFuntionCompose(fn1, fn2, .., fnn) {
return function(arg) {
return fnn(...(fn1(fn2(arg))))
}
}
Этот отвратительный способ, очевидно, не то, что должны делать мы, программисты, и тогда мы также можем увидеть некоторые правила, это не что иное, как принятие возвращаемого значения предыдущей функции в качестве параметра следующего возвращаемого значения.При переходе непосредственно к последней функции , просто вернись.
Так что согласно нормальному мышлению, это будет написано так.
function aCompose(...args) {
let length = args.length
let count = length - 1
let result
return function f1 (...arg1) {
result = args[count].apply(this, arg1)
if (count <= 0) {
count = length - 1
return result
}
count--
return f1.call(null, result)
}
}
Написать так проблем нет, и подчеркивание тоже так пишется, но там еще много обработки робастности, да и ядро наверное такое.
Но как функциональный энтузиаст, я стараюсь мыслить функционально, поэтому я использую reduceRight для написания следующего кода.
function compose(...args) {
return (result) => {
return args.reduceRight((result, fn) => {
return fn(result)
}, result)
}
}
Конечно, есть много способов внедрить составляющую. В этой статьеПять идей для реализации composeЕсть и другие способы реализовать дыру в мозгу.До прочтения этой статьи я не ожидал, что остальные три, но они не казались слишком полезными, но они могут расширить наше мышление.Заинтересованные студенты могут посмотреть.
Примечание: есть спецификация для передачи в функцию компоновки, во-первых, выполнение функции начинается с последнего параметра и продолжается до первого, а также есть требования к функции, передаваемой в композицию в качестве параметра, должны быть только один формальный параметр, а возвращаемое значение функции является аргументом следующей функции.
Если вам не очень нравится, как compose начинает оценку с последней функции, вы можете использовать функцию конвейера для перехода слева направо.
function pipe(...args) {
return (result) => {
return args.reduce((result, fn) => {
return fn(result)
}, result)
}
}
Реализация аналогична compose, но перебирает параметры справа налево (reduceRight) слева направо (reduce).
Вы читали раньше много статей о том, как реализовать композицию, или каррирование, частичное приложение и другие функции, но вы можете не знать, для чего это используется, и вы никогда не использовали это, поэтому вы помните и забываете, забываете и помните Опять же, после прочтения этой статьи я надеюсь, что вы сможете легко добиться этого. Мы продолжим говорить о каррировании и реализации некоторых приложений позже.
point-free
В мире функционального программирования есть такой популярный стиль программирования. Этот стиль называетсяtacit programming, также известен какpoint-free, точка представляет собой формальный параметр, что, вероятно, означает стиль программирования без формальных параметров.
// 这就是有参的,因为 word 这个形参
var snakeCase = word => word.toLowerCase().replace(/\s+/ig, '_');
// 这是 pointfree,没有任何形参
var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);
Цель функции с параметрами — получить одни данные, а цель функции pointfree — получить другую функцию.
Итак, в чем польза от pointfree? Это позволяет нам сосредоточиться на функции, проблемы с именами параметров определенно сохраняются, а код становится более лаконичным и элегантным. Следует отметить, что бесточечная функция может состоять из множества небесточечных функций, то есть большинство основных базовых функций имеют параметры Бесточечная функция отражается в расширенных функциях, состоящих из базовых функций Эти расширенные функции часто могут использоваться в качестве нашей бизнес-функции для формирования нашей реплицированной бизнес-логики путем объединения различных базовых функций.
Можно сказать, что безточечность делает наше программирование более красивым и декларативным.Этот стиль является стремлением и стандартом в функциональном программировании.Мы можем попытаться написать его как безточечную, но не использовать его чрезмерно.Чрезмерное использование неправильно.
Кроме того, видно, что базовые функции, объединенные компоновкой, имеют только один параметр, но часто параметров наших базовых функций, вероятно, больше одного.В этот раз будет использоваться волшебная функция (функция каррирования).
карри
Каррирование определяется в Википедии следующим образом:
в информатике,карри(английский: Currying), также переводится какЗаботливыйилиГалисизация, состоит в том, чтобы принять несколькопараметризфункцияпревратиться вФункция, которая принимает один аргумент (первый аргумент исходной функции), а return принимает оставшиеся аргументы иновая функция, которая возвращает результатТехнология.
В определении получаются две важные части информации:
- принимает один параметр
- Возвращаемый результат - функция
Являются ли эти две точки не требованиями к параметрам функции составления и могут ли они преобразовать функцию с несколькими параметрами в функцию, которая принимает один параметр, могут ли они решить проблему, заключающуюся в том, что базовая функция, о которой мы упоминали выше, не может использоваться, если используется несколько параметров Таким образом, становится ясно, что делает функция каррирования.
Каррирующие функции могут помочь нам лучше работать с бесточечностью и сделать наш код более красивым!
Давайте рассмотрим пример, чтобы понять каррирование:
Допустим, у вас есть продуктовый магазин, и вы хотите предоставить покупателям со скидкой 10% скидку (т. е. 10% скидку):
function discount(price, discount) {
return price * discount
}
Когда привилегированный клиент покупает товар стоимостью 500 долларов, вы даете ему скидку:
const price = discount(500, 0.10); // $50
Вы можете предвидеть, что в долгосрочной перспективе мы будем рассчитывать скидку 10% каждый день:
const price = discount(1500,0.10); // $150
const price = discount(2000,0.10); // $200
// ... 等等很多
Мы можем изменить функцию скидки, чтобы нам не приходилось каждый раз увеличивать скидку на 0,10.
// 这个就是一个柯里化函数,将本来两个参数的 discount ,转化为每次接收单个参数完成求职
function discountCurry(discount) {
return (price) => {
return price * discount;
}
}
const tenPercentDiscount = discountCurry(0.1);
Теперь мы можем просто рассчитать стоимость товаров, купленных вашими клиентами:
tenPercentDiscount(500); // $50
Точно так же некоторые Привилегированные клиенты более важны, чем некоторые Привилегированные клиенты — назовем их Суперклиентами. И мы хотим дать этим суперклиентам скидку 20%. Мы можем использовать нашу функцию каррированной скидки:
const twentyPercentDiscount = discountCurry(0.2);
Мы используем эту функцию каррированной скидки, чтобы скорректировать скидку до 0,2 (т. е. 20%) и настроить новую функцию для нашего суперклиента. Возвращаемая функция twoPercentDiscount будет использоваться для расчета скидки для нашего суперклиента:
twentyPercentDiscount(500); // 100
Я полагаю, что благодаря приведенному выше **discountCurry** у вас уже есть некоторое представление о каррировании.Эта статья посвящена применению каррирования в функциональном программировании, поэтому давайте посмотрим, как оно применяется в функциональном программировании.
Теперь у нас есть такое требование: заданную строку сначала перевернуть, потом заглавную, чтобы узнать, существует лиTAOWENG
, если да, то выведите yes, иначе выведите no.
function stringToUpper(str) {
return str.toUpperCase()
}
function stringReverse(str) {
return str.split('').reverse().join('')
}
function find(str, targetStr) {
return str.includes(targetStr)
}
function judge(is) {
console.log(is ? 'yes' : 'no')
}
Мы можем легко написать эти четыре функции.Первые две были написаны выше, а затем функция поиска также очень проста.Теперь мы хотим добиться бесточечной компоновки, но наша функция поиска должна принимать две Параметр не соответствует требованиям параметра compose. В этот раз, как и в предыдущем примере, мы каррируем функцию find, а затем комбинируем:
// 柯里化 find 函数
function findCurry(targetStr) {
return str => str.includes(targetStr)
}
const findTaoweng = findCurry('TAOWENG')
const result = compose(judge, findTaoweng, stringReverse, stringToUpper)
Посмотрите здесь, если вы видите, что каррирование очень полезно для достижения бесточечного, меньшего количества параметров, шаг за шагом для достижения нашей комбинации.
Однако каррирование указанным выше способом требует изменения ранее инкапсулированных функций, которые также уничтожаются.принцип открыто-закрыто, а для некоторых базовых функций модифицировать исходный код могут быть проблемы в других местах, поэтому надо написать функцию для ручного каррирования.
Согласно определению каррирования перед определением и двум предыдущим функциям каррирования, мы можем написать общую функцию каррирования с двумя переменными (количество параметров равно 2):
function twoCurry(fn) {
return function(firstArg) { // 第一次调用获得第一个参数
return function(secondArg) { // 第二次调用获得第二个参数
return fn(firstArg, secondArg) // 将两个参数应用到函数 fn 上
}
}
}
Таким образом, указанный выше findCurry может быть получен с помощью twoCurry:
const findCurry = twoCurry(find)
Таким образом, мы не можем изменить упакованную функцию или можем использовать каррирование, а затем выполнять композицию функций. Однако здесь мы реализуем только каррирование двоичных функций.Если у нас есть троичные и четверичные функции, нам нужно написать тройные функции каррирования и четверичные функции каррирования.На самом деле, мы можем написать общее n мета-каррирование.
function currying(fn, ...args) {
if (args.length >= fn.length) {
return fn(...args)
}
return function (...args2) {
return currying(fn, ...args, ...args2)
}
}
Здесь я использую рекурсивную идею. Когда количество полученных параметров больше или равно количеству параметров fn, это доказывает, что параметры были получены, поэтому fn выполняется напрямую. Если параметры не получены, продолжаем рекурсивно получать параметры.
Видно, что основная идея общей каррирующей функции очень проста, код также очень лаконичен, а также поддерживает возможность передачи нескольких параметров за один вызов (но это определение передачи нескольких параметров и каррирования не очень хорошо, так что можно использовать как вариант карри).
Моя цель здесь не в том, чтобы говорить о реализации каррирования, поэтому я не писал очень надежные, более мощные функции каррирования, которые можно увидеть в удивлении Ю:Каррирование функций в темах JavaScript.
Некоторые приложения
Частичное применение — это операция, которая создает меньшую функцию арности путем инициализации подмножества неизменяемых аргументов функции фиксированными значениями. Проще говоря, если есть функция с пятью аргументами, при наличии трех аргументов вы получаете функцию с одним, двумя аргументами.
Глядя на приведенное выше определение, вы можете подумать, что это очень похоже на каррирование, которое используется для сокращения длины параметров функции, поэтому, если вы понимаете каррирование, понять некоторые приложения очень просто:
function debug(type, firstArg, secondArg) {
if(type === 'log') {
console.log(firstArg, secondArg)
} else if(type === 'info') {
console.info(firstArg, secondArg)
} else if(type === 'warn') {
console.warn(firstArg, secondArg)
} else {
console.error(firstArg, secondArg)
}
}
const logDebug = 部分应用(debug, 'log')
const infoDebug = 部分应用(debug, 'info')
const warnDebug = 部分应用(debug, 'warn')
const errDebug = 部分应用(debug, 'error')
logDebug('log:', '测试部分应用')
infoDebug('info:', '测试部分应用')
warnDebug('warn:', '测试部分应用')
errDebug('error:', '测试部分应用')
debug
Метод инкапсулирует различные методы, когда мы обычно используем объект консоли для отладки.Первоначально мы должны передать три параметра.Некоторые приложенияПосле инкапсуляции нам нужно только вызывать разные методы по мере необходимости и передавать нужные параметры.
В моем примере можно подумать, что инкапсулировать таким образом ненужно, и это совершенно не снижает нагрузку, но если нам нужно не только выводить в консоль при отладке, но и сохранять отладочную информацию в БД, или сделать что-то еще, то Полезен ли этот пакет?
Потому что некоторые приложения тоже могут уменьшать параметры, он тоже имеет место, когда мы пишем функции композиции, и может быстрее передавать требуемые параметры, оставляя переданные параметры для компоновки.Вот сравнение с каррированием, потому что каррирование По определению, только одно параметр можно передать вызову функции, если параметров четыре или пять, то нужно:
function add(a, b, c, d) {
return a + b + c +d
}
// 使用柯里化方式来使 add 转化为一个一元函数
let addPreThreeCurry = currying(add)(1)(2)(3)
addPreThree(4) // 10
Этот непрерывный вызов (каррирование здесь по определению, а не вариант каррирования, который мы написали), но с частичным применением:
// 使用部分应用的方式使 add 转化为一个一元函数
const addPreThreePartial = 部分应用(add, 1, 2, 3)
addPreThree(4) // 10
Теперь, когда мы поняли роль частичного применения этой функции, давайте реализуем его, это действительно очень просто:
// 通用的部分应用函数的核心实现
function partial(fn, ...args) {
return (..._arg) => {
return fn(...args, ..._arg);
}
}
Кроме того, я не знаю, заметили ли вы, что эта часть приложения очень похожа на функцию связывания в JavaScript.Параметры, которые передаются в первый раз, сохраняются в функции через замыкание, а другие параметры передаются функции при ее повторном вызове. , но некоторым приложениям не нужно указывать это, поэтому вы также можете использовать привязку для реализации функции частичного приложения.
// 通用的部分应用函数的核心实现
function partial(fn, ...args) {
return fn.bind(null, ...args)
}
Кроме того, вы можете видеть, что каррирование на самом деле очень похоже на некоторые приложения, поэтому эти два метода легко спутать. Основное различие между ними заключается во внутреннем механизме и управлении передачей параметров:
- Каррирование генерирует вложенные унарные функции каждый раз, когда вызывается распределение. Под капотом конечный результат функции определяется этими унарными функциямишаг за шагомпроизведено. В то же время вариант карри позволяет передавать подмножество параметров одновременно. Таким образом, полный контроль над функциейКогда и как оценивать.
- Некоторые приложения привязывают (назначают) параметры функции к некоторому предустановленному значению, в результатеНовая функция с меньшим количеством параметров. Закрытие функции содержит эти назначенные параметры, которые полностью оцениваются при последующих вызовах.
Суммировать
В этой статье я хочу сосредоточиться на том, что функции можно комбинировать для удовлетворения наших потребностей, и я также представляю стиль функционального программирования: безточечное, что позволяет нам иметь передовой опыт в функциональном программировании, попробуйте написать его как безточечное form (насколько это возможно, не все), а затем вводит уменьшение параметров функции посредством каррирования или частичного применения, которое удовлетворяет требованиям к параметрам compose или pipe.
Таким образом, цель такого рода статей состоит в том, чтобы понять, как мы можем составлять функции, какПреобразование сложных функций в функции с меньшей степенью детализации и одной функцией. Это упростит поддержку нашего кода и сделает его более декларативным.
Что касается других концепций, упомянутых в этой статье: замыканий, областей видимости и других применений каррирования, я надеюсь получить более глубокое понимание в дополнительной части, и в этой статье в основном рассматривается композиция функций.
Справочная статья
- Функциональное программирование на JavaScript Бесточечное и декларативное программирование
- Understanding Currying in JavaScript
- Руководство по функциональному программированию JavaScript
Статья впервые опубликована на моем личном сайтеПерсиковый сад, а также вgithub blogнайти на.
Если вам интересно, вы также можете подписаться на мой личный аккаунт: «Front-end Taoyuan».