Passing a named function instead of a code block?

P

Paul Jungwirth

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
 
M

Matthias Reitinger

Paul said:
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
 
7

7stud --

Matthias said:
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?
 
J

James Coglan

[Note: parts of this message were removed to make it a legal post.]

2009/3/20 7stud -- said:
Matthias said:
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?



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

7stud --

James said:
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.
 
J

James Coglan

[Note: parts of this message were removed to make it a legal post.]

2009/3/20 7stud -- said:
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.


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

Paul Jungwirth

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?
 
J

James Coglan

[Note: parts of this message were removed to make it a legal post.]

2009/3/20 Paul Jungwirth said:
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?


a.method:)use)

See docs for Object#method:
http://ruby-doc.org/core/classes/Object.html#M000338
 
P

Paul Jungwirth

James said:

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

7stud --

James said:
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?
 
F

Florian Gilcher

James Coglan wrote:

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?


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 Gilcher

smtp: (e-mail address removed)
jabber: (e-mail address removed)
gpg: 533148E2
 
C

Christopher Dicely

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,900
Latest member
Nell636132

Latest Threads

Top