I'm porting some Python code that relies heavily on delayed evaluation. This is accomplished by via thunks. More specifically, any Python expression <expr>
for which delayed evaluation is desired gets enclosed within a Python "lambda expression", i.e. lambda:<expr>
.
AFAIK, the closest JavaScript equivalent of this is function(){return <expr>}
.
Since the code I'm working with is absolutely awash in such thunks, I'd like to make the code for them more succinct, if at all possible. The reason for this is not only to save characters (a non-negligible consideration when it comes to JS), but also to make the code more readable. To see what I mean, compare this standard JavaScript form:
function(){return fetchx()}
with
fetchx()
In the first form, the substantive information, namely the expression fetchx()
, is typographically obscured by the surrounding function(){return
...}
. In the second form1, just one (
) character is used as "delayed evaluation marker". I think this is the optimal approach2.
AFAICT, solutions to this problem would fall into the following categories:
- Using
eval
to simulate delayed evaluation.
- Some special JavaScript syntax that I don't know about, and that accomplishes what I want. (My vast ignorance of JavaScript makes this possibility look quite real to me.)
- Writing the code in some non-standard JavaScript that gets programmatically processed into correct JavaScript. (Of course, this approach will not reduce the final code's footprint, but may at least retain some gains in readability.)
- None of the above.
I'm particularly interested in hearing responses of the last three categories.
P.S.: I'm aware that the use of eval
(option 1 above) is widely deprecated in the JS world, but, FWIW, below I give a toy illustration of this option.
The idea is to define a private wrapper class whose sole purpose would be to tag plain strings as JavaScript code for delayed evaluation. A factory method with a short name (e.g. C
, for "CODE") is then used to reduce, e.g.,
function(){return fetchx()}
to
C('fetchx()')
First, definitions of the factory C
and of the helper function maybe_eval
:
var C = (function () {
function _delayed_eval(code) { this.code = code; }
_delayed_eval.prototype.val = function () { return eval(this.code) };
return function (code) { return new _delayed_eval(code) };
})();
var maybe_eval = (function () {
var _delayed_eval = C("").constructor;
return function (x) {
return x instanceof _delayed_eval ? x.val() : x;
}
})();
The following comparison between a get
function and a lazyget
function shows how the above would be used.
Both functions take three arguments: an object obj
, a key key
, and a default value, and they both should return obj[key]
if key
is present in obj
, and otherwise, the default value.
The only difference between the two functions is that the default value for lazyget
can be a thunk, and if so, it will get evaluated only if key
is not in obj
.
function get(obj, key, dflt) {
return obj.hasOwnProperty(key) ? obj[key] : dflt;
}
function lazyget(obj, key, lazydflt) {
return obj.hasOwnProperty(key) ? obj[key] : maybe_eval(lazydflt);
}
Too see these two functions in action, define:
function slow_foo() {
++slow_foo.times_called;
return "sorry for the wait!";
}
slow_foo.times_called = 0;
var someobj = {x: "quick!"};
Then, after evaluating the above, and using (e.g.) Firefox + Firebug, the following
console.log(slow_foo.times_called) // 0
console.log(get(someobj, "x", slow_foo())); // quick!
console.log(slow_foo.times_called) // 1
console.log(lazyget(someobj, "x",
C("slow_foo().toUpperCase()"))); // quick!
console.log(slow_foo.times_called) // 1
console.log(lazyget(someobj, "y",
C("slow_foo().toUpperCase()"))); // SORRY FOR THE WAIT!
console.log(slow_foo.times_called) // 2
console.log(lazyget(someobj, "y",
"slow_foo().toUpperCase()")); // slow_foo().toUpperCase()
console.log(slow_foo.times_called) // 2
prints out
0
quick!
1
quick!
1
SORRY FOR THE WAIT!
2
slow_foo().toUpperCase()
2
1...which may strike Haskell programmers as strangely familiar. :)
2There's another approach, the one used, e.g., by Mathematica, that avoids the need for delayed evaluation markers altogether. In this approach, as part of a function's definition, one can designate any one of its formal arguments for non-standard evaluation. Typographically, this approach is certainly maximally unobtrusive, but a bit too much so for my taste. Besides, it is not as flexible, IMHO, as using, e.g.,
as a delayed evaluation marker.
See Question&Answers more detail:
os