Symbol#to_proc Problem

http://blog.hasmanythrough.com/articles/read/8

this link suggests a way to hack Symbol to allow

[1, 2, 3].map(&:to_s) # => [“1”, “2”, “3”]

my question is how to change the hack to work correctly for other
methods. details of the problem are below:

it doesn’t work for reversing arrays:

curi-g5:~ curi$ ruby -v
ruby 1.8.4 (2005-12-24) [powerpc-darwin8.5.0]

irb(main):017:0> class Symbol
irb(main):018:1> def to_proc
irb(main):019:2> Proc.new { |obj, *args| obj.send(self, *args) }
irb(main):020:2> end
irb(main):021:1> end
=> nil
irb(main):022:0> [[1,2,3], [4,5,6], [7,8,9]].map {|i| i.reverse}
=> [[3, 2, 1], [6, 5, 4], [9, 8, 7]]

irb(main):023:0> [[1,2,3], [4,5,6], [7,8,9]].map &:reverse
NoMethodError: undefined method reverse' for 1:Fixnum from (irb):19:in send’
from (irb):19:in `to_proc’
from (irb):23
from (null):0

the problem is that *args is capturing part of the array. here’s the
same thing happening without the Symbol hack involved:

irb(main):024:0> [[1,2,3], [4,5,6], [7,8,9]].map {|i, *args| i.reverse}
NoMethodError: undefined method `reverse’ for 1:Fixnum
from (irb):24
from (irb):24
from (null):0

so how should the Symbol hack be redone to work properly?

just removing *args is no good. here is an example that needs *args:

irb(main):027:0> class Fixnum
irb(main):028:1> def foo x
irb(main):029:2> self+x
irb(main):030:2> end
irb(main):031:1> end
=> nil
irb(main):032:0> { 1=>2, 3=>4}.map &:foo
=> [3, 7]

so to illustrate what happens without *args: it solves one problem
while creating another:

irb(main):039:0> class Symbol
irb(main):040:1> def to_proc
irb(main):041:2> Proc.new { |obj| obj.send(self) }
irb(main):042:2> end
irb(main):043:1> end
=> nil
irb(main):044:0> [[1,2,3], [4,5,6], [7,8,9]].map &:reverse # works now!
=> [[3, 2, 1], [6, 5, 4], [9, 8, 7]]

irb(main):045:0> { 1=>2, 3=>4}.map &:foo # breaks now!
NoMethodError: undefined method foo' for [1, 2]:Array from (irb):41:in send’
from (irb):41:in to_proc' from (irb):45:in map’
from (irb):45
from :0

– Elliot T.

On Jun 15, 2006, at 9:40 PM, Elliot T. wrote:

=> [[3, 2, 1], [6, 5, 4], [9, 8, 7]]
the same thing happening without the Symbol hack involved:

irb(main):044:0> [[1,2,3], [4,5,6], [7,8,9]].map &:reverse # works

– Elliot T.
Curiosity Blog – Elliot Temple

Yeah I don’t believe it’s possible to have this work both ways.

You could do

class Symbol
def unary_meth
lambda { |obj| obj.send(self) }
end

def nary_meth
lambda { |obj, *args| obj.send(self, *args) }
end
end

[[1,2,3], [4,5,6]].map &:reverse.unary_meth

{ 1=2, 3=>4 }.map &:foo.nary_meth

This is one of those cases where you can’t have your cake and eat it
too.

On Jun 15, 2006, at 9:44 PM, Logan C. wrote:

lambda { |obj, *args| obj.send(self, *args) }

end
end

[[1,2,3], [4,5,6]].map &:reverse.unary_meth

{ 1=2, 3=>4 }.map &:foo.nary_meth

This is one of those cases where you can’t have your cake and eat
it too.

that is helpful, thanks

is there a reason it’s important for *args to force an array to split
up, instead of just being empty?

– Elliot T.

Hi,

In message “Re: Symbol#to_proc Problem”
on Fri, 16 Jun 2006 10:40:48 +0900, Elliot T. [email protected]
writes:

|it doesn’t work for reversing arrays:

