Forum: Ruby Passing a named function instead of a code block?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Paul J. (Guest)
on 2009-03-20 04:18
Hello,

I have a question about ruby's feature that when you call a method, you
can pass in a block of code after the last argument. Instead of writing
a code block, suppose I already have a def'd method that would work just
as well. Is there any way I can pass that in directly? For example,
suppose I have this code:

#!/usr/bin/env ruby

def fib(n)
    a, b = 0, 1
    n.times do |i|
        a, b = b, a+b
    end
    b
end

c = [1, 2, 3, 4]

puts c.collect {|i| fib i}

That will print fib(1), fib(2), fib(3), fib(4). But why write a code
block that takes one argument and does nothing but call a function that
takes one argument? Is there some way I could have replaced the last
line with something like this?:

puts c.collect \fib

In python I could have written the last line thus:

print map(fib, (1, 2, 3, 4))

Does ruby have something similar?

Thanks,
Paul
Matthias R. (Guest)
on 2009-03-20 04:42
Paul J. wrote:
> I have a question about ruby's feature that when you call a method, you
> can pass in a block of code after the last argument. Instead of writing
> a code block, suppose I already have a def'd method that would work just
> as well. Is there any way I can pass that in directly? For example,
> suppose I have this code:
>
> #!/usr/bin/env ruby
>
> def fib(n)
>     a, b = 0, 1
>     n.times do |i|
>         a, b = b, a+b
>     end
>     b
> end
>
> c = [1, 2, 3, 4]
>
> puts c.collect {|i| fib i}

  puts c.collect(&method(:fib))

-Matthias
7stud -. (Guest)
on 2009-03-20 10:52
Matthias R. wrote:
>
>   puts c.collect(&method(:fib))
>

Why is there a difference here:

def square1(x)
  x*x
end

square2 = lambda { |x| x*x}

puts [1, 2, 3].collect(&square2)
puts [1, 2, 3].collect(&square1)

