Hacker News new | past | comments | ask | show | jobs | submit login
The Use Case for Blocks (github.com/raganwald)
40 points by zephyrfalcon on April 16, 2010 | hide | past | favorite | 21 comments



Or you could use jQuery's filter() to find if there is at least one match. Even better is to use an any() function that can shortcut.

The real issue here is that each() is incorrectly used to perform a search. each() is intended to perform an operation on each member of a set, not query information about the members of the set.

This is not a use case for blocks but an example of an incorrect transformation of the imperative for loop.


You confuse jQuery's .each() with its jQuery.each() function. The first is a method on a jQuery DOM collection, and indeed you could use .filter() or .any() on a colleciton of DOM elements.

jQuery.each() operates on an arbitrary array, .filter() cannot be applied to it. I could have written my own, of course, but doing so would have simply meant wrapping a for loop in a function.

This is a crucial point. The example is NOT bad even if there was a jQuery.select() or jQuery.filter() analogue for arbitrary arrays. The point is that a for loop is a syntactic construct with certain properties, and that you cannot make a direct analogue out of functions. Assuming there was a jQuery.any() function, I could write:

        if jQuery.any(collection, function (...) {...}) return 'close';
But I am still moving the return statement out of the block of code. Ruby blocks allow me to leave it where it is.

The use case for blocks is when you want to leave the return statement where it is just like in a for loop. The answer of 'use a functional programming construct that works with pure functions' is equivalent to the old Unix answer to any question: 'How do you do X? Don't do X, do Y.'

It is informative and useful to point out that there are elegant ways of not needing a return statement inside a loop. Thanks! But I don't think it invalidates the point that this is where blocks replicate a built-in feature of keywords like 'for' and functions do not.


I think a clearer way to put the distinction is that in Ruby, blocks serve both as lambdas AND to let you define new control structures (that are thus analogous to for loops). Combining the two lets you do things that you can't do just with lambdas, and if you want to make an argument as to why blocks are flexible that's the right argument to make. For example, to wrap file access in the appropriate open/close functionality while still letting you return early via the block.

Unfortunately, it can also result in some serious confusion on the part of a programmer that wants to think of them as lambdas. Java was originally going to do something similar and have two types of lambdas in 1.7, one using => and the other using ==> to indicate the non-local return semantics, and I think it would have confused a ton of people.

If you have a filter that takes a predicate function, for example, you might naively do (excuse my incredibly-rusty-and-thus-totally-syntatically-mangled-Ruby):

some_list.filter() |x| do if (x.foo) return true else return false end

In this case, you'll end up returning from the outer function, NOT just returning a true/false value for filtering. To do that, you have to just do:

some_list.filter() |x| do if (x.foo) true else false end

And rely on the way that things you might otherwise think of as statements are expression values in Ruby. But what if my predicate function is really complicated, and I want to end the block early? Um . . . too bad. Better put that in its own function and call that function from your block.

I'm sure that in practice those aren't major issues for people used to Ruby idioms and behavior, but from someone new to Ruby they can really trip you up.


You could use grep() instead of filter(). Sorry for not including it in my first post... Granted, it still suffers the problem of lacking the shortcut, but programming new useful abstractions is part of our job anyway. The point is to make it readable.

I am skeptical that this example proved the thesis: "What is there you can't do with lambdas such that you need to add blocks?" unless the point was that you have the "benefit" of performing a goto/return out of a block and not out of a strictly scoped lambda. It seems like a step backward to me. For this example anyway, the better abstraction would be any() -- to treat the set of gestures as a queryable entity and to react to that query.

I haven't played with ruby in years, so forgive my syntax mistakes, but I do not think people would find

  i = 0
  something_or_other.each do |e|
    return i if null(e) 
    i++
  end

to be a better abstraction than

  return e.length

Come to think of it, you have another return path downstream in that function anyway (not 'close').

  return jQuery.any(collection, tester) ?
             'close' :
             'other value';

I am still open to the possible usefulness of blocks.


For me it comes down to this: If the language uses blocks for its own constructs, it ought to let you use blocks for yours. If blocks are a bad idea for your constructs, then they ought to be a bad idea for `if` and `for` and all the other constructs languages like Javascript provide that have blocks of their own.


One pattern that comes to mind:

  while (true) {
    if (testState()) return valueOfState();
  }
  moreStateChangingStuff()
as opposed to:

  var flag = false, result = null;
  loop(function() {
    if (testState()) {
      flag = true
      result = valueOfState()
    }
  })
  if (flag) return result
  moreStateChangingStuff()


I think there is a case that blocks exist to bridge the bap between the imperative and functional code. If you picked just one paradigm or the other you wouldn't need blocks, but when you try to pick-and-choose which you use where you find that you need blocks over lambdas.

Is there a significant benefit to writing this sort of in-between code, or do you think we should pick one or the other whenever possible?


Sure, this example is bad, but can you really not see the value of an abstraction based on the behavior of the kind of block of code that is attached to an if or for statement?


Ugh. I've always hated the non-local returns in Ruby blocks. They massively clutter up my mental model of a block as an anonymous function, where a return statement should return from the block's function, not the enclosing function.

