[1,2,3].map.with_object(3){|v|v+3} #=> 3 Is this a bug?

D

David A. Black

Hi --

At ruby 1.9.2dev (2009-07-18 trunk 24186) [i386-mswin32_90]

Ruby runs like this.
[1,2,3].map.with_object(3){|v|v+3} => 3
[1,2,3].each.with_object(3).map{|v|v+3}
=> [4, 5, 6]


I think that is better to use.
[1,2,3].map.with_object(3){|v|v+3}
=> [4, 5, 6]

Is it a bug?

I don't think so. with_object always returns its argument, or an
enumerator, as far as I know. But I'm not 100% sure I'm understanding
what part you think might be a bug.


David
 
D

David Masover

At ruby 1.9.2dev (2009-07-18 trunk 24186) [i386-mswin32_90]

Ruby runs like this.
[1,2,3].map.with_object(3){|v|v+3}

=> 3
[1,2,3].each.with_object(3).map{|v|v+3}

=> [4, 5, 6]

That makes sense, now that I know what with_object does.
I think that is better to use.
[1,2,3].map.with_object(3){|v|v+3}

=> [4, 5, 6]

Is it a bug?

I doubt it -- map is the only iterator that returns the results of each
iteration.

In fact, if you look at what with_object does, it's doing exactly what it's
designed to:

irb(main):041:0> [1,2,3].each.with_object({}){|x,h| h[x] = x**2}
=> {1=>1, 2=>4, 3=>9}

I don't see why you want to do with_object at all -- your above example would
work just fine as:

[1,2,3].map{|v|v+3}

If you wanted to use a variable, you could do that too:

x = 3
[1,2,3].map{|v|v+x}

I can't imagine why you'd want to have the behavior of foo.with_object.map,
ever. Could you please give more context?
 
7

7stud --

David said:
=> [4, 5, 6]
That makes sense, now that I know what with_object does.

Could you enlighten me? The following doesn't yield any clues:

$ri19 Enumerator#with_object

------------------------------------------------- Enumerator#with_object
e.with_object(obj) {|(*args), memo_obj| ... }
e.with_object(obj)

From Ruby 1.9.1
 
D

David A. Black

Hi --

David said:
=> [4, 5, 6]
That makes sense, now that I know what with_object does.

Could you enlighten me? The following doesn't yield any clues:

$ri19 Enumerator#with_object

------------------------------------------------- Enumerator#with_object
e.with_object(obj) {|(*args), memo_obj| ... }
e.with_object(obj)

From Ruby 1.9.1
------------------------------------------------------------------------
Iterates the given block for each element with an arbitrary object
given, and returns the initially given object.

If no block is given, returns an enumerator.

That's pretty much what it does. It's kind of like inject, except it
uses the same object each time through instead of assigning the
accumulator slot to the value of the block. Thus:

array = [1,2,3,4,5]
tens_hash = array.each_with_object({}) {|n,obj| obj[n] = n * 10 }
# => {1=>10, 2=>20, 3=>30, 4=>40, 5=>50}

It saves you having to do that awkward:

{|h,n| h[n] = n * 10; h }

thing to feed the object back into the loop.

Without a block, it returns an enumerator, which will then do a
"with"-style double iteration (like with_index) based on whatever you
call on it:
array = [1,2,3,4,5] => [1, 2, 3, 4, 5]
enum = array.each_with_object({})
=> # said:
tens_hash = enum.each {|n,obj| obj[n] = n * 10 }
=> {1=>10, 2=>20, 3=>30, 4=>40, 5=>50}


David
 
7

7stud --

David said:
Hi --

------------------------------------------------- Enumerator#with_object
e.with_object(obj) {|(*args), memo_obj| ... }
e.with_object(obj)

From Ruby 1.9.1
------------------------------------------------------------------------
Iterates the given block for each element with an arbitrary object
given, and returns the initially given object.

If no block is given, returns an enumerator.

That's pretty much what it does. It's kind of like inject, except it
uses the same object each time through instead of assigning the
accumulator slot to the value of the block. Thus:

array = [1,2,3,4,5]
tens_hash = array.each_with_object({}) {|n,obj| obj[n] = n * 10 }
# => {1=>10, 2=>20, 3=>30, 4=>40, 5=>50}

It saves you having to do that awkward:

{|h,n| h[n] = n * 10; h }

thing to feed the object back into the loop.

