Tail Recursion optimization for JavaScript?

后端 未结 3 1233
生来不讨喜
生来不讨喜 2021-02-03 11:45

My apologies to everyone for previous versions of this being vague. Someone has decided to have pity on the new girl and help me rewrite this question - here\'s an update that

3条回答
  •  你的背包
    2021-02-03 12:03

    As I mentioned in my comment, you could always convert your program into continuation passing style and then use asynchronous function calls to achieve true tail call optimization. To drive this point home consider the following example:

    function foldl(f, a, xs) {
        if (xs.length === 0) return a;
        else return foldl(f, f(a, xs[0]), xs.slice(1));
    }
    

    Clearly this is a tail recursive function. So the first thing we need to do is convert it into continuation passing style which is really simple:

    function foldl(f, a, xs, k) {
        if (xs.length === 0) k(a);
        else foldl(f, f(a, xs[0]), xs.slice(1), k);
    }
    

    That's it. Our function is now in continuation passing style. However there is still one big problem - there's no tail call optimization. This can however be easily solved using asynchronous functions:

    function async(f, args) {
        setTimeout(function () {
            f.apply(null, args);
        }, 0);
    }
    

    Our tail call optimized foldl function can now be written as:

    function foldl(f, a, xs, k) {
        if (xs.length === 0) k(a);
        else async(foldl, [f, f(a, xs[0]), xs.slice(1), k]);
    }
    

    Now all you need to do is use it. For example if you want to find the sum of the numbers of an array:

    foldl(function (a, b) {
        return a + b;
    }, 0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function (sum) {
        alert(sum); // 55
    });
    

    Putting it all together:

    function async(f, args) {
        setTimeout(function () {
            f.apply(null, args);
        }, 0);
    }
    
    function foldl(f, a, xs, k) {
        if (xs.length === 0) k(a);
        else async(foldl, [f, f(a, xs[0]), xs.slice(1), k]);
    }
    
    foldl(function (a, b) {
        return a + b;
    }, 0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function (sum) {
        alert(sum); // 55
    });

    Of course continuation passing style is a pain to write in JavaScript. Luckily there's a really nice language called LiveScript which adds the fun back into callbacks. The same functions written in LiveScript:

    async = (f, args) ->
        setTimeout ->
            f.apply null, args
        , 0
    
    foldl = (f, a, xs, k) ->
        if xs.length == 0 then k a
        else async foldl, [f, (f a, xs.0), (xs.slice 1), k]
    
    do
        sum <- foldl (+), 0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
        alert sum
    

    Yes, it's a new language which compiles to JavaScript but it is worth learning. Especially since the backcalls (i.e. <-) allows you to write callbacks easily without nesting functions.

提交回复
热议问题