.map.with_object(3){|v|v+3} #=> 3 Is this a bug?

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?

Hi –

On Mon, 14 Sep 2009, ErMaker wrote:

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

On Sunday 13 September 2009 09:19:31 pm ErMaker wrote:

=> [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?

David M. wrote:

On Sunday 13 September 2009 09:19:31 pm ErMaker wrote:

=> [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.

Hi –

On Tue, 15 Sep 2009, 7stud – wrote:

------------------------------------------------- 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({})
=> #Enumerator:0x1f525c

tens_hash = enum.each {|n,obj| obj[n] = n * 10 }
=> {1=>10, 2=>20, 3=>30, 4=>40, 5=>50}

David

7stud – wrote:

Oh, and it appears that each_with_object and with_object are synonyms.

David A. Black wrote:

Hi –

On Tue, 15 Sep 2009, 7stud – wrote:

------------------------------------------------- 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({})
=> #Enumerator:0x1f525c

tens_hash = enum.each {|n,obj| obj[n] = n * 10 }
=> {1=>10, 2=>20, 3=>30, 4=>40, 5=>50}

David
David A. Black wrote:
Hi –

On Tue, 15 Sep 2009, 7stud – wrote:

------------------------------------------------- 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({})
=> #Enumerator:0x1f525c

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| … }

  1. 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

Hi –

On Wed, 16 Sep 2009, 7stud – wrote:


tens_hash = array.each_with_object({}) {|n,obj| obj[n] = n * 10 }
call on it:

  1. 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:

e.with_object(obj) {|(*args), memo_obj| … }

  1. 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()

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

David

ErMaker wrote:

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]

Hi –

On Wed, 16 Sep 2009, 7stud – wrote:

v + 3
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

David A. Black wrote:

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.
.
.
.
.
.
.
.

7stud – wrote:

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.

Hi –

On Wed, 16 Sep 2009, 7stud – wrote:

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

Hi –

On Wed, 16 Sep 2009, 7stud – wrote:

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

David A. Black wrote:

Hi –

On Wed, 16 Sep 2009, 7stud – wrote:


tens_hash = array.each_with_object({}) {|n,obj| obj[n] = n * 10 }
call on it:

  1. 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.

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.

In the definition:

e.with_object(obj) {|(*args), memo_obj| … }

  1. 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:

I see.

David A. Black wrote:

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.

Ah. Thanks.