Consider this statement.
1 |
fun( alert('Bimbili'), alert('Bumbili') ); |
Besides, reminding us of desperately bad parts of JavaScript (mixed with DOM), what else happens here?
That whole call to fun()
is executed before the fun()
is called. And then the fun()
is called with the results of all the expressions executed in its call. For the above javascript interpreter does something like this:
1 2 3 4 |
var r1 = alert('Bimbili'); var r2 = alert('Bumbili'); var fun = function ( r1, r2 ) { /* whatever here ...*/ }; |
Each and every expression in that call is executed first. This is the “call expression” part that is executed first: r1, r2
. Whatever r1
and r2
happen to be. And they happen to be functions right now. Thus r1 and r2 will be executed, two alerts will popup, and the r1()
and r2()
results will be saved on the call stack, and just then fun( result_of_r1, result_of_r2 )
will be called. Completely useless but legal javascript. Just to convincingly show the issue. And the issue we will discuss next. But first a slight detour.
The LISP. Gasp!
Yes, the deep end of the pool. Stay with us. LISP is basically extremely simple but (to you) strange syntax. We shall gain the ground quickly.
This “expressions in a function call” syndrome can be especially hard-hitting if one does recursive programming. Instead of some obscure example (invented by me), here is one very well known recursion example, found in this tutorial about LISP (cond …).
The cond construct in LISP is used for branching, You know it as if/else cascades in JavaScript. LISP is more elegant. The cond
function syntax:
1 2 3 4 5 |
(cond (test1 action1) (test2 action2) ... (testn action) ) |
If the test evaluates to true, the action is evaluated. And then the whole cond branching stops there. That is from circa 1956, and still more readable than if/else cascades we got used too, in “C like” languages.
How is this helping me?
We shall discuss next how is this helping us and why. Each clause within the cond
call statement consists of pairs: a conditional test and an action to be performed. Now, instead of “running for the hills”, faced with LISP, please observe with me, this little beauty. We shall transpose this into JavaScript.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
;;; This is LISP ;;; try to prove that hotpo terminates for all integers x at least 1. ;;; If you succeed, please publish the result. ;;; hotpo stands for Half Or Triple Plus One ;;; define function hotpo with two arguments (defun hotpo (x steps) (cond ;;; if x == 1, result is steps, stop the processing ((= x 1) steps) ;;; if x is odd do the recursive call hotpo( 1 + (x *3), 1 + steps ) ((oddp x) (hotpo (+ 1 (* x 3)) (+ 1 steps))) ;;; t means true so this will be always called provided program flow comes to it ;;; do a recursive call hotpo ( x /2, 1+ steps) (t (hotpo (/ x 2) (+ 1 steps)) ) ) ) ;;; usage example (hotpo 7 0) 16 |
It looks straightforward, does it not? Everything is coded as a single (cond ...)
call. When x is 1, return the number of steps required to terminate the recursion. If x is odd, call itself recursively with “triple plus one”, and the default cond action is to call hotpo()
recursively with “half”. And that is it. Simple but serious example from computer science history.
Back to the safety of JavaScript
The task for you: write the above LISP in JavaScript
Are you ready yet with your solution to the above? You are not? That is fine. Instead of potentially patronizing you because of your initial solution, I will present my line of thinking first.
So, the task at hand is to refactor the above LISP into JavaScript. To transform this to JS, the same technique from above can be used. Let me first emulate LISP cond in javascript
The cond()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var dbj = {"dbj cond" : "(c) 2020-05 by dbj.org CC BY SA 4.0" }; /*---------------------------------------- (c) 2013 - 2020 by dbj LISP cond emulation, no of arguments has to be even */ dbj.cond = function () { var j = 0, L = arguments.length; if (0 != (L % 2)) throw "dbj.cond no of arguments has to be even"; for (; j < L; j += 2) { if ( true === arguments[j] ) return arguments[j + 1]; } return undefined ; } |
Nice simple and easy. For details please consult your older ECMA Script textbooks now.
Besides coding the above in javascript this also is the time to mix dbj.cond()
(an javascript) and recursive solution, as the hotpo()
requires. Here is that example transformed into the JS with the help of dbj.cond()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function odd(n){ return (n == parseFloat(n)) && (n % 2 == 1); } function hotpo ( x, steps ) { return dbj.cond( // test action x == 1 , function () { return steps ; }, // test action odd(x) , function () { return hotpo( 1 + ( x * 3 ), 1 + steps ); }, // test action true, function () { return hotpo ( x / 2, 1 + steps); } ) // the cond call () ; // call the returned function immediately } // usage example hotpo(7,0); // => 16 |
The key question is: Why I have done the action arguments this way?
What are we actually doing here is called “passing expressions as lambdas”. Why are we doing this? You already know why. We do not want all the expressions executed at the time of dbj.cond()
function call. Those actions as functions will be returned as functions, not executed.
In contrast to JavaScript, LISP and its cond
function too is not executing the whole cond()
call at all. It does evaluation as it meets the next thing to be evaluated. Thus it stops at the first pair that satisfies the condition and the rest is not evaluated. Hence the term “short-circuiting” coined by the LISP inventor McCarthy.
Now let us see your contribution.
Javascript is not LISP
You know now that if we would just leave expressions as arguments, they will be executed immediately and before dbj.cond()
is called. Also provoking a stack overflow, since the argument hotpo(...)
will be called, first, before going into the dbj.cond()
.
I am sure you are not in that group, but I have to report, still, in 90% of cases, people think of this as the initial and “obvious” solution to the LISP hotpo()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/* stack overflow: endless recursion expressions as arguments are executed first */ function hotpo ( x, steps ) { return dbj.cond( x == 1 , steps , /* Black hole here: hotpo() is called first */ odd(x) , hotpo( 1 + ( x * 3 ), 1 + steps ), hotpo ( x / 2, 1 + steps) )() ; // call the returned function immediately } |
To avoid that stack overflow, I had to wrap the expressions in the anonymous functions (sometimes known as “lambdas”). dbj.cond()
returns the action pair if its test value is true. Since it is a function that is returned, we simply call it, in place. As you did too.
And that is it. We have shown a simple but non-trivial real-life use case and a simple solution.
Is this the end of the story then …? No, it is not.
There is more
In a typical JS spirit, there is always yet another, usually, mind-bending variant to any solution. In this case, I do not like the shape of the above code. So, I will do the following, with a little help of (gasp!) eval()
:
1 2 3 4 5 6 7 8 9 10 |
function hotpo ( x, steps ) { // we are inside hotpo() here return eval( // eval the string returned dbj.cond( true, x == 1 , "steps", // return this string odd(x) , "hotpo( 1 + ( x * 3 ), 1 + steps )", // or this "hotpo ( x / 2, 1 + steps)" // or this )) ; } hotpo(7,0) // => 16 |
What is going on here? It this forbidden JavaScript? I beg to differ. This is now, indeed much closer to the original LISP. This works because eval ()
is executed in the context of the caller, which is hotpo()
function. eval()
immediately executes the code passed to it as simple strings. And voila. Elegant solution.
Some (LISP heads) might even like this and call it “solution with true lambdas”. Well, this might be one of those badly needed examples to prove that eval
is not always evil.
What is the point of all of this?
Point number one: You must be good at abstractions. Regardless of the languages, you happen to use. Thinking abstract is what divides you from the rest.
Point number two: It seems, at least to me, if used judiciously, all of this might be useful. I also can think of the above string arguments passing, as indeed passing “pure lambdas” in ordinary JavaScript.
The obvious good result is that no arguments as expressions are executed before needed if we apply one of the two solutions. They are simply wrapped in a string acting like simple “string lambdas” or in javascript anonymous functions.
Does this post have a limited scope of usability? It sure does. Will I use it? I sure will.
Enjoy … or dislike.