Skip to content

Instantly share code, notes, and snippets.

@dherman
Created January 13, 2012 22:50
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dherman/1609202 to your computer and use it in GitHub Desktop.
Save dherman/1609202 to your computer and use it in GitHub Desktop.
using loop controls from within block lambdas
Mailbox.prototype.extractContents = function(contacts) {
let messages = this.messages;
loop:
for (let i = 0, n = messages.length; i < n; i++) {
messages[i].headers.forEach { |header|
if (header.isSpam())
continue loop; // the label is not strictly necessary here
let addresses = header.extractAddresses();
addresses.forEach { |addr|
contacts.add(addr.name, addr.email);
};
};
}
};
@bingomanatee
Copy link

Break in this pattern is just

 Return process.next(this)

Which though noisier is very explicit

@broofa
Copy link

broofa commented Jan 16, 2012

@dherman wrote:

Second, the "goto" complaint is a red herring. Any use of break, continue, or return is a kind of goto. And you can already break to a label in JS today. You just can't do it from within a nested function.

This is just saying that "every control statement is a kind goto", which I see as a strawman argument. While true, this doesn't justify the proposed use of labels any more than it justifies adding bona-fide goto support.

One thing I'm a bit unclear on is what restraints are placed on the proposed use of labels. E.g. What's to prevent the top-of-gist example from being rewritten more functionally as ...

function extractContents() {
  loop:
  ...
    addresses.forEach(inner);
  ...
}

function inner(addr) {
  ...
    continue loop;
  ...
}

... which is pretty clearly just 'goto' with a different name (and a bad idea for all the same reasons Dijkstra outlined 44 years ago.)

@dherman
Copy link
Author

dherman commented Jan 16, 2012

@broofa:

This is just saying that "every control statement is a kind goto", which I see as a strawman argument.

Oh, not at all -- it's saying that return and break (leaving out continue since it's not the same control-flow pattern as break-to-label) are semi-structured jumps that follow a prescribed protocol, as opposed to unstructured goto, and that breaking to a label is following the same protocol: terminate a computation early and return to the same point you were going to return to anyway. The only difference with break-to-label is that you get to name which computation you want to terminate early, instead of just loops and switch.

In fact, maybe that's part of what's causing people's negative reaction to break-to-label: syntactically, when you see:

L: {
   ...
   break L;
   ...
}

it kind of looks like it's saying go back to point L (i.e., the start of the block) and start over (i.e., goto). But it's not; it's saying jump out of the computation that started at L to its end (i.e., the end of the block). A goto that restarts a computation in a different state is weird and hard to understand. A goto that simply terminates a computation early is much easier to reason about and much more common. In fact, that's what exceptions do. I don't think it's controversial to say that exceptions are more structured than goto.

And you could always use exceptions instead of break-to-label. But this gets heavyweight: you have to come up with a new kind of exception, check for it when you catch it, and rethrow if it's not your special exception.

One thing I'm a bit unclear on is what restraints are placed on the proposed use of labels.

Static scope.

E.g. What's to prevent the top-of-gist example from being rewritten more functionally as ...

The fact that the loop label is not in scope inside inner.

@dherman
Copy link
Author

dherman commented Jan 16, 2012

PS Actually, continue isn't that different a pattern either; it just means break out of the block corresponding to the current iteration, whereas break breaks out of the block corresponding to the whole loop.

@bingomanatee
Copy link

"continue" is just syntactic sugar; it can easily be replaced in most contexts with "if" statements.

Here is a formal iterator. It executes each iteration as a separate event. It has break() methods, and a "continu" method.

You DO have to manually "next()" inside the worker to keep the cycle active. One of these methods -- done, next, continu, break -- have to be called or the loop becomes suspended indefinitely -- and calling more then one of them inside a single execution cycle can create ambiguous situations.

