1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/doc/syntax/methods.rdoc
Marcus Stollsteimer 3fc53de5c9 methods.rdoc: Improve method definition documentation
* typos, grammar, formatting
* use `concrete_method` again in `regular_method` example,
  to better distinguish from `forwarding_method` example
* clarify that leading arguments before `...` require Ruby 3.0
2020-12-26 10:40:52 +01:00

699 lines
19 KiB
Text

= Methods
Methods implement the functionality of your program. Here is a simple method
definition:
def one_plus_one
1 + 1
end
A method definition consists of the +def+ keyword, a method name, the body of
the method, +return+ value and the +end+ keyword. When called the method will
execute the body of the method. This method returns +2+.
Since Ruby 3.0, there is also a shorthand syntax for methods consisting
of exactly one expression:
def one_plus_one = 1 + 1
This section only covers defining methods. See also the {syntax documentation
on calling methods}[rdoc-ref:syntax/calling_methods.rdoc].
== Method Names
Method names may be one of the operators or must start a letter or a character
with the eighth bit set. It may contain letters, numbers, an <code>_</code>
(underscore or low line) or a character with the eighth bit set. The convention
is to use underscores to separate words in a multiword method name:
def method_name
puts "use underscores to separate words"
end
Ruby programs must be written in a US-ASCII-compatible character set such as
UTF-8, ISO-8859-1 etc. In such character sets if the eighth bit is set it
indicates an extended character. Ruby allows method names and other identifiers
to contain such characters. Ruby programs cannot contain some characters like
ASCII NUL (<code>\x00</code>).
The following are examples of valid Ruby methods:
def hello
"hello"
end
def こんにちは
puts "means hello in Japanese"
end
Typically method names are US-ASCII compatible since the keys to type them
exist on all keyboards.
Method names may end with a <code>!</code> (bang or exclamation mark), a
<code>?</code> (question mark), or <code>=</code> (equals sign).
The bang methods (<code>!</code> at the end of the method name) are called and
executed just like any other method. However, by convention, a method with an
exclamation point or bang is considered dangerous. In Ruby's core library the
dangerous method implies that when a method ends with a bang (<code>!</code>),
it indicates that unlike its non-bang equivalent, permanently modifies its
receiver. Almost always, the Ruby core library will have a non-bang
counterpart (method name which does NOT end with <code>!</code>) of every bang
method (method name which does end with <code>!</code>) that does not modify
the receiver. This convention is typically true for the Ruby core library but
may or may not hold true for other Ruby libraries.
Methods that end with a question mark by convention return boolean, but they
may not always return just +true+ or +false+. Often, they will return an
object to indicate a true value (or "truthy" value).
Methods that end with an equals sign indicate an assignment method.
class C
def attr
@attr
end
def attr=(val)
@attr = val
end
end
c = C.new
c.attr #=> nil
c.attr = 10 # calls "attr=(10)"
c.attr #=> 10
Assignment methods can not be defined using the shorthand syntax.
These are method names for the various Ruby operators. Each of these
operators accepts only one argument. Following the operator is the typical
use or name of the operator. Creating an alternate meaning for the operator
may lead to confusion as the user expects plus to add things, minus to
subtract things, etc. Additionally, you cannot alter the precedence of the
operators.
<code>+</code> :: add
<code>-</code> :: subtract
<code>*</code> :: multiply
<code>**</code> :: power
<code>/</code> :: divide
<code>%</code> :: modulus division, String#%
<code>&</code> :: AND
<code>^</code> :: XOR (exclusive OR)
<code>>></code> :: right-shift
<code><<</code> :: left-shift, append
<code>==</code> :: equal
<code>!=</code> :: not equal
<code>===</code> :: case equality. See Object#===
<code>=~</code> :: pattern match. (Not just for regular expressions)
<code>!~</code> :: does not match
<code><=></code> :: comparison aka spaceship operator. See Comparable
<code><</code> :: less-than
<code><=</code> :: less-than or equal
<code>></code> :: greater-than
<code>>=</code> :: greater-than or equal
To define unary methods minus and plus, follow the operator with an
<code>@</code> as in <code>+@</code>:
class C
def -@
puts "you inverted this object"
end
end
obj = C.new
-obj # prints "you inverted this object"
The <code>@</code> is needed to differentiate unary minus and plus
operators from binary minus and plus operators.
You can also follow tilde and not (<code>!</code>) unary methods with
<code>@</code>, but it is not required as there are no binary tilde
and not operators.
Unary methods accept zero arguments.
Additionally, methods for element reference and assignment may be defined:
<code>[]</code> and <code>[]=</code> respectively. Both can take one or more
arguments, and element reference can take none.
class C
def [](a, b)
puts a + b
end
def []=(a, b, c)
puts a * b + c
end
end
obj = C.new
obj[2, 3] # prints "5"
obj[2, 3] = 4 # prints "10"
== Return Values
By default, a method returns the last expression that was evaluated in the body
of the method. In the example above, the last (and only) expression evaluated
was the simple sum <code>1 + 1</code>. The +return+ keyword can be used to
make it explicit that a method returns a value.
def one_plus_one
return 1 + 1
end
It can also be used to make a method return before the last expression is
evaluated.
def two_plus_two
return 2 + 2
1 + 1 # this expression is never evaluated
end
Note that for assignment methods the return value will be ignored when using
the assignment syntax. Instead, the argument will be returned:
def a=(value)
return 1 + value
end
p(self.a = 5) # prints 5
The actual return value will be returned when invoking the method directly:
p send(:a=, 5) # prints 6
== Scope
The standard syntax to define a method:
def my_method
# ...
end
adds the method to a class. You can define an instance method on a specific
class with the +class+ keyword:
class C
def my_method
# ...
end
end
A method may be defined on another object. You may define a "class method" (a
method that is defined on the class, not an instance of the class) like this:
class C
def self.my_method
# ...
end
end
However, this is simply a special case of a greater syntactical power in Ruby,
the ability to add methods to any object. Classes are objects, so adding
class methods is simply adding methods to the Class object.
The syntax for adding a method to an object is as follows:
greeting = "Hello"
def greeting.broaden
self + ", world!"
end
greeting.broaden # returns "Hello, world!"
+self+ is a keyword referring to the current object under consideration
by the compiler, which might make the use of +self+ in defining a class
method above a little clearer. Indeed, the example of adding a +hello+
method to the class +String+ can be rewritten thus:
def String.hello
"Hello, world!"
end
A method defined like this is called a "singleton method". +broaden+ will only
exist on the string instance +greeting+. Other strings will not have +broaden+.
== Overriding
When Ruby encounters the +def+ keyword, it doesn't consider it an error if the
method already exists: it simply redefines it. This is called
_overriding_. Rather like extending core classes, this is a potentially
dangerous ability, and should be used sparingly because it can cause unexpected
results. For example, consider this irb session:
>> "43".to_i
=> 43
>> class String
>> def to_i
>> 42
>> end
>> end
=> nil
>> "43".to_i
=> 42
This will effectively sabotage any code which makes use of the method
<code>String#to_i</code> to parse numbers from strings.
== Arguments
A method may accept arguments. The argument list follows the method name:
def add_one(value)
value + 1
end
When called, the user of the +add_one+ method must provide an argument. The
argument is a local variable in the method body. The method will then add one
to this argument and return the value. If given +1+ this method will
return +2+.
The parentheses around the arguments are optional:
def add_one value
value + 1
end
The parentheses are mandatory in shorthand method definitions:
# OK
def add_one(value) = value + 1
# SyntaxError
def add_one value = value + 1
Multiple arguments are separated by a comma:
def add_values(a, b)
a + b
end
When called, the arguments must be provided in the exact order. In other
words, the arguments are positional.
=== Default Values
Arguments may have default values:
def add_values(a, b = 1)
a + b
end
The default value does not need to appear first, but arguments with defaults
must be grouped together. This is ok:
def add_values(a = 1, b = 2, c)
a + b + c
end
This will raise a SyntaxError:
def add_values(a = 1, b, c = 1)
a + b + c
end
Default argument values can refer to arguments that have already been
evaluated as local variables, and argument values are always evaluated
left to right. So this is allowed:
def add_values(a = 1, b = a)
a + b
end
add_values
# => 2
But this will raise a +NameError+ (unless there is a method named
+b+ defined):
def add_values(a = b, b = 1)
a + b
end
add_values
# NameError (undefined local variable or method `b' for main:Object)
=== Array Decomposition
You can decompose (unpack or extract values from) an Array using extra
parentheses in the arguments:
def my_method((a, b))
p a: a, b: b
end
my_method([1, 2])
This prints:
{:a=>1, :b=>2}
If the argument has extra elements in the Array they will be ignored:
def my_method((a, b))
p a: a, b: b
end
my_method([1, 2, 3])
This has the same output as above.
You can use a <code>*</code> to collect the remaining arguments. This splits
an Array into a first element and the rest:
def my_method((a, *b))
p a: a, b: b
end
my_method([1, 2, 3])
This prints:
{:a=>1, :b=>[2, 3]}
The argument will be decomposed if it responds to #to_ary. You should only
define #to_ary if you can use your object in place of an Array.
Use of the inner parentheses only uses one of the sent arguments. If the
argument is not an Array it will be assigned to the first argument in the
decomposition and the remaining arguments in the decomposition will be +nil+:
def my_method(a, (b, c), d)
p a: a, b: b, c: c, d: d
end
my_method(1, 2, 3)
This prints:
{:a=>1, :b=>2, :c=>nil, :d=>3}
You can nest decomposition arbitrarily:
def my_method(((a, b), c))
# ...
end
=== Array/Hash Argument
Prefixing an argument with <code>*</code> causes any remaining arguments to be
converted to an Array:
def gather_arguments(*arguments)
p arguments
end
gather_arguments 1, 2, 3 # prints [1, 2, 3]
The array argument must appear before any keyword arguments.
It is possible to gather arguments at the beginning or in the middle:
def gather_arguments(first_arg, *middle_arguments, last_arg)
p middle_arguments
end
gather_arguments 1, 2, 3, 4 # prints [2, 3]
The array argument will capture a Hash as the last entry if a hash was sent by
the caller after all positional arguments.
def gather_arguments(*arguments)
p arguments
end
gather_arguments 1, a: 2 # prints [1, {:a=>2}]
However, this only occurs if the method does not declare any keyword arguments.
def gather_arguments_keyword(*positional, keyword: nil)
p positional: positional, keyword: keyword
end
gather_arguments_keyword 1, 2, three: 3
#=> raises: unknown keyword: three (ArgumentError)
Also, note that a bare <code>*</code> can be used to ignore arguments:
def ignore_arguments(*)
end
=== Keyword Arguments
Keyword arguments are similar to positional arguments with default values:
def add_values(first: 1, second: 2)
first + second
end
Arbitrary keyword arguments will be accepted with <code>**</code>:
def gather_arguments(first: nil, **rest)
p first, rest
end
gather_arguments first: 1, second: 2, third: 3
# prints 1 then {:second=>2, :third=>3}
When calling a method with keyword arguments the arguments may appear in any
order. If an unknown keyword argument is sent by the caller, and the method
does not accept arbitrary keyword arguments, an ArgumentError is raised.
To require a specific keyword argument, do not include a default value
for the keyword argument:
def add_values(first:, second:)
first + second
end
add_values
# ArgumentError (missing keywords: first, second)
add_values(first: 1, second: 2)
# => 3
When mixing keyword arguments and positional arguments, all positional
arguments must appear before any keyword arguments.
Also, note that <code>**</code> can be used to ignore keyword arguments:
def ignore_keywords(**)
end
To mark a method as accepting keywords, but not actually accepting
keywords, you can use the <code>**nil</code>:
def no_keywords(**nil)
end
Calling such a method with keywords or a non-empty keyword splat will
result in an ArgumentError. This syntax is supported so that keywords
can be added to the method later without affected backwards compatibility.
=== Keyword and Positional Argument Separation
Between Ruby 2.0 and 2.6, keyword and positional arguments were not
separated, and a keyword argument could be used as a positional argument
and vice-versa. In Ruby 3.0, keyword and positional arguments will
be separated if the method definition includes keyword arguments.
In Ruby 3.0, if the method definition does not include keyword arguments,
keyword arguments provided when calling the method will continue to be
treated as a final positional hash argument.
In Ruby 2.7, the keyword and positional arguments are not separated,
but cases where behavior will change in Ruby 3.0 will result in a
warning being emitted.
There are a few different types of keyword argument separation issues.
==== Conversion of Hash to Keywords
If a method is called with the hash, the hash could be treated as
keywords:
def my_method(**keywords)
keywords
end
my_method({a: 1}) # {:a => 1}
This occurs even if the hash could be an optional positional argument
or an element of a rest argument:
def my_method(hash=nil, **keywords)
[hash, keywords]
end
my_method({a: 1}) # [nil, {:a => 1}]
def my_method(*args, **keywords)
[args, keywords]
end
my_method({a: 1}) # [[], {:a => 1}]
However, if the hash is needed for a mandatory positional argument,
it would not be treated as keywords:
def my_method(hash, **keywords)
[hash, keywords]
end
my_method({a: 1}) # [{:a => 1}, {}]
==== Conversion of Keywords to Positional Arguments
If a method is called with keywords, but it is missing one
mandatory positional argument, the keywords are converted to
a hash and the hash used as the mandatory positional argument:
def my_method(hash, **keywords)
[hash, keywords]
end
my_method(a: 1) # [{:a => 1}, {}]
This is also true for empty keyword splats:
kw = {}
my_method(**kw) # [{}, {}]
==== Splitting of Positional Hashes or Keywords
If a method definition accepts specific keywords and not arbitrary keywords,
keywords or a positional hash may be split if the hash includes both Symbol
keys and non-Symbol keys and the keywords or positional hash are not needed
as a mandatory positional argument. In this case, the non-Symbol keys are
separated into a positional argument hash, and the Symbol keys are used
as the keyword arguments:
def my_method(hash=3, a: 4)
[hash, a]
end
my_method(a: 1, 'a' => 2) # [{"a"=>2}, 1]
my_method({a: 1, 'a' => 2}) # [{"a"=>2}, 1]
=== Block Argument
The block argument is indicated by <code>&</code> and must come last:
def my_method(&my_block)
my_block.call(self)
end
Most frequently the block argument is used to pass a block to another method:
def each_item(&block)
@items.each(&block)
end
If you are only going to call the block and will not otherwise manipulate it
or send it to another method using <code>yield</code> without an explicit
block parameter is preferred. This method is equivalent to the first method
in this section:
def my_method
yield self
end
=== Argument Forwarding
Since Ruby 2.7, an all-arguments forwarding syntax is available:
def concrete_method(*positional_args, **keyword_args, &block)
[positional_args, keyword_args, block]
end
def forwarding_method(...)
concrete_method(...)
end
forwarding_method(1, b: 2) { puts 3 }
#=> [[1], {:b=>2}, #<Proc:...skip...>]
Calling with forwarding <code>...</code> is available only in methods
defined with <code>...</code>.
def regular_method(arg, **kwarg)
concrete_method(...) # Syntax error
end
Since Ruby 3.0, there can be leading arguments before <code>...</code>
both in definitions and in invokations (but in definitions they can be
only positional arguments without default values).
def request(method, path, **headers)
puts "#{method.upcase} #{path} #{headers}"
end
def get(...)
request(:GET, ...) # leading argument in invoking
end
get('http://ruby-lang.org', 'Accept' => 'text/html')
# Prints: GET http://ruby-lang.org {"Accept"=>"text/html"}
def logged_get(msg, ...) # leading argument in definition
puts "Invoking #get: #{msg}"
get(...)
end
logged_get('Ruby site', 'http://ruby-lang.org')
# Prints:
# Invoking #get: Ruby site
# GET http://ruby-lang.org {}
Note that omitting parentheses in forwarding calls may lead to
unexpected results:
def log(...)
puts ... # This would be treated as `puts()...',
# i.e. endless range from puts result
end
log("test")
# Prints: warning: ... at EOL, should be parenthesized?
# ...and then empty line
== Exception Handling
Methods have an implied exception handling block so you do not need to use
+begin+ or +end+ to handle exceptions. This:
def my_method
begin
# code that may raise an exception
rescue
# handle exception
end
end
May be written as:
def my_method
# code that may raise an exception
rescue
# handle exception
end
Similarly, if you wish to always run code even if an exception is raised,
you can use +ensure+ without +begin+ and +end+:
def my_method
# code that may raise an exception
ensure
# code that runs even if previous code raised an exception
end
You can also combine +rescue+ with +ensure+ and/or +else+, without
+begin+ and +end+:
def my_method
# code that may raise an exception
rescue
# handle exception
else
# only run if no exception raised above
ensure
# code that runs even if previous code raised an exception
end
If you wish to rescue an exception for only part of your method, use +begin+ and
+end+. For more details see the page on {exception
handling}[rdoc-ref:syntax/exceptions.rdoc].