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

-- Elliot Temple

···

from :0

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:40 PM, Elliot Temple wrote:

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 (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 Temple
Curiosity Blog – Elliot Temple

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 Aoki 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 Fri, Jun 16, 2006 at 10:40:48AM +0900, Elliot Temple wrote:

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

--
Mauricio Fernandez - http://eigenclass.org - singular Ruby

Hi,

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.

···

In message "Re: Symbol#to_proc Problem" on Fri, 16 Jun 2006 10:40:48 +0900, Elliot Temple <curi@curi.us> writes:

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 6/16/06, Elliot Temple <curi@curi.us> wrote:

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 (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 Temple
Curiosity Blog – Elliot Temple

--
Matt

[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

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 Temple

···

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

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.

Hello,

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

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,

···

On 6/16/06, Yukihiro Matsumoto wrote:
--
Nobuhiro IMAI

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

James Edward Gray II

···

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

···

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.

Hi,

···

In message "Re: Symbol#to_proc Problem" on Fri, 16 Jun 2006 23:03:26 +0900, benjohn@fysh.org 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.