Local variables considered not very harmful

Local variables considered not very harmful

  • Comments 6

Like I said, that code from last time was just test code, not real production code. Though clearly it works, I'd never write code like that in a million years. I'm not thrilled with the way it uses the answer variable of the outer scope as an accumulator, and it is profoundly weird that the "do this function n times" function is a member of the Number prototype and not the Function prototype.  Worse still, we have a function that is essentially used as a composition operator that only makes sense to use on functions that have side effects and no arguments or returns. That's a kind of crazy way to do composition operations.

I'd probably do something more like this:

First, define a self-composition factory on the Function prototype, so that it is of general use on all functions of one argument, not just functions of no arguments and no returns that have side effects. What we'll do is make a new member function for every function object that returns a new function which is equivalent to calling the input function n times:

Function.prototype.times = function(n) {
  var f = this;
  return function (x) {
    while (n > 0) {
      x = f(x);
      --n;
    }
    return x;
  };
}

Obviously we don't need to compose functions of more than one argument because we want to feed the single output back into the input.  Now we compose the function "multiply something by x" with itself n times. That gives us the function "multiply something by x, n times".  Pass 1 to that function and we have a handy raiseTo function:

Number.prototype.raiseTo = function(power) {
  var x = this;
  return function(y) {
   
return x * y
  }.times(power)(1);
}

No fuss, no muss, no accumulators, no side effects, no non-number-related functions on the number prototype. I like it!

Heck, if we wanted to get really crazy then we could eliminate the local variables entirely by introducing another function constructor in-line:

Function.prototype.times = function(n) {
  return function(f) { 
    return function (x) {
      while (n > 0) {
        x = f(x);
        --n;
      }
      return x;
    }
  }(this);
}

Number.prototype.raiseTo = function(power) {
  return function(x) {
    return function(y) {
      return x * y
    }
  }(this).times(power)(1);
}

Local variables are actually an unnecessary syntactic sugar in Jscript.  We can do everything that local variables do by cleverly nesting function scopes. However, it becomes somewhat less easy to read, so I would stick to using local variables if I were you!

  • I do not know why Eric chose to make times() and raiseto() member functions. Looks like classic Smalltalk doctrine: all methods are member methods, even if insane. I like to make sure my member methods either
    a) change the object
    b) or simplify the object’s interface
    times() and raiseto() do neither, so should not be member methods.

    Of course some limited OO systems demand every method must be a member method, therefore we create "library objects". Choose your favorite library object, WLOG we will call it G, and make these two methods members of G.

    G.times = function(f, n){
    while (n > 0) {
    f();
    --n;
    }
    }

    G.raiseto = function(n, power){
    var answer = 1;
    n.times(function(){ answer = n * answer;});
    return answer;
    }

    1) I would find it easier to remember which object has the times() and raiseto() methods (always G, or some other library of common methods)
    2) like the original, we have the added benefit of not having to make a object/closure to get over the single-return-value demand of Eric's Function.prototype.times() method
    3) We happen to avoid the namespace nightmare involving the magical "this" variable; that devilish scenario where "this" is not the this suggested by the lexical context.
  • Ah, you've sussed it out. The original test code was a port of a smalltalk library to JScript! Hence the everything-is-a-member-of-something approach.

    I agree that even my improved version is a bizarrely object-happy approach to the problem.
  • I don't know much about the similarity to smalltalk, but I do know that I've crashed and burned (crash-and-burned?) twice by tinkering with the prototypes of the built-in objects. Code that I'd written added what I felt were sensible additions to the Array and String objects. But testing revealed that someone else's objects were trying to use my member functions as properties.

    That person had used the [] notation to create the "properties" for their variables, and retreived them with for (var x in ...) statements. That didn't seem like the right way to do things, but the boss's code is always right, right? ;) Because of that, I didn't spend a lot of time investigating; I just tore my code out, and used static "helper" objects, as Kyle suggests. Nothing wrong with not using objects.

    Is it just this one guy that does this, or is this approach prevalent?
  • "Local variables are actually an unnecessary syntactic sugar in Jscript."

    This is a Lisp/Scheme thing too. One of the Lisp local binding constructs looks like this:

    (let ((x 3)(y 2)) (+ x y))

    That can be mapped, via a macro, into this

    ((lambda (x y) (+ x y)) 3 2)

    Where lambda defines a local function. Given this form, a suitably optimizing compiler can completely eliminate the function call overhead, leaving us back where we started, with local variables, but this time, mathematically cleaner. (Actually, in this case, I'd expect that mess to turn into the literal '5', but that's another level of optimization....)
  • Although I'm quite comfortable with a times function existing in the Number's prototype, it would seem that this new implementation places unnecessary (or at least semi-surprising) restrictions on anyone using Function.prototype.times function, since they must now adhere to a function composition signature.

    function askQuestion(question)
    {
    displayQuestion(question);
    return getUserInput();
    }

    askQuestion.times(3)("Are you sure?");

    For instance, probably will not do what the implementor would expect it to do, based on signatures alone:

    "Are you sure?"
    "<User Input 0>"
    "<User Input 1>"
  • Hm ? Why polluting Function.prototype with times() ?

        Number.prototype.raiseTo = ( function () {

            var times = function(n,f) {

                       for(var i = 0; i++ < n; f());

            } ;

            return function(n) {

                      var answer = 1, x = this;

                          times(n,function(){ answer *= x ;});

                 return answer;

             } ;

       }());

    Above can be "read over the phone" and still be understood ...

Page 1 of 1 (6 items)