But it does show that continue and break can be implemented without requiring a language change. And that is always good.

      function Iterate(data, worker) {
        this.data = data;
        //@TODO: validate data == array, worker == function
        this._worker = worker;
    }

    Iterate.prototype = {
        next: function() {
            if (index >= this.data.length) return this.done();
            ++this.index;
            var self = this;
            return process.nextTick(function() {
                self.work();
            });
        },
        continu: function(){
            this.next();
        },
        index: 0,
        done: function(status) {
            this.status = status || 'done';
            return this.callback(null, this);
        },

        'break': function() {
            this.done('broken');
        },

        work: function() {
            this._worker.call(this);
        },

        status: 'idle',

        start: function(){
            this.status = 'working';
            this.work();
        },

        item: function(n) {
            if (arguments.length == 0) {
                n = this.index;
            }
            return this.data[n];
        }
    }

    /**
     * A use case.
     * Note - the fact that the worker function only iterates after
     * a record has been saved FORCES this to synchronize writes.
     * In a loop where this is not a priority, this.next() could be called
     * at the end of the work function.
     */

    var iterator = new Iterate(
        [
            {name: "foo", id: 3},
            {name: "bar", id: 4},
            {name: "boo", id: 13},
            {name: "far", id: 14},
            {name: "voo", id: 23},
            {name: "var", id: 24}
        ],

        function() {
            var item = this.item();
            if (item_is_bad(item)) {
                return this.continu();
            }
            if (item.id == 14) {
                return this.break(); // note - MUST manually return.
            }

            var self = this;
            item.gender = '?';
            db.save(item, function(err, saved_item) {
                if (err) {
                    self.error = err;
                    self.done('error');
                } else {
                    self.next();
                }
            });
        });

    iterator.start();

    function item_is_bad(item) {
        if (!item) {
            return true;
        }
        return item.id == 3;
    }

    var db = {
        save: function(r, cb) {
            // save the record;
            cb(null, r);
        }
    }

@isaacs
Copy link

isaacs commented Jan 16, 2012

I'm opposed to block-lambdas, but @dherman is right: the "this is a goto" argument is not a valid response to block-lambdas, and the "every control statement is a goto" is not the reason why. Every control statement is, in some way, a method for sending the execution to another point in the program (ie, a goto), but, with specific structured semantics. Gotos lack that structure, which is the source of their hazard. When people say "X is a goto", it derails the conversation into "But everything is a goto" vs "No it's not, because it has some structure", when really, we should be discussing whether the specific structure in this case is a) relevantly different from what already exists, and if so, b) whether the added power is worth the added conceptual complexity.

For me, the issue isn't that block lambdas are a goto, but rather that the structured semantics of block lambdas is hazardous. They are a goto that is passable (like a function), and which has access to the data in the scope where it's defined (like a function), but, also has control-flow effects in the context where it's defined (like a loop).

As I showed in this gist, it can lead very quickly to some very odd scenarios. If the goal is to provide breakable iteration constructs, then we can do that more safely with API today, which in my opinion obviates the need for doing that in syntax.

@dherman
Copy link
Author

dherman commented Jan 16, 2012

@isaacs: I agree that "X is a goto" is a bogus argument, but to be clear no one was arguing that block lambdas are goto. They were arguing that break-to-label is as bad as goto, because I pointed out that you could use your own labels to break out of a forEach.

Anyway I don't really understand your gist. How is it any more of an issue than a function that throws an exception?

@isaacs
Copy link

isaacs commented Jan 16, 2012

@dherman I've heard "block lambdas are a goto" as a nay argument before. (I agree with the "nay" bit of it, but not with the reasoning.) I'll respond to your comment about the other gist in the other gist's comments.

@bingomanatee
Copy link

bingomanatee commented Jan 16, 2012 via email

@grncdr
Copy link

grncdr commented Jan 17, 2012

I commented more verbosely on Isaac's gist, but in short: what about disallowing mixing of block lambda return and normal return in a single function?

@polotek
Copy link

polotek commented Jan 17, 2012

I just want to point out that this is what I mean about goto and FUD. Not that labels are true gotos, not that any form of goto is harmful. But that people will want to argue about it. We will be spending a lot of our time correcting people's misconceptions. And in the end, the consensus will be, "look just don't use labels, you don't need them if you know how to write good javascript". They are not a useful feature of javascript and they should be avoided. We've managed to do it for this long. Let's not falter now.

Yes I do realize that I'm the one that first mentioned goto. Call it a preliminary social experiment that served to fully support my hypothesis.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment