Ruby Proc Syntax
When you prefix the last argument of a call with &
you are making clear that you are sending a block and not a normal argument. Ok, in method(&:something)
, :something
is a symbol, not a proc, so Ruby automatically calls the method to_proc
to get a real block. And Rails guys (and now also vanilla Ruby) cleverly defined it as:
class Symbol
def to_proc
proc { |obj, *args| obj.send(self, *args) }
end
end
That's why you can do:
>> [1, 2, 3].map(&:to_s) # instead of [1, 2, 3].map { |n| n.to_s }
=> ["1", "2", "3"]
[edit] Note: when you realize that this construction is no syntatic sugar but generic infrastructure that Ruby provides, nothing stops you from implementing your own to_proc
for other classes. Never felt limited because &:method
allowed no arguments?
class Array
def to_proc
proc { |obj, *args| obj.send(*(self + args)) }
end
end
>> ["1", "F", "FF"].map(&[:to_i, 16])
=> [1, 15, 255]
Ruby - providing multiple lambda or proc arguments to a function?
Methods in Ruby may not take more than one block. However, you can bind any arbitrary number of blocks as procs (or lambdas) and pass them as regular arguments. You don't get the nice block sugar syntax (do...end
), but it works just fine.
In Ruby, blocks (the anonymous functions passed to methods via {|...| }
or do ... end
syntax) are one of the very few exceptions to the "everything is an object" rule. Ruby makes special allowances for the fact that it is not uncommon to want to pass a callback or anonymous function to a method. You pass that with the block syntax, then can declare that you want access to the block as a Proc by adding the &block
parameter as the last argument in your arg list, which takes your passed anonymous function and binds it to a Proc instance stored in the variable block
. This lets you call it (by calling block.call
) or pass it on as a normal object instance (as block
) or as a block (as &block
). You can also implicitly call it with yield
without binding it to a Proc.
def debug(obj = nil, &block)
p [:object, obj]
p [:block, block]
end
def pass_block_as_proc(&foo)
puts "Passing as foo"
debug(foo)
puts "Passing as &foo"
debug(&foo)
end
pass_block_as_proc {}
Output:
Passing as foo
[:object, #<Proc:0x000055ee3a724a38>]
[:block, nil]
Passing as &foo
[:object, nil]
[:block, #<Proc:0x000055ee3a724a38>]
So, if we want to pass multiple callbacks to a method, we can't rely on the anonymous block sugar. Instead, we have to pass them as bound Proc instances:
def multiple_procs(value, proc1, proc2)
proc2.call(proc1.call(value))
end
puts multiple_procs "foo",
->(s) { s.upcase },
->(s) { s + s }
# => FOOFOO
This link is some additional useful reading to understand the difference between blocks and procs.
When to use lambda, when to use Proc.new?
Another important but subtle difference between procs created with lambda
and procs created with Proc.new
is how they handle the return
statement:
- In a
lambda
-created proc, thereturn
statement returns only from the proc itself - In a
Proc.new
-created proc, thereturn
statement is a little more surprising: it returns control not just from the proc, but also from the method enclosing the proc!
Here's lambda
-created proc's return
in action. It behaves in a way that you probably expect:
def whowouldwin
mylambda = lambda {return "Freddy"}
mylambda.call
# mylambda gets called and returns "Freddy", and execution
# continues on the next line
return "Jason"
end
whowouldwin
#=> "Jason"
Now here's a Proc.new
-created proc's return
doing the same thing. You're about to see one of those cases where Ruby breaks the much-vaunted Principle of Least Surprise:
def whowouldwin2
myproc = Proc.new {return "Freddy"}
myproc.call
# myproc gets called and returns "Freddy",
# but also returns control from whowhouldwin2!
# The line below *never* gets executed.
return "Jason"
end
whowouldwin2
#=> "Freddy"
Thanks to this surprising behavior (as well as less typing), I tend to favor using lambda
over Proc.new
when making procs.
Ruby Procs - ends up with syntax error
&
is a shortcut for calling the to_proc
method.
Because you have a symbol (:anything
) the to_proc
method is called on the symbol. This returns a proc which will call the method with the corresponding name (anything
) on the object that is passed as argument.
If you do this with an enumerator like map
then you actually call the method on each object in the collection.
Now this shortcut does not allow you to pass an argument to the methods. (+
is just syntactic sugar for calling a method on a number that takes another method as argument)
But you can use currying for such a use case:
add = -> (a, b) { a + b }
add_two = add.curry.call(2)
p [1,2,3,4].map(&add_two) # => [3, 4, 5, 6]
What does to_proc method mean in Ruby?
Some methods take a block, and this pattern frequently appears for a block:
{|x| x.foo}
and people would like to write that in a more concise way. In order to do that they use a combination of: a symbol, the method Symbol#to_proc
, implicit class casting, and &
operator. If you put &
in front of a Proc
instance in the argument position, that will be interpreted as a block. If you combine something other than a Proc
instance with &
, then implicit class casting will try to convert that to a Proc
instance using to_proc
method defined on that object if there is any. In case of a Symbol
instance, to_proc
works in this way:
:foo.to_proc # => ->x{x.foo}
For example, suppose you write:
bar(&:foo)
The &
operator is combined with :foo
, which is not a Proc
instance, so implicit class cast applies Symbol#to_proc
to it, which gives ->x{x.foo}
. The &
now applies to this and is interpreted as a block, which gives:
bar{|x| x.foo}
Related Topics
What Are All the Common Ways to Read a File in Ruby
What's the Difference Between Uri.Escape and Cgi.Escape
Rhc Setup Gives Error 'No Such File Dl/Import'
How to Get a Specific Output Iterating a Hash in Ruby
How to Install Sqlite3 For Ruby on Windows
Difference Between Collection Route and Member Route in Ruby on Rails
What Are the Ruby Gotchas a Newbie Should Be Warned About
How to Get a Single Character Without Pressing Enter
Nokogiri/Xpath Namespace Query
Ruby Style: How to Check Whether a Nested Hash Element Exists
Optional Argument After Splat Argument
Reducing N+1 Queries Using the Bullet and Rspec Gems
Need a Simple Explanation of the Inject Method
Library Not Loaded: /Opt/Local/Lib/Libssl.1.0.0.Dylib (Loaderror)