Simple method declarations include a comma-separated list of argument names (in optional parentheses) after the method name. But there is much more to Ruby’s method arguments. The subsections that follow explain:
How to declare an argument that has a default value, so that the argument can be omitted when the method is invoked
How to declare a method that accepts any number of arguments
How to simulate named method arguments with special syntax for passing a hash to a method
How to declare a method so that the block associated with an invocation of the method is treated as a method argument
When you define a method, you can specify default values for some or all of the parameters. If you do this, then your method may be invoked with fewer argument values than the declared number of parameters. If arguments are omitted, then the default value of the parameter is used in its place. Specify a default value by following the parameter name with an equals sign and a value:
def prefix(s, len=1) s[0,len] end
This method declares two parameters, but the second one has a default. This means that we can invoke it with either one argument or two:
prefix("Ruby", 3) # => "Rub" prefix("Ruby") # => "R"
Argument defaults need not be constants: they may be arbitrary expressions, and can refer to instance variables and to previous parameters in the parameter list. For example:
# Return the last character of s or the substring from index to the end def suffix(s, index=s.size-1) s[index, s.size-index] end
Parameter defaults are evaluated when a method is invoked rather
than when it is parsed. In the following method, the default value
[]
produces a new empty array on
each invocation, rather than reusing a single array created when the
method is defined:
# Append the value x to the array a, return a. # If no array is specified, start with an empty one. def append(x, a=[]) a << x end
In Ruby 1.8, method parameters with default values must appear after all ordinary parameters in the parameter list. Ruby 1.9 relaxes this restriction and allows ordinary parameters to appear after parameters with defaults. It still requires all parameters with defaults to be adjacent in the parameter list—you can’t declare two parameters with default values with an ordinary parameter between them, for example. When a method has more than one parameter with a default value, and you invoke the method with an argument for some, but not all, of these parameters, they are filled in from left to right. Suppose a method has two parameters, and both of those parameters have defaults. You can invoke this method with zero, one, or two arguments. If you specify one argument, it is assigned to the first parameter and the second parameter uses its default value. There is no way, however, to specify a value for the second parameter and use the default value of the first parameter.
Sometimes we want to write methods that can accept an
arbitrary number of arguments. To do this, we put an *
before one of the method’s parameters. Within the body of the
method, this parameter will refer to an array that contains the zero
or more arguments passed at that
position. For example:
# Return the largest of the one or more arguments passed def max(first, *rest) # Assume that the required first argument is the largest max = first # Now loop through each of the optional arguments looking for bigger ones rest.each {|x| max = x if x > max } # Return the largest one we found max end
The max
method requires at least one argument, but it may accept any
number of additional arguments. The first argument is available
through the first
parameter. Any
additional arguments are stored in the rest
array. We can invoke max
like this:
max(1) # first=1, rest=[] max(1,2) # first=1, rest=[2] max(1,2,3) # first=1, rest=[2,3]
Note that in Ruby, all Enumerable
objects automatically have a max
method, so the method defined here is
not particularly useful.
No more than one parameter may be prefixed with an *
. In Ruby 1.8, this parameter must appear
after all ordinary parameters and after all parameters with defaults
specified. It should be the last parameter of the method, unless the
method also has a parameter with an &
prefix (see below). In Ruby 1.9, a
parameter with an *
prefix must
still appear after any parameters with defaults specified, but it may
be followed by additional ordinary parameters. It must also still
appear before any &
-prefixed
parameter.
We’ve seen how *
can
be used in a method declaration to cause multiple
arguments to be gathered or coalesced into a single array. It can
also be used in a method invocation to scatter, expand, or explode
the elements of an array (or range or enumerator) so that each
element becomes a separate method argument. The *
is sometimes called the splat operator,
although it is not a true operator. We’ve seen it used before in the
discussion of parallel assignment in Parallel Assignment.
Suppose we wanted to find the maximum value in an array (and
that we didn’t know that Ruby arrays have a built-in max
method!). We could pass the elements
of the array to the max
method
(defined earlier) like this:
data = [3, 2, 1] m = max(*data) # first = 3, rest=[2,1] => 3
Consider what happens without the *
:
m = max(data) # first = [3,2,1], rest=[] => [3,2,1]
In this case, we’re passing an array as the first and only
argument, and our max
method
returns that first argument without performing any comparisons on
it.
The *
can also be used with
methods that return arrays to expand those arrays for use in another
method invocation. Consider the polar
and cartesian
methods defined earlier in this chapter:
# Convert the point (x,y) to Polar coordinates, then back to Cartesian x,y = cartesian(*polar(x, y))
In Ruby 1.9, enumerators are splattable objects. To find the largest letter in a string, for example, we could write:
max(*"hello world".each_char) # => 'w'
When a method definition includes parameters with default values or a
parameter prefixed with an *
, the
assignment of argument values to parameters during method invocation
gets a little bit tricky.
In Ruby 1.8, the position of the special parameters is restricted so that argument values are assigned to parameters from left to right. The first arguments are assigned to the ordinary parameters. If there are any remaining arguments, they are assigned to the parameters that have defaults. And if there are still more arguments, they are assigned to the array argument.
Ruby 1.9 has to be more clever about the way it maps arguments to
parameters because the order of the parameters is no longer
constrained. Suppose we have a method that is declared with o
ordinary parameters, d
parameters with default values, and one
array parameter prefixed with *
,
and that these parameters appear in some arbitrary order. Now assume
that we invoke this method with a
arguments.
If a
is less than o
, an ArgumentError
is raised; we have not
supplied the minimum required
number of arguments.
If a
is greater than or equal
to o
and less than or equal to
o+d
, then the leftmost a–o
parameters with defaults will have
arguments assigned to them. The remaining (to the right) o+d–a
parameters with defaults will not have
arguments assigned to them, and will just use their default
values.
If a
is greater than o+d
, then the array parameter whose name is
prefixed with an *
will have
a–o–d
arguments stored in it;
otherwise, it will be empty.
Once these calculations are performed, the arguments are mapped to parameters from left to right, assigning the appropriate number of arguments to each parameter.
When a method requires more than two or three arguments, it can be difficult for the programmer invoking the method to remember the proper order for those arguments. Some languages allow you to write method invocations that explicitly specify a parameter name for each argument that is passed. Ruby does not support this method invocation syntax, but it can be approximated if you write a method that expects a hash as its argument or as one of its arguments:
# This method returns an array a of n numbers. For any index i, 0 <= i < n, # the value of element a[i] is m*i+c. Arguments n, m, and c are passed # as keys in a hash, so that it is not necessary to remember their order. def sequence(args) # Extract the arguments from the hash. # Note the use of the || operator to specify defaults used # if the hash does not define a key that we are interested in. n = args[:n] || 0 m = args[:m] || 1 c = args[:c] || 0 a = [] # Start with an empty array n.times {|i| a << m*i+c } # Calculate the value of each array element a # Return the array end
You might invoke this method with a hash literal argument like this:
sequence({:n=>3, :m=>5}) # => [0, 5, 10]
In order to better support this style of programming, Ruby
allows you to omit the curly braces around the hash literal if it is
the last argument to the method (or if the only argument that follows
it is a block argument, prefixed with &
). A hash without braces is sometimes
called a bare hash, and when we use one it looks
like we are passing separate named arguments, which we can reorder however we like:
sequence(:m=>3, :n=>5) # => [0, 3, 6, 9, 12]
As with other ruby methods, we can omit the parentheses, too:
# Ruby 1.9 hash syntax sequence c:1, m:3, n:5 # => [1, 4, 7, 10, 13]
If you omit the parentheses, then you must omit the curly braces. If curly braces follow the method name outside of parentheses, Ruby thinks you’re passing a block to the method:
sequence {:m=>3, :n=>5} # Syntax error!
Recall from Iterators and Enumerable Objects that a block is a
chunk of Ruby code associated with a method invocation, and that an
iterator is a method that expects a block. Any method invocation may
be followed by a block, and any method that has a block associated
with it may invoke the code in that block with the yield
statement. To refresh your memory, the following code is a
block-oriented variant on the sequence
method developed earlier in the
chapter:
# Generate a sequence of n numbers m*i + c and pass them to the block def sequence2(n, m, c) i = 0 while(i < n) # loop n times yield i*m + c # pass next element of the sequence to the block i += 1 end end # Here is how you might use this version of the method sequence2(5, 2, 2) {|x| puts x } # Print numbers 2, 4, 6, 8, 10
One of the features of blocks is their anonymity. They are not
passed to the method in a traditional sense, they have no name, and
they are invoked with a keyword rather than with a method. If you
prefer more explicit control over a block (so that you can pass it on
to some other method, for example), add a final argument to your
method, and prefix the argument name with an ampersand.[*] If you do this, then that argument will refer to the
block—if any—that is passed to the method. The value of the argument
will be a Proc
object, and instead
of using yield
, you
invoke the call
method of
the Proc
:
def sequence3(n, m, c, &b) # Explicit argument to get block as a Proc i = 0 while(i < n) b.call(i*m + c) # Invoke the Proc with its call method i += 1 end end # Note that the block is still passed outside of the parentheses sequence3(5, 2, 2) {|x| puts x }
Notice that using the ampersand in this way changes only the method definition. The method invocation remains the same. We end up with the block argument being declared inside the parentheses of the method definition, but the block itself is still specified outside the parentheses of the method invocation.
Twice before in this chapter, we’ve said that a special kind of parameter must be the last one in the parameter list. Block arguments prefixed with ampersands must really be the last one. Because blocks are passed unusually in method invocations, named block arguments are different and do not interfere with array or hash parameters in which the brackets and braces have been omitted. The following two methods are legal, for example:
def sequence5(args, &b) # Pass arguments as a hash and follow with a block n, m, c = args[:n], args[:m], args[:c] i = 0 while(i < n) b.call(i*m + c) i += 1 end end # Expects one or more arguments, followed by a block def max(first, *rest, &block) max = first rest.each {|x| max = x if x > max } block.call(max) max end
These methods work fine, but notice that you can avoid the
complexity of these cases by simply leaving your blocks anonymous and
calling them with yield
.
It is also worth noting that the yield
statement still works in a method
defined with an &
parameter.
Even if the block has been converted to a Proc
object and passed as an argument, it
can still be invoked as an anonymous block, as if the block argument
was not there.
We saw earlier that you can use *
in a method definition to specify that
multiple arguments should be packed into an array, and that you can
use *
in a method invocation to
specify that an array should be unpacked so that its elements become
separate arguments. &
can also be used in definitions and invocations. We’ve
just seen that &
in a method
definition allows an ordinary block associated with a method
invocation to be used as a named Proc
object inside the method. When
&
is used before a Proc
object in a method invocation, it
treats the Proc
as if it was an
ordinary block following the invocation.
Consider the following code which sums the contents of two arrays:
a, b = [1,2,3], [4,5] # Start with some data. sum = a.inject(0) {|total,x| total+x } # => 6. Sum elements of a. sum = b.inject(sum) {|total,x| total+x } # => 15. Add the elements of b in.
We described the inject
iterator earlier in Enumerable Objects. If you
don’t remember, you can look up its documentation with ri Enumerable.inject
. The important thing
to notice about this example is that the two blocks are identical.
Rather than having the Ruby interpreter parse the same block twice,
we can create a Proc
to represent
the block, and use the single Proc
object twice:
a, b = [1,2,3], [4,5] # Start with some data. summation = Proc.new {|total,x| total+x } # A Proc object for summations. sum = a.inject(0, &summation) # => 6 sum = b.inject(sum, &summation) # => 15
If you use &
in a
method invocation, it must appear before the last argument in the
invocation. Blocks can be associated with any method call, even when
the method is not expecting a block, and never uses yield
. In the same way, any method
invocation may have an &
argument as its last argument.
In a method invocation an &
typically appears before a Proc
object. But it is actually allowed
before any object with a to_proc
method. The Method
class (covered
later in this chapter) has such a method, so Method
objects can be passed to iterators
just as Proc
objects can.
In Ruby 1.9, the Symbol
class defines a to_proc
method,
allowing symbols to be prefixed with &
and passed to iterators. When a
symbol is passed like this, it is assumed to be the name of a
method. The Proc
object returned
by the to_proc
method invokes the
named method of its first argument, passing any remaining arguments
to that named method. The canonical case is this: given an array of
strings, create a new array of those strings, converted to
uppercase. Symbol.to_proc
allows
us to accomplish this elegantly as follows:
words = ['and', 'but', 'car'] # An array of words uppercase = words.map &:upcase # Convert to uppercase with String.upcase upper = words.map {|w| w.upcase } # This is the equivalent code with a block
[*] We use the term “block argument” instead of “block
parameter” for method parameters prefixed with &
. This is because the phrase “block
parameter” refers to the parameter list (such as |x|
) of the block itself.
3.147.77.4