Каковы преимущества или преимущества вложенных функций? - PullRequest
0 голосов
/ 03 марта 2019

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

Насколько мне известно, первый код выдает тот же результаткак второй.Так зачем кому-то прибегать к чему-то более сложному;Каковы преимущества этого метода?

convertBase(num).numFrom(from).numTo(to);

let convertBase = (num) => {
    return {
        numFrom: function (baseFrom) {
            return {
                numTo: function (baseTo) {

                }
            }
        }
    }
}
convertBase(num, from, to);

let convertBase = (num, baseFrom, baseTo) => {
    return parseInt(num, baseFrom).toString(baseTo);
}

Ответы [ 3 ]

0 голосов
/ 03 марта 2019

Основная концепция возврата функции из другой функции называется замыканием.

Эта концепция замыканий может быть применена для частичного применения и каррирования.

Вы можете прочитать о них здесь

В ней есть соответствующие примеры того, почему вложенныефункции лучше.

0 голосов
/ 03 марта 2019

Речь идет не столько о вложенных функциях, сколько о 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 и, самое главное, никаких объектов!Даже модули и классы в основном являются синтаксическим сахаром поверх вложенных функций.

0 голосов
/ 03 марта 2019

Он обеспечивает свободный интерфейс , который ясно показывает, куда и куда направляется значение.

convert(3).fromBase(16).toBase(2);

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

convertBase(3, 16, 2);

, где порядок трех целочисленных параметров не очевиден.

...