Tuesday 24 September 2024

Recursive IIFE's

I guess most of my hobbyist programming revolves around stuff with no real use other than making me think. A good example of this is that the other idea it came to my mind such an existential question like this "can we write recursive Immeditelly Invokable Function Expressions?"

In modern JavaScript we have Function Expressions and Arrow Function Expressions. As Funtion Expressions can be named, making it recursive is not a problem at all. For example:


let txt = function fn(txt, times) {
    return times <= 0 
        ? txt
        : fn(`[${txt}]`, --times);
}("aa", 3);
    
console.log(txt);
// [[[aa]]]


Arrow functions do not have a name, so they have no way to call themselves save if they have been assigned to a variable. So you have to assign it to a variable and then invoke it, and for running that you'll have to wrap both things in an additional function (an IIA(rrow)E.


txt = (() => {
    const fn = (txt, times) => times <= 0 
        ? txt
        : fn(`[${txt}]`, --times);
    return fn("aa", 3);
})();
console.log(txt);
// [[[aa]]]


That's way more verbose and unintuitive than a Function Expression, so the only reason for using it that I can think of is if that code was running inside a method and the recursive function wanted to use/trap the "this" of that method (arrow functions use the lexical "this"). That would be more convenient than the old-times "self = this;" trick.


class A {
    constructor(logger) {
        this.logger = logger;
    }

    doSomething() {
        // 2 nested arrow functions trapping the "lexical this"
        return (() => {
            const fn = (txt, times) => {
                this.logger.log("in call");
                return times <= 0 
                    ? txt
                    : fn(`[${txt}]`, --times);
            };
            return fn("aa", 3);
        })();        
    }
}

let a = new A(console);
txt = a.doSomething();
console.log(txt);
// [[[aa]]]


Lambda functions in Python are anonymous, but the Assignment Expressions (aka walrus operator) comes to rescue so we can write something so nice like this:



txt = (fn := lambda txt, times: txt 
    if times <= 0
    else fn(f"[{txt}]", times - 1)
)("aa", 3)

print(txt)
# [[[aa]]] 


So in the above code we have an expression that defines a lambda, assigns it to the 'fn' variable and retuns it. Then we immediatelly invoke the returned function. That function has trapped the 'fn' variable in its closure (it's a freevar) and it can invoke it recursivelly.

No comments:

Post a Comment