Without a block, it returns an enumerator, which will then do a
"with"-style double iteration (like with_index) based on whatever you
call on it:
array = [1,2,3,4,5] => [1, 2, 3, 4, 5]
enum = array.each_with_object({})
=> # said:
tens_hash = enum.each {|n,obj| obj[n] = n * 10 }
=> {1=>10, 2=>20, 3=>30, 4=>40, 5=>50}


David
David said:
Hi --

------------------------------------------------- Enumerator#with_object
e.with_object(obj) {|(*args), memo_obj| ... }
e.with_object(obj)

From Ruby 1.9.1
------------------------------------------------------------------------
Iterates the given block for each element with an arbitrary object
given, and returns the initially given object.

If no block is given, returns an enumerator.

That's pretty much what it does. It's kind of like inject, except it
uses the same object each time through instead of assigning the
accumulator slot to the value of the block. Thus:

array = [1,2,3,4,5]
tens_hash = array.each_with_object({}) {|n,obj| obj[n] = n * 10 }
# => {1=>10, 2=>20, 3=>30, 4=>40, 5=>50}

It saves you having to do that awkward:

{|h,n| h[n] = n * 10; h }

thing to feed the object back into the loop.

Without a block, it returns an enumerator, which will then do a
"with"-style double iteration (like with_index) based on whatever you
call on it:
array = [1,2,3,4,5] => [1, 2, 3, 4, 5]
enum = array.each_with_object({})
=> # said:
tens_hash = enum.each {|n,obj| obj[n] = n * 10 }
=> {1=>10, 2=>20, 3=>30, 4=>40, 5=>50}


David


Thanks for the response.

In the definition:
e.with_object(obj) {|(*args), memo_obj| ... }

1) Why *args?

2) Why (*args) ?

My tests show the first argument that with_object yields to the block
may or may not be an array:

example a:
----------
class A
def my_iter
yield "red", 1
yield "blue", 2
end
end

a = A.new
a.my_iter{|x, y| puts "#{x} #{y}"}

--output:--
red 1
blue 2
(That shows that the yield sends two arguments to the block--not an
array.)


h = {}

results = e.with_object(h) do |args, _|
p args
end

--output:--
["red", 1]
["blue", 2]


example b:
---------
class A
def my_iter
yield 1
yield 2
end
end

a = A.new
a.my_iter{|x| puts "#{x}"}

--output:--
1
2


e = a.enum_for:)my_iter)

h = {}

results = e.with_object(h) do |args, _|
p args
end

--output:--
1
2


======


In the definition:
e.with_object(obj) {|(*args), memo_obj| ... }

3) Why 'memo_obj' and not 'obj'?

Even though two different variable names makes it hard to track what is
going on, the following assignment takes place:

memo_obj = obj

For example:

e = a.enum_for:)my_iter)

h = {}
puts "#{h.inspect} #{h.object_id}"

results = e.with_object(h) do |args, accumulator_hash|
key, val = args

accumulator_hash[key] = val
print "#{accumulator_hash.inspect}"
puts " #{accumulator_hash.object_id}"
end

--output:--
{} 345296
{"red"=>1} 345296
{"red"=>1, "blue"=>2} 345296


In the description:

------------------------------------------------- Enumerator#with_object
e.with_object(obj) {|(*args), memo_obj| ... }
e.with_object(obj)

From Ruby 1.9.1
------------------------------------------------------------------------
Iterates the given block for each element with an arbitrary object
given, and returns the initially given object.

If no block is given, returns an enumerator.

----------------------------------------------------

there needs to be a little more detail. Maybe something like:

Yields element_of_e, obj to the block. args will be an array when e is
attached to an iterator that yields more than one value, otherwise args
will be the value the iterator yields. The return value of the block is
discarded.

If a block is given, with_object returns memo_obj. If no block is
given, with_object returns an enumerator.



Here's a full example that I played around with:


class A
def my_iter
yield "red", 1
yield "blue", 2
end
end

a = A.new
a.my_iter{|x, y| puts "#{x} #{y}"}

--output:--
red 1
blue 2


e = a.enum_for:)my_iter)

h = {}
puts "#{h.inspect} #{h.object_id}"

--output:--
{} 345212

results = e.with_object(h) do |args, accumulator_hash|
p args
key, val = args

accumulator_hash[key] = val
print "#{accumulator_hash.inspect}"
puts " #{accumulator_hash.object_id}"

10
end


--output:--
["red", 1]
{"red"=>1} 345212
["blue", 2]
{"red"=>1, "blue"=>2} 345212
 
D

David A. Black

Hi --

David said:
Hi --

