On 10/7/05, Daniel Amelang <daniel.amelang / gmail.com> wrote:
> I wonder how many people *do* fully understand the whole block/proc/
> lambda/Proc.new/Method/UnboundMethod/etc relationships and the use of
> next/break/redo/return/etc within each of them (not to mention the
> different styles/rules when it comes to parameter lists).
>
> (no, I'm not looking for a comprehensize explanation, just pointing
> out the complexity of the situation)
>
> Often, when a simplification is proposed, it is struck down as 'just a
> way to make it easier for newbies that are still learning the
> language' (ironically, David is oft to say such) and not really a good
> idea.
>
> After 3 years of Ruby programming, I'm still left scratching my head
> at times. Maybe I'm just a little slow.

No, I don't think it's just you. I don't think that your assessment of
David's past comments is fair or accurate. It certainly doesn't match my
recollection of what David has said about the attempts to simplify
block/etc.

I *believe* that what I'm about to say is similar to what David has
said, but it's obviously my interpretation and as such is not to be
taken as "speaking for David".

The problem that I see is that most attempts to "clean up" the block/
proc divide rely on some pretty ugly syntax and have, at least to my
eye, relied more on a theoretical unification than the practical use.
Some attempts have also attempted to eliminate Ruby's pragmatic
one-block case and yield. That is fix is needed may not be in question;
the nature of the fixes provided so far, however, seem to add to the
confusion.

Blocks and Procs are mostly interchangeable. There are some differences
in the way that parameters are handled (block parameters are assigned as
parallel variables; Proc parameters are assigned as function parameters)
and the way that they respond to break and return. There might be other
differences, but these are the major ones. I would argue that the
distinction between the two should be in the *use*, not in the
definition. Specifically:

    def test_yield
      yield 1
    end
    def test_call(&p)
      p.call(1)
    end

    z = lambda { |x, y| [ x, y ] }

    test_yield { |x, y| [ x, y ] }  # => [ 1, nil ]
    test_yield &z                   # => [ 1, nil ]

    test_call { |x, y|  [ x, y ] }  # => [ 1, nil ]
    test_call &z                    # => ArgumentError

To me, the surprising result (I know, I know) is *not* the second
test_yield, but rather the first test_call (mostly because a "puts
p.arity" indicates that the arity is 2 in both cases). In the case of
yield, I don't mind that it's treated as (almost):

    def yield(*args, &block)
      &block.call(*args)
    end

...but for #call, I would want arity respected, whether it's a simple
block or a full lambda. To make this cleaner, it might be better to have
a second method, say #yield (problematic because yield is a keyword, but
something like that) that allows for either style to be called as a
method on the Proc.

I don't have an answer for the other problem (break, return, etc.).

-austin
--
Austin Ziegler * halostatue / gmail.com
               * Alternate: austin / halostatue.ca