for-in vs. map closures

M

Mike Austin

I was experimenting with closures and JavaScript's and Ruby's
behavior. Ruby seems to close over its arguments automatically if
using map:

[1, 2, 3].map { |n| lambda { n * n } }.map { |b| b.call() } # [1, 4,
9]

But using "for" iteration, returns a different result:

a = []
for n in [1, 2, 3] do a << lambda { n * n } end
a.map { |b| b.call() } # [9, 9, 9]

This is also the behavior in JavaScript, which frequently catches
developers off guard when attaching event handlers to elements.

Can anyone explain the difference? Is map creating a new anonymous
function every iteration?

Mike
 
J

Josh Cheek

[Note: parts of this message were removed to make it a legal post.]

I was experimenting with closures and JavaScript's and Ruby's
behavior. Ruby seems to close over its arguments automatically if
using map:

[1, 2, 3].map { |n| lambda { n * n } }.map { |b| b.call() } # [1, 4,
9]

But using "for" iteration, returns a different result:

a = []
for n in [1, 2, 3] do a << lambda { n * n } end
a.map { |b| b.call() } # [9, 9, 9]

This is also the behavior in JavaScript, which frequently catches
developers off guard when attaching event handlers to elements.

Can anyone explain the difference? Is map creating a new anonymous
function every iteration?

Mike
I don't know, it seems like very peculiar behaviour. It does create new
lambdas every time, but strangely, the objects created within the block are
shared.

a = Array.new
for s in %w(a b c)
s2 = s
a << lambda { [ s2 , s , s2.object_id ] }
end

a.map { |b| b.call() } # => [["c", "c", 2148175880], ["c", "c",
2148175880], ["c", "c", 2148175880]]
a[0].object_id # => 2148175860
a[1].object_id # => 2148175840
a[2].object_id # => 2148175820
 
D

David A. Black

Hi --

I was experimenting with closures and JavaScript's and Ruby's
behavior. Ruby seems to close over its arguments automatically if
using map:

[1, 2, 3].map { |n| lambda { n * n } }.map { |b| b.call() } # [1, 4,
9]

But using "for" iteration, returns a different result:

a = []
for n in [1, 2, 3] do a << lambda { n * n } end
a.map { |b| b.call() } # [9, 9, 9]

This is also the behavior in JavaScript, which frequently catches
developers off guard when attaching event handlers to elements.

Can anyone explain the difference? Is map creating a new anonymous
function every iteration?

Mike
I don't know, it seems like very peculiar behaviour. It does create new
lambdas every time, but strangely, the objects created within the block are
shared.

a = Array.new
for s in %w(a b c)
s2 = s
a << lambda { [ s2 , s , s2.object_id ] }
end

a.map { |b| b.call() } # => [["c", "c", 2148175880], ["c", "c",
2148175880], ["c", "c", 2148175880]]
a[0].object_id # => 2148175860
a[1].object_id # => 2148175840
a[2].object_id # => 2148175820

for doesn't create a new local scope:
defined?(b) => nil
for a in [1]; b = 2; end => [1]
b
=> 2

It's similar to if and friends in this respect. So s and s2 in your
example are the same variables every time through the loop.


David

--
David A. Black, Senior Developer, Cyrus Innovation Inc.

THE Ruby training with Black/Brown/McAnally
COMPLEAT Coming to Chicago area, June 18-19, 2010!
RUBYIST http://www.compleatrubyist.com
 
J

Jesús Gabriel y Galán

I was experimenting with closures and JavaScript's and Ruby's
behavior. =A0Ruby seems to close over its arguments automatically if
using map:

[1, 2, 3].map { |n| lambda { n * n } }.map { |b| b.call() } =A0# [1, 4,
9]

Here, n is local to the block, so the n in the lambda is different
every time. The lambda is a closure over the scope of the block, and
in that block the variable n is different in each iteration.
But using "for" iteration, returns a different result:

a =3D []
for n in [1, 2, 3] do a << lambda { n * n } end
a.map { |b| b.call() } =A0# [9, 9, 9]