------------------------------------------------- Enumerator#with_object
e.with_object(obj) {|(*args), memo_obj| ... }
e.with_object(obj)

From Ruby 1.9.1
------------------------------------------------------------------------
Iterates the given block for each element with an arbitrary object
given, and returns the initially given object.

If no block is given, returns an enumerator.

That's pretty much what it does. It's kind of like inject, except it
uses the same object each time through instead of assigning the
accumulator slot to the value of the block. Thus:

array = [1,2,3,4,5]
tens_hash = array.each_with_object({}) {|n,obj| obj[n] = n * 10 }
# => {1=>10, 2=>20, 3=>30, 4=>40, 5=>50}

It saves you having to do that awkward:

{|h,n| h[n] = n * 10; h }

thing to feed the object back into the loop.

Without a block, it returns an enumerator, which will then do a
"with"-style double iteration (like with_index) based on whatever you
call on it:
array = [1,2,3,4,5] => [1, 2, 3, 4, 5]
enum = array.each_with_object({})
=> # said:
tens_hash = enum.each {|n,obj| obj[n] = n * 10 }
=> {1=>10, 2=>20, 3=>30, 4=>40, 5=>50}


David


Thanks for the response.

In the definition:
e.with_object(obj) {|(*args), memo_obj| ... }

1) Why *args?

2) Why (*args) ?

I read it as just shorthand for "miscellaneous other arguments". I
don't know whether there's a better way to denote that.
In the definition:


3) Why 'memo_obj' and not 'obj'?

Even though two different variable names makes it hard to track what is
going on, the following assignment takes place:

memo_obj = obj

I think it's schematic. obj is not necessarily the variable "obj";
it's just an object:

e.with_object(<some object here>)

while memo_obj indicates that the last parameter will bind to that
object (and could serve as a variable name).


David
 
7

7stud --

ErMaker said:
At ruby 1.9.2dev (2009-07-18 trunk 24186) [i386-mswin32_90]

Ruby runs like this.
[1,2,3].map.with_object(3){|v|v+3} => 3
[1,2,3].each.with_object(3).map{|v|v+3}
=> [4, 5, 6]


I think that is better to use.
[1,2,3].map.with_object(3){|v|v+3}
=> [4, 5, 6]

Is it a bug?


In this line:
[1,2,3].map.with_object(3){|v|v+3}


map is called without a block, and

----
...most built-in iterators return an enumerator when they are called
without a block.
----
p. 307 "The Well Grounded Rubyist"

Is that true in this case?

e = [1, 2, 3].map
p e

--output:--
#<Enumerator:0x0e7038>


Yep. So what's "in" that enumerator:

e.each do |x, y|
puts "#{x.inspect} #{y.inspect}"
end

--output:--
1 nil
2 nil
3 nil

The map enumerator just yields the elements of the array.

The next call in the method chain is with_object():
[1,2,3].map.with_object(3){|v|v+3}

and regardless of the iterator that with_object is attached to, when
with_object() is called with a block, with_object returns its argument,
which in this case is 3. That is why the final result is 3:
[1,2,3].map.with_object(3){|v|v+3}
=> 3


On the other hand, in this line,
[1,2,3].each.with_object(3).map{|v|v+3}
=> [4, 5, 6]

with_object is called without a block. And according to the ri
information:

------------------------------------------------- Enumerator#with_object
e.with_object(obj) {|(*args), memo_obj| ... }
e.with_object(obj)

From Ruby 1.9.1
------------------------------------------------------------------------
Iterates the given block for each element with an arbitrary object
given, and returns the initially given object.

***If no block is given, returns an enumerator.***
------------------------

that means with_object returns an enumerator:


p [1,2,3].each.with_object(3)

--output:--
#<Enumerator:0x0e72bc>

What's "in" that enumerator?

e = [1,2,3].each.with_object(3)

e.each do |x, y|
puts "#{x.inspect} #{y.inspect}"
end

--output:--
1 3
2 3
3 3

As you can see, the enumerator is returning each element of the array
along with with_object's argument: 3.

The next call in the method chain is map():
[1,2,3].each.with_object(3).map{|v|v+3}

map takes the value(s) it is sent, converts the value(s), then stores
the converted value(s) in an array, then the array is returned as the
final result of the call to map():

result = [1,2,3].each.with_object(3).map do |v, w|
puts "#{v} #{w}"
v + 3
end

p result

--output:--
1 3
2 3
3 3
[4, 5, 6]

That is why you get the result:
[1,2,3].each.with_object(3).map{|v|v+3}
=> [4, 5, 6]
 
