New Local Variable Scope rule

S

Shashank Date

In one of Matz's slides at RubyConf ,

(http://www.rubyist.net/~matz/slides/rc2003/mgp00012.html)

he mentions his New Local Variable Scope rule as follows:

#---------------------------------------------------------------
def foo
a = nil
ary.each do |b|
# b is block local
c = b
a = b
# a and c are local to the method
end
# a and c available here
end
#---------------------------------------------------------------

I was wondering what would happen if it was written as follows:

#---------------------------------------------------------------
def foo(&block)
a=nil;
ary.each(&block)
end;

foo(lambda {|b| a=b; c=b})
#---------------------------------------------------------------

In other words, in general, what is the advantage of having variables in
closures leak out?
-- shanko
 
R

Robert Klemme

Shashank Date said:
In one of Matz's slides at RubyConf ,

(http://www.rubyist.net/~matz/slides/rc2003/mgp00012.html)

he mentions his New Local Variable Scope rule as follows:

#---------------------------------------------------------------
def foo
a = nil
ary.each do |b|
# b is block local
c = b
a = b
# a and c are local to the method
end
# a and c available here
end
#---------------------------------------------------------------

I was wondering what would happen if it was written as follows:

#---------------------------------------------------------------
def foo(&block)
a=nil;
ary.each(&block)
end;

foo(lambda {|b| a=b; c=b})
#---------------------------------------------------------------

First of all both examples are erroneous IMHO because ary is not defined
in foo().
In other words, in general, what is the advantage of having variables in
closures leak out?

Your second example will not modify the a defined in foo() because the
binding takes place at the point of invocation, i.e. another scope. If
you do

def foo(&block)
a=nil;
ary.each(&block)
puts "a=#{a.inspect}"
end;

foo(lambda {|b| a=b; c=b})
puts "a*#{a.inspect}"

You'll see
a=nil
a*<last enum element>

Regards

robert
 
D

David Alan Black

Hi --

Robert Klemme said:
First of all both examples are erroneous IMHO because ary is not defined
in foo().

OK, OK... def ary; [1,2,3]; end :)
Your second example will not modify the a defined in foo() because the
binding takes place at the point of invocation, i.e. another scope. If
you do

I think you mean the point of creation (of the closure), rather than
invocation?
def foo(&block)
a=nil;
ary.each(&block)
puts "a=#{a.inspect}"
end;

foo(lambda {|b| a=b; c=b})
puts "a*#{a.inspect}"

You'll see
a=nil
a*<last enum element>

OK, but there's still 'c'. The new thing is the idea of a local
variable created in a block and persisting after the block exits. If
the scope that matters is where the block was created, then what
happens in this case:

def call_foo
foo(lambda{|b| c=b})
puts c # does c persist if the lambda was called?
end


David
 
T

ts

D> def call_foo
D> foo(lambda{|b| c=b})

D> puts c # does c persist if the lambda was called?
D> end

What you have written is similar to

def call_foo
a = lambda {|b| c = b}
foo(a)
puts c
end

foo(a) don't change anything and if I've well understood : this is at
compile time that ruby will make the decision (local/block-local)


Guy Decoux
 
D

David Alan Black

Hi --

ts said:
D> def call_foo
D> foo(lambda{|b| c=b})

D> puts c # does c persist if the lambda was called?
D> end

What you have written is similar to

def call_foo
a = lambda {|b| c = b}
foo(a)
puts c
end

foo(a) don't change anything and if I've well understood : this is at
compile time that ruby will make the decision (local/block-local)

OK... but what will the decision be, for 'c'? Why would it be any
different here than for:

def foo
[1].each { c = 0 }
puts c
end

In both cases, an assignment is being made to c, visible at compile
time, in a particular scope. So, in your example (and mine), c is
created at compile time in the calling scope (call_foo). Then the
lambda is called, and c is actually assigned something.

Or is there going to be a further distinction between plain lambdas
and code blocks passed to iterators? If so, I really think it's time
for a Block class. (Matz: you wanted more reasons.... :)


David
 
T

ts

D> OK... but what will the decision be, for 'c'? Why would it be any
D> different here than for:

Well, write your example like this

def call_foo
c = nil
foo(lambda{|b| c=b})
puts c
end

and you can test the result with 1.8 :)


Guy Decoux
 
R

Robert Klemme

David Alan Black said:

Maybe the call should've read "foo &a".
OK... but what will the decision be, for 'c'? Why would it be any
different here than for:

def foo
[1].each { c = 0 }
puts c
end

In both cases, an assignment is being made to c, visible at compile
time, in a particular scope. So, in your example (and mine), c is
created at compile time in the calling scope (call_foo). Then the
lambda is called, and c is actually assigned something.

Yes, of course. But there's a difference between

def foo
[1].each { c = 0 }
puts c
end

and

def foo(&b)
[1].each &b
puts c
end
foo { c = 0 }

'c' comes into existence in the lexically surrounding scope, i.e. foo in
the first case and the calling context in the second case. There is no
magic that would make the second example print something other than 'nil'
for 'c'.
Or is there going to be a further distinction between plain lambdas
and code blocks passed to iterators? If so, I really think it's time
for a Block class. (Matz: you wanted more reasons.... :)

