3

I have been working on a number system converter recently and found this block of nested function code which made me question the use of it.

As far as I am concerned, the first code outputs the same result as the second one. So why would someone resort to something more complicated; what are the advantages of this method?

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);
}
Özenç B.
  • 928
  • 3
  • 8
  • 25

3 Answers3

3

This is not so much about nested functions but about schönfinkeling / currying. Schönfinkeling / currying is named after Moses Schönfinkel who developed this technique (after Gottlob Frege had previously introduced it) and Haskell Curry, who perfected and described it.

In simple terms, currying is a technique that allows to turn any function of n arguments into a function of n-1 arguments which returns a function that takes the nth argument. And by applying this repeatedly, you can show that you never need functions of more than one argument, to model functions with arbitrary many arguments.

Here is an example. I can turn a function that adds two numbers:

function add(a, b) { return a + b; }

add(2, 3)
//=> 5

Into an "adder factory" that returns adder functions, which when called will then produce the sum of the two numbers:

function adderFactory(a) {
    return function adder(b) { return a + b; };
} 


const twoAdder = adderFactory(2);
twoAdder(3)
//=> 5

or

adderFactory(2)(3)
//=> 5

Now, you might think: "but ECMAScript supports functions with more than one argument, so why would I simulate them using currying, if I can have them natively?" And you would be right! It makes no sense to use currying for this reason.

But, there is another interesting thing that you might want to do with functions: partial application. "Function application" is just functional programming speak for "calling a function", so "partial application" means "calling a function with only a subset of its arguments". Partial application calls a function with only some of its arguments, and produces a function that is specialized for only those arguments. In a language that supports partial application, I could do something like this:

const fourAdder = add(4, ?);

But, ECMAScript doesn't have partial application.

However, when I curry my functions, then I can do "sort-of partial application", where I can at least only supply the first few arguments, and leave out the last few arguments. This means that you have to think about which arguments are more likely to be fixed and which arguments are more likely to be variable, and you should order them by "variability".

So, in the case of the function that you posted, it is possible to create a base converter that can only convert one specific number from one specific base into any number of bases. I must admit, that is actually not terribly useful. It would be much more useful if the function were defined like this:

const convertFromBaseToBase = baseFrom =>
    baseTo =>
        num => parseInt(num, baseFrom).toString(baseTo);

convertFromBaseToBase(2)(8)('1001')
//=> '11'

Now, you can, for example, create a converter from octal to hexadecimal like this:

const octalToHexadecimalConverter = convertFromBaseToBase(8)(16);

octalToHexadecimalConverter('17')
//=> "F"

Note! With the restriction that you can only "partially apply from the right", you could actually also do this with optional parameters with default arguments, kind of like this:

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'

But, as you can see, this starts to get real ugly, real fast.

The snippet in the question also is useful for another reason: it provides a fluent interface which gives names to the particular parameters, so that you can not confuse the two number parameters baseFrom and baseTo. This can, however, also be solved in a couple of other ways. One is by naming the function such that it is clear whether the baseFrom or the baseTo comes first, i.e. instead of convertBase(num, baseFrom, baseTo) call it convertNumberFromBaseToBase(num, baseFrom, baseTo). Another possibility would be to use an object parameter, like this:

function convertBase({ num, baseFrom, baseTo }) {
    return parseInt(num, baseFrom).toString(baseTo);
}

convertBase({ num: '17', baseFrom: 8, baseTo: 16 })
//=> 'F'

But, even when using a more descriptive function name or a fluent interface, then it would still make sense to change the order of parameters, to make currying and partial application more useful.

Note also that I haven't said anything at all about nested functions that are not used for currying, as in this case, for example [Code adapted from Ruby Recursive Indexing/Searching Method (Using Middle Comparison) Returning Incorrect Index Value]:

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

Here, the nested function bsearchRec is nested inside bsearch because it is a private internal implementation detail of bsearch, and nobody except the author of bsearch should know about it.

And lastly functions are the vehicle used in ECMAScript for encapsulation. In particular, functions are how ECMAScript implements objects. Objects have behavior identified by names, and encapsulation. In most OO languages, those three things, the behavior, the encapsulation, and the mapping of names to behavior (aka "method calls") are provided by one entity, the object. In ECMAScript, encapsulation is provided by functions (closures), behavior is provided by functions (nested inside closures to share private state), and the mapping from names to behavior is provided by dictionaries, which are confusingly called objects, even though they only implement one third of what it means to be an object.

So, without nested functions, there would be no encapsulation in ECMAScript, and most importantly, no objects! Even modules and classes are mainly syntactic sugar on top of nested functions.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • This was an incredible read, thank you! But I feel like I have to read it a few more times and maybe check other sources while I am on it because it seems like a very useful thing to grasp thoroughly. Again, thank you so much, I appreciate it! – Özenç B. Mar 04 '19 at 05:47
1

The underlying concept of returning a function from another function is called closure.

This concept of closures can be applied for partial application and currying.

You can read about them here

It has appropriate examples on why nested functions are better.

Utsav Patel
  • 2,789
  • 1
  • 16
  • 26
0

It provides a fluent interface that makes clear which value goes where.

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

is strictly better (more maintainable, more readable, less errorprone) than

convertBase(3, 16, 2);

where the order of the 3 integer parameters is not evident.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375