D

David A. Black

Hi --

The next call in the method chain is map():
[1,2,3].each.with_object(3).map{|v|v+3}

map takes the value(s) it is sent, converts the value(s), then stores
the converted value(s) in an array, then the array is returned as the
final result of the call to map():

result = [1,2,3].each.with_object(3).map do |v, w|
puts "#{v} #{w}"
v + 3
end

p result

--output:--
1 3
2 3
3 3
[4, 5, 6]

That is why you get the result:
[1,2,3].each.with_object(3).map{|v|v+3}
=> [4, 5, 6]
Except....
[1,2,3].each.with_object("hi!").map{|v| v + 3 }
=> [4, 5, 6]

The map is only binding one of the arguments, and the argument to
with_object is being ignored. (The 3 is hard-coded in the block.)

To make use of it in the map, you'd have to do:

[1,2,3].each.with_object(3).map{|v,w| v + w }


David
 
7

7stud --

David said:
Except....
[1,2,3].each.with_object("hi!").map{|v| v + 3 }
=> [4, 5, 6]

The map is only binding one of the arguments, and the argument to
with_object is being ignored. (The 3 is hard-coded in the block.)

I think my examples show that pretty clearly:
#<Enumerator:0x0e72bc>

What's "in" that enumerator?

e = [1,2,3].each.with_object(3)

e.each do |x, y|
puts "#{x.inspect} #{y.inspect}"
end

--output:--
1 3
2 3
3 3

As you can see, the enumerator is returning each element of the array
along with with_object's argument: 3.

The next call in the method chain is map():
[1,2,3].each.with_object(3).map{|v|v+3}

map takes the value(s) it is sent, converts the value(s), then stores
the converted value(s) in an array, then the array is returned as the
final result of the call to map():

result = [1,2,3].each.with_object(3).map do |v, w|
puts "#{v} #{w}"
v + 3
end

p result

--output:--
1 3
2 3
3 3
[4, 5, 6]

In both examples, I tried to demonstrate that the op was discarding the
second argument yielded to the map block.
 
7

7stud --

7stud said:
The next call in the method chain is with_object():
[1,2,3].map.with_object(3){|v|v+3}

and ***regardless of the iterator that with_object is attached to***,
when
with_object() is called with a block, with_object returns its argument,
which in this case is 3.

That isn't accurate, though. In this case with_object isn't attached to
an iterator--it's attached to an enumerator. So I guess I should have
simply said,

When with_object is called with a block, with_object returns its
argument.

and avoided the issue of how ruby allows you to chain enumerators to
both iterators and other enumerators.
 
D

David A. Black

Hi --

David said:
Except....
[1,2,3].each.with_object("hi!").map{|v| v + 3 }
=> [4, 5, 6]

The map is only binding one of the arguments, and the argument to
with_object is being ignored. (The 3 is hard-coded in the block.)

I think my examples show that pretty clearly:

I thought you were saying at the end that the yielding of the 3
explained why you got [4,5,6].


David
 
D

David A. Black

Hi --

7stud said:
The next call in the method chain is with_object():
[1,2,3].map.with_object(3){|v|v+3}

and ***regardless of the iterator that with_object is attached to***,
when
with_object() is called with a block, with_object returns its argument,
which in this case is 3.

That isn't accurate, though. In this case with_object isn't attached to
an iterator--it's attached to an enumerator. So I guess I should have
simply said,

When with_object is called with a block, with_object returns its
argument.

and avoided the issue of how ruby allows you to chain enumerators to
both iterators and other enumerators.

I don't think it does; with_object is an instance method of
Enumerator.


David
 
7

7stud --

David said:
Hi --


I read it as just shorthand for "miscellaneous other arguments". I
don't know whether there's a better way to denote that.

When I see *args as a parameter variable, I expect the parameter
variable to always reference an array. For instance:

def meth(*args)
p args
end

meth(1, 2)
meth("red")

--output:--
[1, 2]
["red"]


So the *args confuses me in the ri info:

------------------------------------------------- Enumerator#with_object
e.with_object(obj) {|(*args), memo_obj| ... }
e.with_object(obj)

From Ruby 1.9.1
--------------


As my examples show, sometimes args will be an array and other times it
won't.


I think it's schematic. obj is not necessarily the variable "obj";
it's just an object:

I see.
 

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

Forum statistics

Threads
474,169
Messages
2,570,917
Members
47,458
Latest member
Chris#

Latest Threads

Top