Речь идет не столько о вложенных функциях, сколько о schönfinkeling / curry.Schönfinkeling / curry назван в честь Моисея Шёнфинкеля, который разработал эту технику (после того, как Готтлоб Фреге ранее представил ее), и Хаскелла Карри, который усовершенствовал и описал ее.
Проще говоря, карри - это техника, которая позволяет превратить любойфункция n аргументов в функцию n-1 аргументов, которая возвращает функцию, которая принимает nth аргумент.И, применяя это многократно, вы можете показать, что вам никогда не нужны функции с более чем одним аргументом для моделирования функций с произвольным количеством аргументов.
Вот пример.Я могу превратить функцию, которая добавляет два числа:
function add(a, b) { return a + b; }
add(2, 3)
//=> 5
в «фабрику сумматоров», которая возвращает функции сумматора, которые при вызове выдают сумму двух чисел:
function adderFactory(a) {
return function adder(b) { return a + b; };
}
const twoAdder = adderFactory(2);
twoAdder(3)
//=> 5
или
adderFactory(2)(3)
//=> 5
Теперь вы можете подумать: «но ECMAScript поддерживает функции с более чем одним аргументом, так зачем мне имитировать их с помощью каррирования, если я могу иметь их изначально?»И ты был бы прав!По этой причине нет смысла использовать карри.
Но есть еще одна интересная вещь, которую вы можете захотеть сделать с функциями: частичное применение .«Функциональное приложение» - это просто функциональное программирование, означающее «вызов функции», поэтому «частичное приложение» означает «вызов функции только с подмножеством ее аргументов».Частичное приложение вызывает функцию только с некоторыми аргументами и создает функцию, специализированную только для этих аргументов.На языке, который поддерживает частичное применение, я мог бы сделать что-то вроде этого:
const fourAdder = add(4, ?);
Но ECMAScript не имеет частичного применения.
Однако, когда я каррирую свои функции, тогда яЯ могу сделать «частичное применение», где я могу по крайней мере предоставить только первые несколько аргументов и пропустить последние несколько аргументов.Это означает, что вам нужно подумать о том, какие аргументы с большей вероятностью будут фиксированными, а какие аргументы с большей вероятностью будут переменными, и вы должны упорядочить их по «изменчивости».
Итак, в случае функциичто вы разместили, можно создать конвертер баз, который может конвертировать только одно конкретное число из одной конкретной базы в любое количество баз.Я должен признать, что это на самом деле не очень полезно.Было бы гораздо полезнее, если бы функция была определена следующим образом:
const convertFromBaseToBase = baseFrom =>
baseTo =>
num => parseInt(num, baseFrom).toString(baseTo);
convertFromBaseToBase(2)(8)('1001')
//=> '11'
Теперь вы можете, например, создать преобразователь из восьмеричного в шестнадцатеричное значение, например:
const octalToHexadecimalConverter = convertFromBaseToBase(8)(16);
octalToHexadecimalConverter('17')
//=> "F"
Заметка!С ограничением, которое вы можете только «частично применить справа», вы также можете сделать это с необязательными параметрами с аргументами по умолчанию, например, так:
const baseToToken = Symbol('baseTo'),
numToken = Symbol('num');
function convertFromBaseToBase(baseFrom, baseTo=baseToToken, num=numToken) {
if (num === numToken) {
if (baseTo === baseToToken) {
return (baseTo, num=numToken) =>
num === numToken ?
num => parseInt(num, baseFrom).toString(baseTo) :
parseInt(num, baseFrom).toString(baseTo);
} else {
return num => parseInt(num, baseFrom).toString(baseTo);
}
} else {
return parseInt(num, baseFrom).toString(baseTo);
}
}
convertFromBaseToBase(8, 16, '17')
//=> 'F'
convertFromBaseToBase(8, 16)('17')
//=> 'F'
convertFromBaseToBase(8)(16)('17')
//=> 'F'
convertFromBaseToBase(8)(16, '17')
//=> 'F'
Но, как вы можете видеть, этоначинает становиться очень уродливым, очень быстрым.
Фрагмент в вопросе также полезен по другой причине: он предоставляет плавный интерфейс , который дает имена определенным параметрам, так что вы можетеНе путайте два числовых параметра baseFrom
и baseTo
.Однако это также можно решить несколькими другими способами.Один из них заключается в названии функции таким образом, чтобы было ясно, стоит ли сначала baseFrom
или baseTo
, т. Е. Вместо convertBase(num, baseFrom, baseTo)
назовите ее convertNumberFromBaseToBase(num, baseFrom, baseTo)
.Другой возможностью было бы использование параметра объекта, например:
function convertBase({ num, baseFrom, baseTo }) {
return parseInt(num, baseFrom).toString(baseTo);
}
convertBase({ num: '17', baseFrom: 8, baseTo: 16 })
//=> 'F'
Но даже при использовании более описательного имени функции или свободного интерфейса, тогда все равно имеет смысл изменить порядок параметров,чтобы сделать карринг и частичное применение более полезными.
Обратите также внимание, что я ничего не сказал о вложенных функциях, которые не используются для каррирования, как, например, в этом случае [Код, адаптированный с Метод рекурсивного индексирования / поиска в Ruby (с использованием среднего сравнения), возвращающий неверное значение индекса ]:
function bsearch(arr, target) {
function bsearchRec(arr, target, offset=0) {
const middleIndex = Math.floor(arr.length / 2);
if (arr[middleIndex] === target) { return offset + middleIndex; }
if (arr.length === 1) { return undefined; }
if (target > arr[middleIndex]) {
return bsearchRec(arr.slice(middleIndex+1), target, offset + middleIndex + 1);
} else if (target < arr[middleIndex]) {
return bsearchRec(arr.slice(0, middleIndex), target, offset);
}
}
return bsearchRec(arr, target);
}
bsearch([1, 3, 4, 5, 9], 5)
//=> 3
Здесь вложенная функция bsearchRec
вложена в bsearch
, потому что это частная внутренняя деталь реализации bsearch
, и никто, кроме автора bsearch
, не должен знать об этом.
Инаконец, функции - это средство, используемое в ECMAScript для инкапсуляции.В частности, функции - это то, как ECMAScript реализует объекты.Поведение объектов определяется именами и инкапсуляцией.В большинстве ОО-языков эти три вещи: поведение, инкапсуляция и сопоставление имен с поведением (так называемые «вызовы методов») предоставляются одним объектом - объектом.В ECMAScript инкапсуляция обеспечивается функциями (замыканиями), поведение обеспечивается функциями (вложенными в замыкания для совместного использования частного состояния), а сопоставление имен с поведением обеспечивается словарями, которые до смеха называются objects даже если они реализуют только одну треть того, что значит быть объектом.
Таким образом, без вложенных функций не было бы инкапсуляции в ECMAScript и, самое главное, никаких объектов!Даже модули и классы в основном являются синтаксическим сахаром поверх вложенных функций.