It would certainly be intersting to hear that. Up to now I thought
'lambda' is merely a syntactical alias for 'proc' but I may be wrong here.

Regards

robert
 
T

ts

R> def foo(&b)
R> [1].each &b
R> puts c
R> end
R> foo { c = 0 }

R> 'c' comes into existence in the lexically surrounding scope, i.e. foo in
R> the first case and the calling context in the second case. There is no
R> magic that would make the second example print something other than 'nil'
R> for 'c'.

Well, it will just give

in `foo': undefined local variable or method `c'


:)


Guy Decoux
 
R

Robert Klemme

ts said:
R> def foo(&b)
R> [1].each &b
R> puts c
R> end
R> foo { c = 0 }

R> 'c' comes into existence in the lexically surrounding scope, i.e. foo in
R> the first case and the calling context in the second case. There is no
R> magic that would make the second example print something other than 'nil'
R> for 'c'.

Well, it will just give

in `foo': undefined local variable or method `c'

Exactly.

robert
 
D

David Alan Black

Hi --

ts said:
D> def call_foo
D> foo(lambda{|b| c=b})

D> puts c # does c persist if the lambda was called?
D> end

What you have written is similar to

def call_foo
a = lambda {|b| c = b}
foo(a)
puts c
end

foo(a) don't change anything and if I've well understood : this is at
compile time that ruby will make the decision (local/block-local)

I'm really answering two of your posts, sort of -- this one, and the
one where you give the 1.8 example with c=nil.

I think foo(a) does change something, in the 2.0 model. If you don't
have foo(a), then puts c will raise an error:

def call_foo
a = lambda {|b| c = b}
puts c # error
end

This is therefore not the same as doing this in 1.8:

def call_foo
c = nil
a = lambda {|b| c = b}
puts c # OK, even if lambda never executes
end


David
 
D

David Alan Black

Hi --

Robert Klemme said:
David Alan Black said:

Maybe the call should've read "foo &a".
OK... but what will the decision be, for 'c'? Why would it be any
different here than for:

def foo
[1].each { c = 0 }
puts c
end

In both cases, an assignment is being made to c, visible at compile
time, in a particular scope. So, in your example (and mine), c is
created at compile time in the calling scope (call_foo). Then the
lambda is called, and c is actually assigned something.

Yes, of course. But there's a difference between

def foo
[1].each { c = 0 }
puts c
end

and

def foo(&b)
[1].each &b
puts c
end
foo { c = 0 }

'c' comes into existence in the lexically surrounding scope, i.e. foo in
the first case and the calling context in the second case. There is no
magic that would make the second example print something other than 'nil'
for 'c'.

Right -- see my wording above ("c is created at compile time in the
calling scope", which means foo in your first example and top-level in
your second example, and call_foo in my call_foo example). I somehow
had the impression Guy was saying that in the call_foo example, no 'c'
would be created in call_foo but I now don't think he was. (That's
what I meant when I asked why one would be different from the other.)

I still find it odd what happens in that calling scope. As I
understand it, after calling foo {|b| c = b}, you may or may not have
a variable 'c', depending on the behavior of foo (i.e., whether foo
does or does not call [or yield to] your block). To me, this
particular kind of dependency between scopes is strange.
It would certainly be intersting to hear that. Up to now I thought
'lambda' is merely a syntactical alias for 'proc' but I may be wrong here.

No, you're right. ('proc' is deprecated in favor of lambda, to avoid
the similarity in name between the non-similar Proc and proc objects.)
But see ruby-core, and probably ruby-talk too in the past; there have
been discussions about the various differences between and among Proc
objects, lambdas, and code blocks. (I'm not going to tempt fate by
trying to summarize them :)


David
 
T

ts

D> def call_foo
D> a = lambda {|b| c = b}
D> puts c # error
D> end

You want to say that this will give an error

def foo(x)
end

def call_foo
foo(lambda {|b| c = b})
puts c # error
end

and this not ?

def foo(x)
x.call(24)
end

def call_foo
foo(lambda {|b| c = b})
puts c # 24
end


Guy Decoux
 
D

David Alan Black

Hi --

ts said:
D> def call_foo
D> a = lambda {|b| c = b}
D> puts c # error
D> end

You want to say that this will give an error

def foo(x)
end

def call_foo
foo(lambda {|b| c = b})
puts c # error
end

and this not ?

def foo(x)
x.call(24)
end

def call_foo
foo(lambda {|b| c = b})
puts c # 24
end

Yes, that's my interpretation. The other possibility, I think, is
that c would be nil in the first example, similar to the familiar case
of:

if false; a = 1; end
p a # nil


That may be what will happen:

l = lambda { a = 1 }
p a # nil (not error)

though I find that also to be strange behavior for a closure.

