1

Why are these not equal?

['a'].each_with_object('b'){|x,memo| memo << x}
=> "ba"

['a'].each_with_object('b', &:<<)
=> "b"

I thought that the second is just a syntactically sugarized version of the first.

sawa
  • 165,429
  • 45
  • 277
  • 381
newUserNameHere
  • 17,348
  • 18
  • 49
  • 79
  • 1
    second code is not same as the first output tells that. But I don't know from where to start the explanations. :) In your second code `memo` is `'b'` and `x` is `'a'`. But that `memo << x` is not happening, and it shouldn't though. And that is why once block is finished `memo` is getting returned, as that is how `each_with_object` works, it returns `memo` – Arup Rakshit Jul 13 '16 at 12:44
  • Could you write the long form equivalent of the second example and submit that as an answer? – newUserNameHere Jul 13 '16 at 12:50
  • This answer describes how the `&` operator works and might shed some light on why the two are not equivalent: http://stackoverflow.com/a/9429972/446681 – Hector Correa Jul 13 '16 at 12:53
  • You can seem the implementation in C here: http://ruby-doc.org/core-2.2.2/Symbol.html#method-i-to_proc. If you don't know C, like me. You might find this interesting: `['a'].reduce('b', &:<<) #=> "ba"`. If you think about it, with each_with_object, even if the strings did concatenate, you would have "ab" instead of "ba", so the two examples should not be equal anyway. –  Jul 13 '16 at 13:06
  • @jphager2 “even if the strings did concatenate”—the strings _did concatenate_, see my answer. – Aleksei Matiushkin Jul 13 '16 at 13:21
  • aha so its just because of the order of the args to the proc, and that each_with_object returns the object. Obviously =p –  Jul 13 '16 at 13:34

2 Answers2

2

Symbol#to_proc returns a Proc instance of arity -1:

:<<.to_proc.arity
#⇒ -1

That said, it will silently accept any number of arguments, then basically call args.first.method(self).to_proc and pass the result of the above as codeblock parameter to the caller. That is why ['a'].reduce('b', :<<) works as you expected: reduce’s first block argument is memo, and

'b'.method(:<<, 'a')

returns "ba" string.

Your code works the same way:

arr = ['a']
arr.each_with_object('b', &:<<)
arr
#⇒ ['ab']

You’ve just expected the wrong receiver for String#<< method. In this case, the receiver is an array element, since it’s passed to the block as the first argument. Look:

arr = ['a', 'b']
arr.each_with_object('z', &:<<)
arr
#⇒ ['az', 'bz']

memo itself stays untouched and returned as a result of call to each_with_object. You might even have it frozen and the code will still work.

That said, there is no way to achieve the functionality you requested with syntactic sugar using Symbol#to_proc and each_with_object.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • The old rails implementation of `Symbol#to_proc` was `Proc.new { |*args| args.shift.__send__(self, args) }` –  Jul 13 '16 at 13:49
  • @jphager2 How is it different from the current implementation and/or the code snippet I provided (save for that my version does not mutate `args` array, what makes no sense taking into account that `args` are all on the stack)? – Aleksei Matiushkin Jul 13 '16 at 13:53
  • The current implementation is written in C and does a lot more, like trying to find the proc in a cache, marking the proc for garbage collection, etc. Actually, I think your implementation has a bug, assuming that you are in the context of a Symbol instance. You will not have access to the args in the method definition of Symbol#to_proc. –  Jul 13 '16 at 14:00
  • Ah, if you meant the current Rails, implementation, then there isn't one, because Symbol#to_proc is defined now in Ruby. –  Jul 13 '16 at 14:15
0

The returned object is the first argument passed to each_with_object. In the first example, this first object "b" is appended with "a", which is returned. In the second example, the element of the receiver "a" is appended with the first argument "b", but nothing happens to the first argument.

sawa
  • 165,429
  • 45
  • 277
  • 381