8 ms·
JavaScript Needs Blocks
- deleted 15y ago[deleted]
- sitharus 15y agoI'm confused, aren't blocks just a hack around a lack of first class functions and/or closures? The actual complaint seems to be that JavaScript's scoping rules are different to his expectations and 'this' binding is, well, we know.
- deleted 15y ago[deleted]
- raganwald 15y agoNo, blocks are not a hack around first-class functions. Let’s talk about “what we’re making first-class.” A first-class function is something we both understand: A thingy with its own variable scope, its own notion of “this,” some parameters, and some executable code that may or may not return a result. So what is a block? That’s easy in JavaScript. Here’s a block: if (foo === bar) // The block starts here v { return foo; } // The block ends here ^ Blocks are the chunks of executable code living inside of a function. They aren’t functions! They share their enclosing function’s scope. They share their enclosing function’s notion of “this”. You can’t “return” from a block, if you execute a return form a block, you return from the surrounding function. Blocks already exist in JavaScript, but at the moment they only exist for built-in keyword construct like “if” and “for." Yahuda is simply explaining why it would be valuable to create a way to pass blocks to functions. The blocks would continue to be associated with their enclosing function invocation, unlike passing a function. To summarize, JavaScript already has first-class functions, and it already has blocks, this proposal concerns a way to make blocks first-class. Blocks are not a hack around first-class functions, they’re something else that JavaScript already has.
- oinksoft 15y agoActually, while it's almost never used (I certainly haven't seen code using it), blocks cooperate with labels too. The following is perfectly valid: var fn = function() { nowWeDance: { dance(); } }; The block uses the same execution context and scope, of course, so it's not useful. A block is legal alone too: (function() { { return 1; } })(); // 1 I suppose for a very long switch statement (god forbid), blocks could be useful: switch (x) { case 1: { // do stuff break; } case 2: { // do stuff break; } } But I wouldn't favor it because it makes the break seem implicit to the reader when it in fact is not.
- deleted 15y ago[deleted]
- evanphx 15y agoYou can actually cause a block to return control to it's invoker (the equiv of return in JS) by using the 'next' keyword.
- gizmo 15y agoHe's not proposing to make blocks first-class! That would mean you'd be able to assign blocks to variables, and that makes little sense. Blocks would remain special non-first-class entities.
- raganwald 15y agoNo, he isn’t proposing that blocks be entirely first class. Although... Why not? foo ? bar.map { |x| x * x } : bar.map { |x| x + x } Could be: bar.map foo ? { |x| x * x } : { |x| x + x } Perhaps this is too much or too Ruby-ish: function either (pred) { return pred ? { |x| x * x } : { |x| x + x }; } bar.map(either(foo))
- gizmo 15y agoConsider the following: function map(a, cc) { r = [] a.each{|e| r.push( cc(e) ) } return r } Now `cc` is some kind of callable. It might be a function or it might be a block. Suppose `cc` is a block defined as cc = {|e| return e } Does this mean the `return` in `cc` breaks out of the outer each-loop because it's a block as well, or does the return simply do the normal thing? In the first case you need case analysis every time you write a higher order function in order to make sure the code flow makes sense. This is unacceptable. The alternative is that the "return e" in `cc` behaves like it would in a lambda function (i.e. the map function works correctly). But that means that when you assign a block to a variable you change its semantic meaning. Also completely unacceptable. I see absolutely no way to make first-order blocks work.
- masklinn 15y ago> I'm confused, aren't blocks just a hack around a lack of first class functions and/or closures? Not in general. In Ruby the case is more complex because Ruby blows, but if you take Smalltalk's blocks they implement closures (in Dolphin, they're instances of `BlockClosure`) and they're first-class functions. The way in which colloquial "blocks" differ from "lambdas" first-class functions is control flow in the semantics of return: lambdas use local returns, a `return` will break from the current dynamic scope (discarding the current stack frame), whereas blocks use non-local returns: a `return` will break from an enclosing lexical scope (generally a method and you can't nest methods). You could very well have both behaviors with the same function type, using different return operators (yehuda is full of crap when he asserts that you need both lambdas and blocks by the way, even more so since Smalltalk did not have lambdas: Smalltalk has methods and blocks, methods are not lambdas as they can't be anonymous or nested, they're a top-level construct of the class) just as you can already emulate non-local returns via exceptions today (that's basically what a non-local return is)
- sitharus 15y agoThanks for the succinct explanation. I can see why that'd be useful for some constructs.
- masklinn 15y ago> I can see why that'd be useful for some constructs. It's most useful in getting rid of "C-style blocks" constructs (by which I mean non first-class blocks), which Smalltalk did quite beautifully.
- wycats 15y agoJust to clarify, here's my original comment: In order to have a language with return (and possibly super and other similar keywords) that satisfies the correspondence principle, the language must, like Ruby and Smalltalk before it, have a function lambda and a block lambda. In the context of my article, "function lambda" means function in JavaScript and method in Ruby and Smalltalk. My argument is that you need a construct you can return from, and a separate construct (a "block") that can be nested in that construct.
- snprbob86 15y agoSorta... If they are a hack around anything, it would be the lack of the intersection between first class lambas, closures, continuations, dynamic scoping, and macros. In a Lisp-like language with all of those features, you could define arbitrary non-local return points by defining a macro which uses dynamic-scoping to introduce a `return` function which captures the current continuation. One argument would be a lambda/closure that would be evaluated in this new scope: capable of calling `return`.
- mattdeboard 15y agoDoesn't most of this apply to Python anonymous functions?
- endlessvoid94 15y agoTechnically python doesn't have closures, which is what he's talking about here. Python has lambdas, which are a sort of crippled version of anonymous functions. http://mail.python.org/pipermail/python-3000/2006-November/004395.html http://mail.python.org/pipermail/python-3000/2006-November/0... EDIT: of course the replies are correct. i suppose it was unclear what the parent meant by "anonymous functions". if he meant lambdas, then they are indeed closures but without the same capabilities as normal python functions. their capabilities are unrelated to the fact that they're anonymous or closures.
- oinksoft 15y agoPython has full-featured closures (they must be named functions). But like you said, lambda is the neutered, anonymous version of these.
- hetman 15y agoPython 3 has full featured closures. In Python 2 it is not possible to alter what a variable references. It's still possible to alter the contents of the object being referenced leading to the "put the value in a list" type of hackery to simulate full featured closures.
- kingkilr 15y agoNone of what you just said is particularly true. Python has closures. Lambdas are anonymous functions which support only expressions, like all functions they can form closures. Named functions also form closures and can contain any number of statements or expressions.
- spacemanaki 15y agoTechnically Python has what I like to call "Java closures" (although I admit that is not really fair). In Python the body of an anonymous function (lambda) can only be an expression and the bindings closed over by a named inner function are immutable. You can get around the second limitation with a single element list and this is the same way you get around Java's limitation that a local variable referenced by an anonymous inner class be final.
- oinksoft 15y agoI can't take Katz' suggestions for JavaScript seriously. He desperately wants JS to be his blub (ruby) and doesn't seriously think about how to accomplish his goals with what the language provides. > There are two very common problems here. First, this has changed contexts. We can fix this by allowing a binding as a second parameter, but it means that we need to make sure that every time we refactor to a lambda we make sure to accept a binding parameter and pass it in. The var self = this pattern emerged in JavaScript primarily because of the lack of correspondence. Or, you could use Function.prototype.bind where it's needed. Only functions where an execution context is frequently provided should accept an execution context (as sugar, basically), and it's generally better to assume prudent use of bind(). But I guess I'm crazy. He then proposes a wholesale change to function semantics in the language. There are no "acrobatics" performed if you want to return a value passed to forEach(). You just enclose a variable and assign from within the callback. Any experienced JS dev will tell you that the disadvantage of forEach() and relatives is not that you can't easily return, but rather, that you can't easily break. However, proponents of a functional style would argue that you should be filtering your list first so that you only have to deal with interesting values, and so that there is no need to break: If you need `break`, use for ()! I mean, come on, this is the same guy who wrote a reopenClass function in Ember.js -- for a language with plainly open prototypes. This is nothing but a post glorifying Ruby and bashing JS for not being Ruby. There are plenty of valid nits to pick with JS, but being unlike Ruby is not one of them.
- wycats 15y agoHe then proposes a wholesale change to function semantics in the language. Actually, I linked to a proposal by Brendan Eich, the creator of JavaScript. I mean, come on, this is the same guy who wrote a reopenClass function in Ember.js JavaScript's open prototypes suffer from the inability to define a number of new properties at once using an object literal. reopen (not reopenClass), provides that functionality. This is nothing but a post glorifying Ruby and bashing JS for not being Ruby Nope. It's a post glorifying the correspondence principle, which Smalltalk and Lisp had before Ruby, and arguing that JS would be better with it.
- oinksoft 15y ago
- cloudhead 15y agoYou forgot haskell and erlang, they need blocks too!
- jdale27 15y agoBy the way, Google seems to think that it's called Tennent's Correspondence Principle, not Tennet's. Might be helpful to those who, like me, hadn't heard of it.
- wycats 15y agoFixed in the original post. Thanks for letting me know about the typo!
- bodhi 15y agoIt's described briefly in R. D. Tennent's Book Principles of Programming Languages, but it doesn't really go into much depth about the ramifications. Did Tennent write a paper with more discussion of the principle? (I got a hold of a copy after I ran into the topic previously: http://blog.marcchung.com/2009/02/18/how-closures-behave-in-ruby.html http://blog.marcchung.com/2009/02/18/how-closures-behave-in-... )
- dahlia 15y agoNo. What Katz really needs is not blocks, but RAII. See Python’s with statement or C#’s using statement.
- LeafStorm 15y agoYou can do RAII using `with` or `using` blocks, but you can't control execution with those like you can with blocks - the body of the `with` or `using` statement will always execute once. You can't prevent it from executing (unless you throw an exception), and you can't re-execute it with different values in the `as` clause.
- MartinMond 15y agoWhat advantage would we get by only having RAII over full block syntax? (Not sure, you can do using(some-other-method(file)) in C#?)
- ricardobeat 15y agoWhy do Tennent's Correspondence and Abstraction Principles matter? That's missing.
- tomdale 15y agoFrom the article: This is also known as the principle of abstraction, because it means that it is easy to refactor common code into methods that take a block. I can attest that quickly refactoring code in Ruby is much more straightforward than in JavaScript. In practice, a small reduction in cognitive load means that it's easy to just do it now, instead of putting it off (to the point it never gets done).
- amasad 15y ago+1 For some kind of keyword for Return-from (Although could be emulated with exceptions). -1 For adding block syntax.
- MartinMond 15y agoWhy? Adding a new keyword also means adding new syntax and also will break stuff, e.g. var new-return-from-keyword = "test";
- amasad 15y agoIts not about "breaking stuff". Its about keeping the language as small as possible. Part of JS's success is being small as a language. [EDIT] For example you could teach functions to beginner programmers once. And could then easily introduce lambdas/callbacks/generators(And maybe block lambdas) without introducing much new syntax.
- dextorious 15y ago1) who said that it has to be added as a keyword? 2) It's trivial to fix it even in that case. 3) A well chosen keyword will break like 0.0000000000000000001% of the web. Big deal.
- munificent 15y ago> In order to have a language with return (and possibly super and other similar keywords) that satisfies the correspondence principle, the language must, like Ruby and Smalltalk before it, have a function lambda and a block lambda. Keywords like return always return from the function lambda, even inside of block lambdas nested inside. In case you want to get your google/wikipedia on, what Katz is talking about here is a "non-local return". Normal returns return from the immediately enclosing (i.e. local) lambda/fn/block/thing. Non-local ones unwind past multiple enclosing functions to cause an outer one to return. In Smalltalk, the distinction is between methods and blocks. A return expression always returns from the enclosing method and will unwind past any blocks that the return expression is contained in. In Common Lisp, I believe you name functions and then indicate the name of the enclosing function that you want to return from by doing `(return-from <fn name> 3)`. I thought non-local returns were a bit impure and kind of a weird language novelty for a while. Recently, I added them to a hobby language of mine (Finch) and then wrote some code that uses them. Holy crap are they awesome. Being able to early return is, I think, one of the things that's really handy about imperative languages. I use it all the time in my code. But that cripples your ability to define your own flow-control-like structures that take functions. For example, here's some code that sees if an array contains a given item using a for loop: function contains(array, seek) { for (var i = 0; i < array.length; i++) { if (array[i] == seek) return true; } return false; } Let's say you don't like doing an explicit C-style loop and you want to make your own forEach() control-flow-like function: function forEach(array, callback) { for (var i = 0; i < array.length; i++) { callback(array[i]); } } Now we try to refactor `contains` to use it: function contains(array, seek) { forEach(array, function(item) { if (item == seek) return true; }); return false; } Crap, that doesn't work. There's no easy way to make `forEach()` stop early unless you go out of your way to make it support that by having the callback return some magic sentinel value. Or you do something hideous like throw a "return" exception. Non-local returns solve this neatly and end up being pretty delightful to use in practice.
- ricardobeat 15y agoThis problem doesn't exist: // returns true/false array.some(function(item){ item === seek }) Your "magic sentinel value" would be a Boolean, which is exactly how `some` is implemented: function forEach(array, callback) { for (var i = 0; i < array.length; i++) { if (callback(array[i]) === true) break; } }
- jason_slack 15y agoWeird question, can one download the 'source' to JavaScript, compile and therefore built on top of it? I guess I have never thought about it, it is free, is it open too?
- ZitchDog 15y agov8: http://code.google.com/p/v8/ http://code.google.com/p/v8/ TraceMonkey: http://hg.mozilla.org/tracemonkey http://hg.mozilla.org/tracemonkey rhino: http://www.mozilla.org/rhino/ http://www.mozilla.org/rhino/ IE: n/a
- BrendanEich 15y agoknowtheory was right, follow the MDN SpiderMonkey links to http://hg.mozilla.org/mozilla-central/js/src http://hg.mozilla.org/mozilla-central/js/src -- beware that http://hg.mozilla.org/tracemonkey http://hg.mozilla.org/tracemonkey is an inactive repo.
- knowtheory 15y agoWell, there are a lot of different implementations of Javascript. You can absolutely download the source to Google's v8 javascript engine (http://code.google.com/p/v8/ http://code.google.com/p/v8/ ), or the implementations that Mozilla has built over the years (https://developer.mozilla.org/en/SpiderMonkey https://developer.mozilla.org/en/SpiderMonkey and http://www.mozilla.org/rhino/ http://www.mozilla.org/rhino/ ). EDIT: Yeah check the comment below regarding tracemonkey rather than spidermonkey. It should also be noted that Javascript is a specification first and foremost (unlike Ruby which is defined primarily by its reference implementation). It's worth reading through the standard: http://www.ecma-international.org/publications/standards/Ecma-262.htm http://www.ecma-international.org/publications/standards/Ecm...
- edsrzf 15y agoInterestingly, there are places where even Ruby doesn't follow Tennent's Correspondence Principle: the next and break keywords. They always act on the innermost block.
- Volpe 15y agoDoesn't Coffeescript 'solve' this problem? (quotes because of the coffeescript vs javascript debate) The binding of this is solved with => (fat arrow), and the returning value is resolved with the fact that both the inner function and outer function will return their result (I think? :\) and thus the refactoring is essentially equivalent to the ruby version? Anyone else read this as "Ruby guy wants javascript to be more like ruby." ?
- wslh 15y agoJavaScript needs fibers and futures, asynchronous programming is incomplete without that. Happily Node supports that with https://github.com/laverdet/node-fibers/ https://github.com/laverdet/node-fibers/
- ericbb 15y agoAlternate formulation with hypothetical shift/reset (delimited continuation support) and blocks that return the same way functions do: mtime: function () { return reset { var mtime = shift (succeed) { this.withFile ({ |f| var mtime = this.mtimeFor (f); if (mtime < new Date () - 1000) { return "too old"; } return succeed (mtime); }); }; sys.print (mtime); return "young enough"; }; }, The succeed function is a first class, indefinite-extent function equivalent to: function (mtime) { sys.print (mtime); return "young enough"; } It's the computation within the reset block that comes after the shift form is evaluated. Calling it after returning from the method would not raise an exception. Edit: Keep blocks with 'this' inheritance (I had originally used a standard closure).
- BrendanEich 15y agoDelimited continuations are not gonna happen. See http://wiki.ecmascript.org/doku.php?id=strawman:shallow_continuations http://wiki.ecmascript.org/doku.php?id=strawman:shallow_cont..., http://calculist.blogspot.com/2010/04/single-frame-continuations-for-harmony.html http://calculist.blogspot.com/2010/04/single-frame-continuat..., and http://calculist.org/blog/2011/12/14/why-coroutines-wont-work-on-the-web/ http://calculist.org/blog/2011/12/14/why-coroutines-wont-wor....
- ericbb 15y agoThanks for the links. It's neat to see such things being considered. My feeling is that such complicated language devices are out of place in JavaScript but it was fun to run the experiment. While I'm thinking about it... I wonder about the choice to overload/reuse the keyword 'return' in the blocks proposal. Any thought on using something else to emphasize the distinction in semantics? Edit: Nevermind. That would break the Tennent Correspondence Principle wouldn't it? Bad idea.
- n0thing2kn0 15y agoTo play devils advocate here... This specific example could be solved with implicit returns in Coffeescript: class FileInfo constructor: (@name)-> mtime: -> #implicit return @withFile (f)-> if f.time isnt 999 then "too old" withFile: (block)-> try block name: @name time: 1000 finally console.log 'finished' f = new FileInfo('name') console.log f.mtime() #finished #too old
- aufreak3 15y agoHere is another way to refactor that example in JS - // constructor for the FileInfo class FileInfo = function(filename) { function withFile(block) { var f; try { f = File.open(filename, "r"); return block(f); } finally { f.close(); } } this.mtime = function () { var mtime = withFile(File.mtime); if (mtime < new Date() - 1000) { return "too old"; } sys.print(mtime); }; this.body = function () { return withFile(function (f) { return f.read(); }); }; }; An advantage with this refactoring is that the file handle, which isn't required after getting the mtime, is closed before doing other things.
- ericbb 15y agoHere's a question about blocks: should they even have return values? I was wondering about that because (1) they are supposed to have an equivalence with a code sequence and code sequences don't have values and (2) one awkward difference between blocks and closures is that a closure can return a value early whereas a block can only (as I understand) return the last value of its body. Edit: Also, as I think about it, one could maybe argue that closures are to expressions as blocks are to statements. In which case, it seems that blocks should never be bound to values but things that invoke blocks should only be able to do so as in the Ruby 'yield' where the block is implicit. You could then avoid the whole escaping problem. Edit: And further, a thing which invokes a block should maybe belong to yet another category from closures and blocks? In a sense, such a thing is analogous to: for (...) or if (...) In other words, such a thing is half-a-statement. You have to append a block to make a whole statement. :)
- devongovett 15y agoI might be missing something, but it seems to me like @wycats examples could be done quite easily in JS with a few extra return statements and the use of Function.prototype.bind. CoffeeScript handles that quite nicely with its `=>` binding syntax and implicit returns as well if you're into that. https://gist.github.com/1593480 https://gist.github.com/1593480