|irb(main):023:0> [[1,2,3], [4,5,6], [7,8,9]].map &:reverse
|NoMethodError: undefined method `reverse’ for 1:Fixnum

Good point. The ActiveSupport Symbol#to_proc consider an array as a
group of a receiver and method arguments. It was a design choice.
You need to ask the rationale behind this design to the rails list.

As you’ve stated, the alternative is making everything a receiver.
I think either choice is both good and bad. Personally I prefer the
latter, it’s more consistent. But compatibility problem would arise.

						matz.

Hello,

On 6/16/06, Yukihiro M. wrote:

                                                    matz.

For example, I submitted http://dev.rubyonrails.org/ticket/5295 as
follow:

class Symbol
def to_proc
Proc.new{|*args| args.shift.send(self, *args)}
end
end

[[1,2,3], [4,5,6], [7,8,9]].map(&:reverse)
=> [[3, 2, 1], [6, 5, 4], [9, 8, 7]]
(1…10).inject(&:+)
=> 55
{0=>“zero”,1=>“one”,2=>“two”,3=>“three”}.sort_by(&:first).map(&:last)
=> [“zero”, “one”, “two”, “three”]

However, I wonder whether below is what we want.

{1=>2,3=>4}.map(&:reverse)
=> [[2, 1], [4, 3]]

Thanks,

You could always write a String#to_proc to accept something like [12,
34, 56].map(&‘to_s.reverse’), but I think this is ugly. Or you may as
well just do [12, 34, 56].map(&:to_s).map(&:reverse) which is equally
ugly.

But of course, the traditional [12, 34, 56].map { |x| x.to_s.reverse }
way exists, so why fix something that isn’t broken?

On Fri, Jun 16, 2006 at 10:40:48AM +0900, Elliot T. wrote:

=> nil
irb(main):022:0> [[1,2,3], [4,5,6], [7,8,9]].map {|i| i.reverse}
=> [[3, 2, 1], [6, 5, 4], [9, 8, 7]]

irb(main):023:0> [[1,2,3], [4,5,6], [7,8,9]].map &:reverse
NoMethodError: undefined method reverse' for 1:Fixnum from (irb):19:in send’
from (irb):19:in `to_proc’
from (irb):23
from (null):0

FYI the “official” implementation, recently added to Ruby 1.9, works
that
way too:

$ ./ruby19 -v -e “p [[1,2,3], [4,5,6], [7,8,9]].map(&:reverse)”
ruby 1.9.0 (2006-06-11) [i686-linux]
-e:1: undefined method `reverse’ for 1:Fixnum (NoMethodError)
from -e:1

I read that Minero A. was against this being added to the core[1];
maybe it
was for the reason you exposed.

[1] http://mput.dip.jp/mput/?date=20060611#p01

On Jun 16, 2006, at 4:09 AM, Nobuhiro IMAI wrote:

For example, I submitted http://dev.rubyonrails.org/ticket/5295 as
follow:

class Symbol
def to_proc
Proc.new{|*args| args.shift.send(self, *args)}
end
end

I like that version better, for the official Ruby method.

James Edward G. II

Hi,

In message “Re: Symbol#to_proc Problem”
on Fri, 16 Jun 2006 23:03:26 +0900, [email protected] writes:

|I’m intregued: Why wouldn’t you simply use send? The Picaxe defines them
|as synonymous. I’ve seen arangements where all methods are stripped off
|an object (BlankSlate) except those prefixed with a _. Is that the
|reason?

The receiver may be a socket, which accidentally has send method of
different behavior.

						matz.

[snip Elliot trying to have his cake and eat it ;)]

Think about what you’re trying to do here.

Applying map on its own to both input data structures gives you:

{ 1=>2, 3=>4}.map #=> [[1,2], [3,4]]
[[1,2,3], [4,5,6], [7,8,9]].map #=> [[1,2,3], [4,5,6], [7,8,9]]

i.e. in both cases you are effectively mapping an array of arrays.

In the &:foo case, you want to apply the proc to the first element of
each array,
with subsequent elements as arguments. In the &:reverse case, you want
to apply
the proc to each array as a whole. How can the lambda in the to_proc
method know which case is which?

You might be able to devise a hack to check the arity of the methods
involved, but even that is effectively guess work. On top of that,
using Symbol.to_proc in this way is very inefficient compared to using
blocks.

IMHO this is one of those clever hacks that shouldn’t make it into
production code.

Regards,
Sean

I like that version better, for the official Ruby method.

I’m intregued: Why wouldn’t you simply use send? The Picaxe defines them
as synonymous. I’ve seen arangements where all methods are stripped off
an object (BlankSlate) except those prefixed with a _. Is that the
reason?

Cheers,
Benjohn