--output:--
1
4
9
r1test.rb:8:in `square1': wrong number of arguments (0 for 1)
(ArgumentError)
        from r1test.rb:8


Why does ruby make you use the tortured syntax:

&method(:square1)

for a method vs. the easier syntax for a Proc object?
James C. (Guest)
on 2009-03-20 11:10
(Received via mailing list)
2009/3/20 7stud -- <removed_email_address@domain.invalid>

>
> (ArgumentError)
>        from r1test.rb:8
>
>
> Why does ruby make you use the tortured syntax:
>
> &method(:square1)
>
> for a method vs. the easier syntax for a Proc object?



square2 is a variable name (ie. something you've made an assignment to),
it's just a reference to the lambda object. However, square1 is a method
and
Ruby allows calling methods without parens, so 'square1' is actually
interpreted as a method call to square1 with no arguments. Therefore, to
grab a method as an object without calling it, we need to use
method(:square1).
7stud -. (Guest)
on 2009-03-20 12:05
James C. wrote:
> 2009/3/20 7stud -- <removed_email_address@domain.invalid>
>
>>
>> (ArgumentError)
>>        from r1test.rb:8
>>
>>
>> Why does ruby make you use the tortured syntax:
>>
>> &method(:square1)
>>
>> for a method vs. the easier syntax for a Proc object?
>
>
>
> square2 is a variable name (ie. something you've made an assignment to),
> it's just a reference to the lambda object. However, square1 is a method
> and
> Ruby allows calling methods without parens, so 'square1' is actually
> interpreted as a method call to square1 with no arguments. Therefore, to
> grab a method as an object without calling it, we need to use
> method(:square1).

Ok.  But there is a certain amount of hypocrisy in that explanation
Look here:

&square1
:square1

In the first expression there is a method call, and in the second there
isn't.  Yet, you could describe both those lines as: a method name
preceded by some symbol.
James C. (Guest)
on 2009-03-20 12:19
(Received via mailing list)
2009/3/20 7stud -- <removed_email_address@domain.invalid>

> >> &method(:square1)
> > grab a method as an object without calling it, we need to use
> preceded by some symbol.
Yes, it probably looks that way. To see the difference it helps to know
how
Ruby is parsed. :square1 is an atomic unit representing the symbol whose
name is 'square1'. ":" is not an operator, it is part of the syntax for
symbols. However, "&" is an operator responsible for casting between
procs
and blocks. The expression '&square1' should be read '&( square1 )',
that is
we call square1 and cast the result of that using '&'. The same applies
to
'method'. 'method' is a method that takes a symbol/string and returns
the
Method object with that name in the current scope. 'method(square1)'
would
be interpreted as a call to square1, passing the result to 'method'.

So, '&square1' throws an error because you're calling a method with
insufficient arguments. '&:square1' would try to cast a symbol to a
proc,
which if you're using ActiveSupport would return the block { |object|
object.square1 }.

'&square2' is fine as square2 is just a variable referring to a proc.
Likewise, '&method(:square1)' is fine because method(:square1) is a
Method
object, which can be cast to a block.
Paul J. (Guest)
on 2009-03-20 13:07
Matthias R. wrote:
>   puts c.collect(&method(:fib))

Thank you for your help; this does just what I wanted.
Paul J. (Guest)
on 2009-03-20 13:40
Okay, now how about when you want to reference a method on an instance?
For example:

#!/usr/bin/env ruby

class Adder
    def initialize(n)
        @n = n
    end

    def use(x)
        x + n
    end
end


a = Adder.new(5)

print [1, 2, 3, 4].collect(&method(:a.use))

This doesn't work. It looks like : binds tighter than .. But :(a.use)
doesn't work either. Any suggestions?
James C. (Guest)
on 2009-03-20 13:43
(Received via mailing list)
2009/3/20 Paul J. <removed_email_address@domain.invalid>

>    def use(x)
> doesn't work either. Any suggestions?
a.method(:use)

See docs for Object#method:
http://ruby-doc.org/core/classes/Object.html#M000338
Paul J. (Guest)
on 2009-03-20 15:08
James C. wrote:
> See docs for Object#method:
> http://ruby-doc.org/core/classes/Object.html#M000338

Ah, so I want a line like this:

puts [1, 2, 3, 4].collect(&a.method(:use))

Actually, this helps me understand the previous solution. I guess
method(:foo) and a.method(:foo) are actually going to the same place,
huh? And each return a Method object, which I then cast to a block with
&. It is all starting to make sense....

Doesn't this seem like an appropriate place for some syntactic sugar?
I'd be nice if you could abbreviate such calls with something like:
  \:foo
  \:a.foo
Since ruby is so focused on closures, this seems like an appropriate
place for such brevity.

Thanks again!
7stud -. (Guest)
on 2009-03-22 05:23
James C. wrote:
> 2009/3/20 7stud -- <removed_email_address@domain.invalid>
>
>> >> &method(:square1)
>> > grab a method as an object without calling it, we need to use
>> preceded by some symbol.
> Yes, it probably looks that way. To see the difference it helps to know
> how
> Ruby is parsed. :square1 is an atomic unit representing the symbol whose
> name is 'square1'. ":" is not an operator, it is part of the syntax for
> symbols. However, "&" is an operator responsible for casting between
> procs
> and blocks. The expression '&square1' should be read '&( square1 )',
> that is
> we call square1 and cast the result of that using '&'. The same applies
> to
> 'method'. 'method' is a method that takes a symbol/string and returns
> the
> Method object with that name in the current scope. 'method(square1)'
> would
> be interpreted as a call to square1, passing the result to 'method'.
>
> So, '&square1' throws an error because you're calling a method with
> insufficient arguments. '&:square1' would try to cast a symbol to a
> proc,
> which if you're using ActiveSupport would return the block { |object|
> object.square1 }.
>
> '&square2' is fine as square2 is just a variable referring to a proc.
> Likewise, '&method(:square1)' is fine because method(:square1) is a
> Method
> object, which can be cast to a block.

Thanks for the explanation.  You said that (&) casts between block and
Procs.  But (&) also appears to be casting a Method to a block in this
line:

puts c.collect(&method(:fib))

(&) can also be used to cast in the reverse direction, e.g. from a block
to a Proc:

def test(&aProc)
  aProc.call(10)
end

test {|x| puts x}

--output:--
10


Can (&) cast a block to a Method?
Florian G. (Guest)
on 2009-03-22 10:05
(Received via mailing list)
On Mar 22, 2009, at 4:20 AM, 7stud -- wrote:

> to a Proc:
>
> Can (&) cast a block to a Method?


There are no casts in Ruby.

& in a Method call calls #to_proc on an Object and expects it to
return a proc. Thats a conversion that has to be done explicitly by
the programmer.

& in a Method definition indicates that the argument has to be a Proc
or something that responds to #to_proc (or a passed Block) which then
gets enforced.

Blocks are Procs (AFAIK, internally, Methods are basically procs as
well). The only magic is "yield" together with a block, which to my
knowledge doesn't require the construction of a Proc Object that has
to be visible to the programmer. But thats up to the implementation.

Regards,
Florian

--
Florian G.

smtp:   removed_email_address@domain.invalid
jabber: removed_email_address@domain.invalid
gpg:    533148E2
Christopher D. (Guest)
on 2009-03-22 10:19
(Received via mailing list)
On 3/21/09, 7stud -- <removed_email_address@domain.invalid> wrote:
>> symbols. However, "&" is an operator responsible for casting between
>>
>
> Thanks for the explanation.  You said that (&) casts between block and
> Procs.  But (&) also appears to be casting a Method to a block in this
> line:
>
> puts c.collect(&method(:fib))

Actually, its casting a Method to a Proc (by calling to_proc on it)
and then treating the proc as a block. To see that's what is
happening, try this:

class Integer
  def to_proc
    Proc.new { self }
  end
end

(1..10).map(&1)

> Can (&) cast a block to a Method?

No. & in a method call always treats a the given proc as a block given
to the method while & in a method definition always makes a block
given to the method as a Proc. Its probably best not to think of this
as "casting" except insofar as the first one "casts" to a proc first
by calling to_proc; blocks aren't objects, and if you do this:

def consume(object=nil)
  if block_given? then yield else object end
end

p = lambda { puts "foo!" }
consume(&p)

You don't get the results you' d get if an object of the
(non-existent) class "Block" was passed to consume, you get the
results you would get if consume was called with a block. So thinking
of & as "casting" an argument to a different class in the method call
is misleading.
This topic is locked and can not be replied to.