Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is hoisting really necessary in javascript to enable mutual recursion?

In an online course, Kyle Simpson says the following code demonstrates the necessity of hoisting in javascript, because without hoisting "one of the functions would always be declared too late."

a(1)  // 39

function a(foo){
  if (foo > 20) return foo
    return b(foo+2)
}

function b(foo){
  return c(foo) + 1
}

function c(foo){
  return a(foo*2)
}

But this works just fine.

var a = function(foo){
  if (foo > 20) return foo
    return b(foo+2)
}

var b = function(foo){
  return c(foo) + 1
}

var c = function(foo){
  return a(foo*2)
}

a(1) // 39

So what's the story? Convenience and placement of invocation aside, are there any situations that require hoisting?

like image 874
rswerve Avatar asked Apr 25 '16 05:04

rswerve


Video Answer


2 Answers

The claim I've made about a non-hoisted JS being unable to support mutual recursion is just conjecture for illustration purposes. It's designed to help understand the need for the language to know about variables available in the scope(s). It's not a prescription for exact language behavior.

A language feature like hoisting -- actually hoisting doesn't exist, it's just a metaphor for variables being declared in scope environments ahead of time during compilation, before execution -- is such a fundamental characteristic that it can't easily be reasoned about when separated from the rest of the language's characteristics.

Morever, it is impossible to fully test this hypothesis in just JS. The snippet in the OP only deals with part of the equation, which is that it uses function expressions instead of function declarations to avoid function hoisting.

The language I was using to compare to for illustration is C, which for example requires function signatures to be declared in .h header files so that the compiler knows what a function looks like even if it hasn't "seen" it yet. Without it, the compiler chokes. That's a sort of manual hoisting in a sense. C does it for type checking, but one can imagine this sort of requirement existing for other reasons than that.


Another way of thinking about this is whether JS is a compiled language where everything has been discovered before it executes, or whether it is interpreted top-down in a single pass.

If JS were top-down interpreted, and it got to the definition of an a() function that referenced a b() inside it that it hadn't seen yet, that could be a problem. If that call expression was handled non-lazy, the engine couldn't figure out at that moment what the b() call would be about, because b() hadn't been processed yet. Some languages are lazy and some are non-lazy.

As is, JS is compiled first before execution, so the engine has discovered all the functions (aka "hoisting") before running any of them. JS also treats expressions as lazy, so together that explains why mutual recursion works fine.

But if JS had no hoisting and/or was not lazy, one can imagine the JS engine would be unable to handle mutual recursion because the circular reference between a() and b() would in fact mean that one of the two was always declared "too late".

That's really all I meant in the book.

like image 72
Kyle Simpson Avatar answered Oct 11 '22 19:10

Kyle Simpson


Convenience and placement of invocation aside, there are not any situations that require hoisting.

Just make sure to declare all the functions before using them.

Note: In some browser, function a(){} creates a function with name a while var a = function(){} does not (considered anonymous function). The function name is used when debugging. You could also do var b = function a(){}.

like image 4
RainingChain Avatar answered Oct 11 '22 20:10

RainingChain