From: Daniel Brockman Date: 2005-07-01T02:38:58+09:00 Subject: Re: iterators and block arguments Daniel Amelang writes: > Well, let's look at what our code looks like now vs. how it > would look: Good idea. > # Current way > def meth(num) > yield(num) if block_given? > end > > meth 42 { |num| puts num } Actually, that would have to be either this meth 42 do |num| puts num end or this, meth(42) { |num| puts num } since this meth 42 { |num| puts num } is syntactically treated as this, meth (42 { |num| puts num }) which makes no sense. (You can argue that it does not make sense to treat the code as nonsense on purpose, when there is really only one reasonable interpretation. But then you have this meth 42.factorial { |num| puts num } which has two reasonable interpretations, and the one Ruby takes conflicts with the one needed for the original code to make sense.) > # Unified block/proc way > def meth(num, block=nil) > block.call(num) if block > end > > meth 42, { |num| puts num } > > Nothing too strange, except for that comma in the argument list when > you call the method, since the block is now just a regular parameter. This is actually an interesting comparison: meth(42) { |num| puts num } meth 42, { |num| puts num } I know everyone will jump the opportunity to scream that the first one is better in every way. But I don't think it's a clear-cut winner. (Except when you need to chain more calls onto it --- read on.) > So when you call 'inject' now, it'd look like this: > > sum = [1,2,3].inject 0, { |s, n| s + n } > > That comma does look a little funny. Not a big deal, though. How about this? average = [1,2,3].inject(0) { |s, n| s + n }.to_f / [1,2,3].size You'd need to write that like so, average = [1,2,3].inject(0, { |s, n| s + n }).to_f / [1,2,3].size unless we kept the old syntax as sugar. On the other hand, I don't think there would be any problem in doing so. That is, this code foo(bar) { baz } would be syntactic sugar for passing `bar' and `{ baz }' to `foo'. Without the sugar, chaining method calls onto method calls with long blocks would get pretty ugly. This cutie moomin snufkin do |a, b, c| snork.snork.snork.snork snork.snork.snork.snork snork.snork.snork.snork end.frobnicate would turn into this moomin(snufkin, do |a, b, c| snork.snork.snork.snork snork.snork.snork.snork snork.snork.snork.snork end).frobnicate (I'm assuming `do |x| y end' would be the same as `{ |x| y }'.) I personally think method calls chained onto long blocks looks bad, and I try to avoid it myself. But there are probably people who love doing it, and who would hate it if it started to look even worse. Note that this would be no problem even without the sugar --- moomin snufkin do |a, b, c| snork.snork.snork.snork snork.snork.snork.snork snork.snork.snork.snork end it would just get an extra comma: moomin snufkin, do |a, b, c| snork.snork.snork.snork snork.snork.snork.snork snork.snork.snork.snork end > Seriously, though, getting rid of the yield keyword, block_given?, the > whole & thing for converting between blocks/procs and the concept of > blocks now being just regular parameters is a big change. The result > would border on a whole different language. I agree that it is a very big change, but I wouldn't go so far as to say it would make a different language. Adam is right that Ruby has been shifting more and more towards this, to the point where Ruby 1.9 can make you think the block syntax looks like an historic quirk. > It is _conceptually_ pleasing to unify them. Does it really make our > lives easier? I would like to know this as well. The best example I can think of is this, gizmos.find({ || Gizmo.new :foo => true }) { |x| x.foo? } which would turn into this, gizmos.find { || Gizmo.new :foo => true }, { |x| x.foo? } which I think is an extremely minor improvement. And what about this syntax? def foo *args, &block ; ... end I guess that'd have to become something like this, def foo *args ; block = args.pop end which is starting to look suspiciously much like Perl. Maybe this syntax could be adopted? def foo a, b, *args, c, d; ... end That is, at least four arguments, and any extra ones in the the middle get splatted into `args'. I think it makes sense. Then we'd have def foo *args, block ; ... end I think this is an interesting discussion. It's definitely good food for thought. But, of course, we're just brainstorming. -- Daniel Brockman