[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Continuations in Ruby v. Scheme



On Tue, 16 Apr 2002, Ken Anderson wrote:

> I've been reading Ruby too.  The details seem a bit more complicated.
> It looks like a block, which looks like a closure to me, can only appear at
> the end of a method call, and the block gets invoked by a yield statement
> inside the method, which is how iteration is performed.  So, maybe, this is
> just turning a generator into an iterator?

I think the right way to think about blocks in Ruby is just as a strange
syntactic sugar for functions that take at most one closure argument.
That is, in Ruby,

foo(bar) {|x| x + 1}

is essentially just sugar for

foo(bar, lambda{|x| x + 1}).

The main benefit, as far as I can tell, is that it stops you from having
the close paren at the end of a long lambda expression... , ie, instead of

foo(bar, lambda{|x|
                 ...
                 ...
                 ...})

you get

foo(bar) {|x|
           ...
           ...
           ...}

or (more sugar)

foo(bar) do |x|
 ...
 ...
 ...
end

All this fuss over punctuation will seem very strange to people used to
prefix syntax, but there you go.

As for lexical vs. dynamic scope, make no mistake, Ruby is completely
lexically scoped.  It's just that there's a distinction between methods,
which always introduce a new scope, and blocks, which, um, usually do.

To be specific: if the parameter name used in the block exists in the
enclosing scope, the existing binding is reused.  If not, a new scope is
created.  This can lead to some wacky behavior: for example, the following
works, and prints '6':

fac = lambda{|x| if x == 0 then 1 else fac.call(x - 1) * x end}
puts fac.call(3)

However, if you create a binding for 'x' earlier in the same scope, it
won't work anymore:

x = 'foo'
fac = lambda{|x| if x == 0 then 1 else fac.call(x - 1) * x end}
puts fac.call(3)

This outputs '0', because the same binding gets reused for each recursive
invocation of the block.

Of course if I turn it around to use "x * fac.call(x - 1)"  instead, it'll
still work.  Such bugs can be hard to catch, but it's easy enough to avoid
shadowing names that it doesn't happen much in practice .

HTH,
Avi