Here n is the same in all iterations (it's outside the do ... end),
for defines it in the scope that is above the block, so it's the same
for all iterations. The lambda closes over that scope, but the n in
all lambdas point to the same object. So after the loop, n is 3, and
so all lambdas see 3 as the value.

Jesus.
 
J

Josh Cheek

[Note: parts of this message were removed to make it a legal post.]

Wow, those were both really good explanations. Thanks David and Jesus.
 
J

Jesús Gabriel y Galán

Wow, those were both really good explanations. Thanks David and Jesus.

In fact there was something not so clear (or just plain wrong,
depending who you ask :), in my explanation. I said:
Here n is the same in all iterations (it's outside the do ... end),
for defines it in the scope that is above the block

The fact that n is outside the do...end has nothing to do with the
rest of the explanation. As David said, for doesn't start a new scope,
so even if it were like this it wouldn't yield different results:

irb(main):008:0> a = []
=> []
irb(main):009:0> for i in [1,2] do
irb(main):010:1> value = i
irb(main):011:1> a << lambda {value * value}
irb(main):012:1> end
=> [1, 2]
irb(main):013:0> a.each {|l| puts l.call}
4
4

Even though value is inside the block, as the for keyword doesn't
create a new scope, value is defined in the outer scope. So, what's
here between the do...end is not a regular ruby block, in fact, the do
is optional:

irb(main):008:0> a = []
=> []
irb(main):009:0> for i in [1,2]
irb(main):010:1> value = i
irb(main):011:1> a << lambda {value * value}
irb(main):012:1> end
=> [1, 2]
irb(main):013:0> a.each {|l| puts l.call}
4
4


After either of those:

irb(main):020:0> defined? value
=> "local-variable"

Jesus.
 
M

Mike Austin

Wow, those were both really good explanations. Thanks David and Jesus.

In fact there was something not so clear (or just plain wrong,
depending who you ask :), in my explanation. I said:
Here n is the same in all iterations (it's outside the do ... end),
for defines it in the scope that is above the block

The fact that n is outside the do...end has nothing to do with the
rest of the explanation. As David said, for doesn't start a new scope,
so even if it were like this it wouldn't yield different results:

irb(main):008:0> a = []
=> []
irb(main):009:0> for i in [1,2] do
irb(main):010:1> value = i
irb(main):011:1> a << lambda {value * value}
irb(main):012:1> end
=> [1, 2]
irb(main):013:0> a.each {|l| puts l.call}
4
4

Even though value is inside the block, as the for keyword doesn't
create a new scope, value is defined in the outer scope. So, what's
here between the do...end is not a regular ruby block, in fact, the do
is optional:

irb(main):008:0> a = []
=> []
irb(main):009:0> for i in [1,2]
irb(main):010:1> value = i
irb(main):011:1> a << lambda {value * value}
irb(main):012:1> end
=> [1, 2]
irb(main):013:0> a.each {|l| puts l.call}
4
4

After either of those:

irb(main):020:0> defined? value
=> "local-variable"

Jesus.

So if a new context is created for every block call in map(), in
theory it should be slower than a for-in loop, correct? I'll have to
try it when I get home.

Mike
 
J

Jesús Gabriel y Galán

Wow, those were both really good explanations. Thanks David and Jesus.

In fact there was something not so clear (or just plain wrong,
depending who you ask :), in my explanation. I said:
Here n is the same in all iterations (it's outside the do ... end),
for defines it in the scope that is above the block

The fact that n is outside the do...end has nothing to do with the
rest of the explanation. As David said, for doesn't start a new scope,
so even if it were like this it wouldn't yield different results:

irb(main):008:0> a =3D []
=3D> []
irb(main):009:0> for i in [1,2] do
irb(main):010:1> value =3D i
irb(main):011:1> a << lambda {value * value}
irb(main):012:1> end
=3D> [1, 2]
irb(main):013:0> a.each {|l| puts l.call}
4
4

Even though value is inside the block, as the for keyword doesn't
create a new scope, value is defined in the outer scope. So, what's
here between the do...end is not a regular ruby block, in fact, the do
is optional:

irb(main):008:0> a =3D []
=3D> []
irb(main):009:0> for i in [1,2]
irb(main):010:1> value =3D i
irb(main):011:1> a << lambda {value * value}
irb(main):012:1> end
=3D> [1, 2]
irb(main):013:0> a.each {|l| puts l.call}
4
4

After either of those:

irb(main):020:0> defined? value
=3D> "local-variable"

Jesus.

So if a new context is created for every block call in map(), in
theory it should be slower than a for-in loop, correct? =A0I'll have to
try it when I get home.

I suppose it's slower but I think it's mainly because the block call.
Let us know if you test it.

Jesus.
 
R

Robert Dober

So if a new context is created for every block call in map(), in
theory it should be slower than a for-in loop, correct? =A0I'll have to
try it when I get home.

Mike
No not really (at least for 1.9)
http://gist.github.com/391953
here is the output
ruby -v loops.rb
ruby 1.9.1p378 (2010-01-10 revision 26273) [i686-linux]
Rehearsal ----------------------------------------
loop 1.340000 0.000000 1.340000 ( 1.508669)
each 1.340000 0.010000 1.350000 ( 1.388928)
------------------------------- total: 2.690000sec

user system total real
loop 1.380000 0.010000 1.390000 ( 1.419883)
each 1.370000 0.000000 1.370000 ( 1.417870)

ruby -v loops.rb
ruby 1.8.7 (2009-06-12 patchlevel 174) [i486-linux]
Rehearsal ----------------------------------------
loop 3.690000 0.650000 4.340000 ( 4.927702)
each 4.570000 0.770000 5.340000 ( 5.764859)
------------------------------- total: 9.680000sec

user system total real
loop 4.000000 0.630000 4.630000 ( 4.739408)
each 4.510000 0.680000 5.190000 ( 5.489487)


My na=EFve interpretation would be that the closure is here anyway only
that with for in it contains a ref to i and with each a copy. Memory
usage should be up though.

Cheers
R.

--=20
The best way to predict the future is to invent it.
-- Alan Kay
 

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,770
Messages
2,569,584
Members
45,077
Latest member
SangMoor21

Latest Threads

Top