It seems like it adds in a lot of potential confusion and diverges from any other closure/lambda implementation in any other language without adding much value.


It doesn't diverge from any other closure/lambda implementation in every other language. It's just like Smalltalk blocks. I had never thought about it before, but it does make Smalltalk-style control structures(normal messages with blocks as parameters) much more convenient to use. Smalltalkers need non-local return in blocks to be able to do:

foo ifFalse: [^ bar].

doSomething.

Ruby has if and unless and some other control structures with support for returns, but why not allow control structures implemented as higher-order functions to include returns, too?


It's a personal taste thing, honestly. Language features aren't free to users: as a user, you need to understand the behavior, so if there are two possible behaviors that's something else to learn.

There are two different use cases here: lambdas (which may be true closures) and user-defined control constructs. In the first case, I'd argue that you want the declaration semantics to be as close as possible to a normal function definition. You should be defining a function, so return returns from that function, and break/continue statements can't have targets outside the function. For control constructs, you want the code to appear as if its inline, so you want non-local return semantics. I.e. you want it to behave as if it's not actually in a separate function at all.

So I think those are two different constructs, and things get confusing when you conflate the two. At the very least if you want to have those two constructs, make them look different enough that users don't confuse them. Java was going to go the wrong way there on their initial closure proposal (the => versus ==> thing) and make things that look like lambdas behave like in-line statements. Ruby kind of goes the other way and makes everything look like a bunch of statements within the body of something else in contexts (like the reduce() method) where what you really want or would expect is a lambda. If I write "return x + y" in the body of my argument to reduce(), I expect that to be the value of the reducer function, not to exit the function that was calling reduce(). But at least in Ruby a block is always a block and always has the same semantics, even if those semantics seem inappropriate for many use cases.

But again, it's personal taste as to which of those, or both, you want to add to a language. In my personal opinion user-defined control statements aren't all that useful and you can fill most of the needs with language-defined control constructs. But it's a tradeoff the language designer has to make, and there's no one right path.


If you do need lambdas, Ruby does also have those. "lambda { return x + y}" does what you want it to.


Ugh. I have to say, that's one of my least favorite features of blocks.

In Clojure (which has no blocks or return statement), I would write that code as

    (defn outer []
        (first (filter close? something_or_other))
What advantage does the block+return code have over that?


I have this problem with Ruby and even moreso with Perl. It's not that I don't understand the reasoning behind $GLORIOUS_FEATURE, my question is how come I don't miss it when I have to go program in other languages? And how come even though the other language doesn't have $GLORIOUS_FEATURE it sometimes still works better in the other language?

Perl <-> Python is my canonical example; I program professionally in Perl and only as a hobby in Python, so the time spent with each language is probably about 5:1 (but still quite substantial with both, as I've been programming with Perl for a long time), so I'm actually more familiar with Perl if anything. Perl is shot through with features, syntactic nuances, and all sorts of magical features both by default and in CPAN, and I even use some of them (selectively), but if it's all so awesome how come I never miss any of it in Python? (And how come the feature I really miss is the ability to use objects as the key for hashes, which Python has and Perl does not? Yes, I know about the CPAN modules, which are hacks and not something I trust, not like Python's built-in support.)

When I look at languages, I want to look at and talk about the global effects the language has on my programming. It is far too easy to get caught up in how awesome this little feature is and how awesome that little feature is and miss what global effect it is having on your code.


Because an object does not match the definition of a hash key in Perl, viz. 'Hashes are unordered collections of scalar values indexed by their associated string key'. You can also use a number or filehandle as a hash key, but don't expect to use the keys as anything other than strings without some manipulation.

Just like with ordered hashes, using hashes with non-string keys needs programming on your part (perhaps overloading stringification for your object and providing a way to get your object back from the string) or others (CPAN). Or you could overload (or just add to) the object and provide an accessor for your value so you can set it on the object itself.


I agree. I've even noticed this with python <-> java. Python is great and has a lot of time saving features, but I haven't really noticed that my work is better or more well tested than what I do with Java. I also haven't even noticed that the work gets done any faster, and I've been doing a lot of python lately.


For starters, they don't do the same thing right? You're returning the matching something_or_other and he's returning the string 'close' if something_or_other matches.


The use case mentioned can be fulfilled by another feature ruby already has, if I'm not mistaken: first-class continuations. Simply have "return" be syntactic sugar for invocation of a contination that is implicitly created by a "def"-defined function. No need for a separate type of object, just add some syntactic sugar and hey presto.


Also, you don't need full continuations for this, just continuations with dynamic extent. (E works this way, plus it defines 'return' as syntactic sugar for calling the enclosing method's continuation.)

Since I don't know much Ruby I'm not sure if its blocks have other advantages.


raganwald, you answered "can we do away with blocks". What about "can we do away with lambdas"? Going by return semantics, Smalltalk has only blocks, not lambdas. I find that constraint bites me far less frequently than Javascript's opposite one.

Of course, there's also "can we do away with explicit returns". This is what Clamato does to be able to compile a Smalltalk-and-Ruby-like language efficiently into Javascript. You can always abuse exceptions to return early from however far up the stack when you really need to.


If you want a nonlocal return, Ruby already has a construct: throw and catch.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: