diff options
Diffstat (limited to 'doc/syntax/methods.rdoc')
| -rw-r--r-- | doc/syntax/methods.rdoc | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/doc/syntax/methods.rdoc b/doc/syntax/methods.rdoc new file mode 100644 index 0000000000..14810a188f --- /dev/null +++ b/doc/syntax/methods.rdoc @@ -0,0 +1,653 @@ += 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> :: OR +<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 keywords were +provided 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 + +You can also use a bare <code>*</code> when calling a method to pass the +arguments directly to another method: + + def delegate_arguments(*) + other_method(*) + 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 + +You can also use <code>**</code> when calling a method to delegate +keyword arguments to another method: + + def delegate_keywords(**) + other_method(**) + 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. + +If a method definition does not accept any keywords, and the +<code>**nil</code> syntax is not used, any keywords provided when calling +the method will be converted to a Hash positional argument: + + def meth(arg) + arg + end + meth(a: 1) + # => {:a=>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 + +You are not required to give a name to the block if you will just be passing +it to another method: + + def each_item(&) + @items.each(&) + 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 invocations (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]. |