(Of course, I'm one of the few who think the way block variables work
pre-2.0 is fine, but that's another story :)


David
 
T

ts

D> That may be what will happen:

D> l = lambda { a = 1 }
D> p a # nil (not error)

Well, this is what I've trying to say

l = lambda { a = 1 } # 2.0

is similar to

a = nil
l = lambda { a = 1 } # 1.8

D> (Of course, I'm one of the few who think the way block variables work
D> pre-2.0 is fine, but that's another story :)

1) confuse some persons, which see |a, b| as a declaration rather than an
assignement

2) there are really 2 different uses for block-local variable. In an
iterator probably you'll expect the 2.0 behaviour, in a Thread (or
perhaps a Proc) probably you expect the 1.8 behaviour


Guy Decoux
 
D

David Alan Black

Hi --

ts said:
D> That may be what will happen:

D> l = lambda { a = 1 }
D> p a # nil (not error)

Well, this is what I've trying to say

l = lambda { a = 1 } # 2.0

is similar to

a = nil
l = lambda { a = 1 } # 1.8

I guess I've been trying to have it be something else because I
dislike that so much (the 2.0 version).
D> (Of course, I'm one of the few who think the way block variables work
D> pre-2.0 is fine, but that's another story :)

1) confuse some persons, which see |a, b| as a declaration rather than an
assignement

I wish they would just learn it :) But of course Matz has decided he
doesn't like it, so that's that.
2) there are really 2 different uses for block-local variable. In an
iterator probably you'll expect the 2.0 behaviour, in a Thread (or
perhaps a Proc) probably you expect the 1.8 behaviour

That's why I was asking earlier whether this is a further difference
between Procs and blocks. For example:

# 2.0
def x
pr = Proc.new { a = 1 }
l = lambda { b = 1 }
# do we have a and b defined here, or just b?
end

Or when you say 'expect', do you mean 'expect, but not receive'? :)


David
 
T

ts

D> I guess I've been trying to have it be something else because I
D> dislike that so much (the 2.0 version).

Well, if I've well understtod you still have the possibility to write

l = lambda { local {|a| a = 1} } # ugly ???

D> pr = Proc.new { a = 1 }
D> l = lambda { b = 1 }

and what do you do with this ?

Thread.new { a = 1 } # like Proc or like lambda ?
NewClass.new { a = 1 } # like Proc or like lambda ?
new_method { a = 1 } # like Proc or like lambda ?


Guy Decoux
 
D

David Alan Black

Hi --

ts said:
D> I guess I've been trying to have it be something else because I
D> dislike that so much (the 2.0 version).

Well, if I've well understtod you still have the possibility to write

l = lambda { local {|a| a = 1} } # ugly ???

I admit I'm confused by this. Is 'local' being introduced? And if
|a| is a block variable, isn't it local to the block anyway?
D> pr = Proc.new { a = 1 }
D> l = lambda { b = 1 }

and what do you do with this ?

Thread.new { a = 1 } # like Proc or like lambda ?
NewClass.new { a = 1 } # like Proc or like lambda ?
new_method { a = 1 } # like Proc or like lambda ?

I'm not sure what you mean. Do you mean what would I do if I were
designing Ruby 2.0, or what do I think Matz will have them do? I
don't know, since I still don't know the answer to the above (whether
Proc and lambda will be different in this respect). I think maybe you
think that I know, but I don't :)


David
 
T

ts

D> I admit I'm confused by this. Is 'local' being introduced? And if
D> |a| is a block variable, isn't it local to the block anyway?

[ruby-talk:63199]

D> I'm not sure what you mean. Do you mean what would I do if I were
D> designing Ruby 2.0,

yes,

D> or what do I think Matz will have them do? I
D> don't know, since I still don't know the answer to the above (whether
D> Proc and lambda will be different in this respect). I think maybe you
D> think that I know, but I don't :)

If you introduce a difference between Proc and lambda, what do you do
with the examples that I've given ?

Thread will work like Proc or like lambda ?



Guy Decoux
 
G

Gavin Sinclair

No, you're right. ('proc' is deprecated in favor of lambda, to avoid
the similarity in name between the non-similar Proc and proc objects.)
But see ruby-core, and probably ruby-talk too in the past; there have
been discussions about the various differences between and among Proc
objects, lambdas, and code blocks. (I'm not going to tempt fate by
trying to summarize them :)


I wish someone would summarize them. The inevitable difficulty of
doing so would demonstrate what to my mind is obvious: the complexity
is getting way out of hand!

I've forgotten what the motivation for splitting apart a seemingly
unified concept is.

Gavin
 
R

Robert Klemme

David Alan Black said:
Hi --



I guess I've been trying to have it be something else because I
dislike that so much (the 2.0 version).


I wish they would just learn it :) But of course Matz has decided he
doesn't like it, so that's that.


That's why I was asking earlier whether this is a further difference
between Procs and blocks. For example:

# 2.0
def x
pr = Proc.new { a = 1 }
l = lambda { b = 1 }
# do we have a and b defined here, or just b?
end

Why would we have anything defined here although both blocks are not
executed in this example? Slowly I'm loosing grips here... A summary was
definitely in order.

I used to be confused, but now I'm not sure anymore.

robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,578
Members
45,052
Latest member
LucyCarper

Latest Threads

Top