Saturday, May 7, 2016

Ruby Methods

A method is a way of grouping related code into a single container. In other words, methods are named chunks of code that can be reused. Usually, methods take some data (called input or arguments), does something with that input and gives back a result (called return or output).

Basic Syntax


We'll start by creating a simple method that does not take any arguments (input) nor returns anything. A methods definition begins with the def keyword and ends with an end keyword.


def say_hello
    puts "Hello!"
end

We can execute our method just by calling it's name:


say_hello
Output:

Hello!

Positional Arguments


Usually the terms parameter and argument are used interchangeably. In a stricter sense, a parameter (AKA formal parameter) is defined at the method declaration and an argument (AKA actual parameter) is the data provided to the method at calling time, which is "passed through" a parameter.

In the example below, we'll create a method that takes an argument called name. All we have to do is include the parameter name after the method name and it's value (passed at calling time) will be available inside the method:


def say_hello name
    puts "Hello, #{name}!"
end

say_hello "dear visitor"  # Call the method and provide the the string "dear visitor" as an argument
Output:

Hello, dear visitor!

We can also enclose the arguments in parentheses when calling a method, the output will be the same. Example:


say_hello("dear visitor")

Methods can also have multiple parameters and hence take multiple arguments, like this:


def greet time, name, *foo
    puts "Good #{time}, #{name}!"
end

greet "morning", "dear visitor"
Output:

Good morning, dear visitor!

Notice that when we called the method, the arguments were provided in the same order as the corresponding parameters were specified when the method was defined. These are called positional arguments.

Keyword Arguments


When passing a large number of arguments to a method, the positional syntax used above can cause confusion. For instance, if we forget to include an argument when calling the method, all following arguments will be passed through the wrong parameters. In these cases, it's usually better to use keyword arguments, which allows us to switch the order of the arguments without affecting the result. Keyword arguments are passed by name, instead of by position.

As of Ruby 2.1, the basic syntax is:


def greet time:, name: 
    puts "Good #{time}, #{name}!"
end

In the example above, notice the colons following the parameter names. That is what makes them keyword parameters (AKA keyword arguments) instead of positional parameters.

Now let's call the method providing arguments by name:


greet time: "morning", name: "dear visitor"
Output:

Good morning, dear visitor!

Optional keyword arguments were introduced in Ruby 2.0 and mandatory keyword arguments (as seen in the example above) are available as of Ruby 2.1.

Optional arguments and default values


When a method expects arguments and we don't provide them, or we provide the wrong number of arguments, an exception is thrown.


def greet time, name 
    puts "Good #{time}, #{name}!"
end

greet
Output:

ArgumentError: wrong number of arguments (given 0, expected 2)

We can use the built-in arity method to find out the number of arguments expected by a method:


method(:greet).arity
=> 2

When called on a method that takes mandatory arguments only, arity returns the number of expected arguments. When invoked on a method that takes both mandatory and optional arguments, arity returns -n-1 where n is the number of required arguments. So, a method with 3 required arguments and any number of optional arguments has an arity of -4.

Methods can also have optional arguments. When a default value is set, the argument becomes optional. Example:


def greet time="afternoon", name="dear visitor"
    puts "Good #{time}, #{name}!"
end


greet  # Call the method without providing any arguments

Output:

Good afternoon, dear visitor!


The method was executed without any errors because both arguments are optional, as we have assigned them default values. The same can be done with keyword arguments:


def greet time: "afternoon", name: "dear visitor"
    puts "Good #{time}, #{name}!"
end

greet  # Call the method without providing any arguments
Output:

Good afternoon, dear visitor!

Splat (*) operator


Methods can take any number of arguments by prefixing a parameter with a * (splat) or a ** (double splat) operator. This is useful when the exact number of arguments is unknown at method definition.

First let's look at the * (splat) operator. All arguments passed through the parameter prefixed with * are automatically stored in an array.


def list_masters *names
    print names
end

# Call the method and pass it three arguments
list_masters "Shunryu Suzuki", "Thich Nhat Hanh", "Kodo Sawaki"
Output:

["Shunryu Suzuki", "Thich Nhat Hanh", "Kodo Sawaki"]

Notice the output is an array.

There are other uses for the splat operator in Ruby, such as unwraping an array when passing it to a method, coercing values into arrays and deconstructing arrays, but these are outside the scope of this post.

Double splat (**) operator


Now let's look at the ** (double splat) operator. It was was introduced in Ruby 2.0 and works in a similar way to the single splat operator, except it stores the arguments in a hash instead of an array. Let's try it:


def meditate **details
    print details
end

# Call the method and pass it three arguments
meditate mat: "zabuton", cushion: "zafu", practice: "shikantaza", time: 40
Output:

{:mat=>"zabuton", :cushion=>"zafu", :practice=>"shikantaza", :time=>40}

Notice that the output is a hash.

Parameters prefixed with double splat only accept keyword arguments, such as minutes: 40 or meditation: "zazen".

Mixing * and ** operators


If a method includes both single splat and double splat parameters, non-keyword arguments will be taken by the parameter prefixed with * and hash arguments will be taken by the parameter prefixed with **. Example:


def meditate *practices, **details
    puts "Practices: #{practices}"
    puts "Details: #{details}"
end

meditate"zazen", "kinhin", duration: 40, time: "morning"
Output:

Practices: ["zazen", "kinhin"]
Details: {:duration=>40, :time=>"morning"}

Notice that the arguments passed through the *practices parameter were inserted into an array and arguments passed through the **details parameter were stored into a hash.

When a method includes both * and ** parameters, all of the positional arguments should be provided first, then the keyword arguments. If we try to call the above method and provide the keyword arguments first, a syntax error is thrown:


meditate time: "morning", duration: 40, "zazen", "kinhin"
Output:

SyntaxError: (irb):59: syntax error, unexpected ',', expecting =>
meditate time: "morning", duration: 40, "zazen", "kinhin"
                                                ^

The ampersand (&) operator at method definition


When the last parameter of a method is prefixed with the ampersand (&) operator, it means the method is expecting a block. As seen in the Introduction to Blocks, any method can take a block as an implicit argument. However, this is different. When we define a method with the last parameter prefixed with &, any block passed to the method will be converted into a proc (the to_p method is invoked under the hood) and assigned to the parameter.


def tester &foo
    puts "foo is a #{foo.class}"
    foo.call  # call the proc
end

tester { puts "Hello from the proc" }
Output:

foo is a Proc
Hello from the proc

Notice in the example above that the block (which is now a proc) was assigned to the foo variable. We would not be able to do that to a block as blocks are not objects and cannot be assigned to variables.

Even though the block is converted into a proc, it is also treated as the method's block, hence we can execute it with either call (as seen above) or yield. Example:


def tester &foo
    yield if block_given?
end

tester { puts "Hello from the block" }
Output:

Hello from the block

The ampersand (&) operator at method call


When calling a method, we can pass a proc as an argument and prefix it with the ampersand (&) operator. This will convert the proc into a block and pass it to the method.


def tester
  yield if block_given?
end

p = Proc.new { puts "Hello from the proc, converted to a block" }
tester &p
Output:

Hello from the proc, converted to a block

Lambdas are instances of the Proc class. Athough they handle arguments and return statements deifferently, they can also be passed to a method as an argument prefixed with & and converted into blocks.


l = lambda { puts "Hello from the lambda, converted to a block" }
tester &l
Output:

Hello from the lambda, converted to a block

Ampersand and object (&:method)


The & operator can also be used to pass an object as a block to a method, as in the following example:


arr = [ 1, 2, 3, 4, 5 ]

arr.map { |n| n.to_s } 
arr.map &:to_s

Both the examples above have the same result. In both, the map method takes the arr array and a block, then it runs the block on each element of the array. The code inside the block runs to_s on each element, converting it from integers to strings. Then, the map method returns a new array containing the converted items.

The first example is common and widely used. The second example may look a bit cryptic at first glance. Let's see what's happening:

In Ruby, items prefixed with colon (:) are symbols. If you are not familiar with the Symbol class/data type, I suggest you Google it and read a couple of articles before continuing. All method names in Ruby are internally stored as symbols. By prefixing a method name with a colon, we are not converting the method into a symbol, neither are we calling the method, we are just passing the name of the method around (referencing the method). In the example above, we are passing :to_s, which is a reference to the to_s method, to the ampersand (&) operator, which will create a proc (by calling to_proc under the hood). The proc takes a value as an argument, calls to_s on it and returns the value converted into a string.

Although the :to_s symbol is always the same, when running the map loop, it will refer to the to_s method of the class corresponding to each array item. If we passed an array such as [ 21, 4.453, :foobar, ] to the map method, the to_s method of the Fixnum class would be applied (called) on the first item, the to_s method of the Float class would be applied to the second item and the to_s method of the Symbol class would be applied to the third item. This makes sense because, as we learned above, we are not passing the actual to_s method to the ampersand operator, just its name.

Below is an example of creating a proc that takes an argument, calls a method on it and returns the result of the method.


p = :upcase.to_proc
p.call("foo bar")
Output:

 => "FOO BAR"

Let's review what is going on in arr.map &:to_s
  1. At each iteration of map, one item of the array (an integer) is passed to &:to_s
  2. The :to_s symbol (which is a reference to the to_s method) is passed to the & operator, which creates a proc that will take an argument (an array item), call to_s on the argument and return the value converted into string;
  3. The map method returns a new array containing the strings "1", "2", "3", "4" and "5".

Mixing all kinds of arguments


The method below includes all kinds of arguments discussed above. Notice that when mixing different kinds of parameters, they have to be included in the method definition in a specific order:
  1. Positional parameters (required and optional) and a single splat parameter, in any order;
  2. Keyword parameters (required and optional), in any order;
  3. Double splat parameter;
  4. Block parameter (prefixed with &);
The order above is somewhat flexible. We could define a method and begin the parameter list with a single splat argument, then a couple of optional positional arguments, and so on. Even though Ruby allows that, it's usually a very bad practice as the code would be hard to read and even harder to debug. It's usually best to use the following order:
  1. Required positional parameters;
  2. Optional positional parameters (with default values);
  3. Single splat parameter;
  4. Keyword parameters (required and optional, their order is irrelevant);
  5. Double splat parameter;
  6. Explicit block parameter (prefixed with &).
Let's see an example:


def meditate cushion, meditation="kinhin", *room_items, time: , posture: "kekkafuza", **periods, &b
    puts "We are practicing #{meditation}, for #{time} minutes, in the #{posture} posture (ouch, my knees!)."
    puts "Room items: #{room_items}"
    puts "Periods: #{periods}"
    b.call # Run the proc received through the &b parameter
end

meditate("zafu", "zazen", "zabuton", "incense", time: 40, period1: "morning", period2: "afternoon" ) { puts "Hello from inside the block" }
Output:

We are practicing zazen, for 40 minutes, in the kekkafuza posture (ouch, my knees!).
Room items: ["zabuton", "incense"]
Periods: {:period1=>"morning", :period2=>"afternoon"}
Hello from inside the block

Notice that when calling the method, we have:
  1. Provided the cushion mandatory positional argument;
  2. Overwritten the default value of the meditation optional positional argument;
  3. Passed a couple of extra positional arguments (zabuton and incense) through the *room_items parameter;
  4. Provided the time mandatory keyword argument;
  5. Omitted the posture optional keyword argument;
  6. Passed a couple of extra keyword arguments (period1: "morning", period2: "afternoon") through the **periods parameter;
  7. Passed the block { puts "Hello from inside the block" } through the &b parameter;
Please note the example above servers only to illustrate the possibility of mixing different types of parameters. Building a method like this in real code would be a bad practice. If a method needs that many arguments, it's probably best to split it into smaller methods. If it's absolutely necessary to pass that much data to a single method, we should probably create a class to store the data in a more organized way, then pass an instance of that class to the method as a single argument. BTW, I will soon publish a post on classes, so stay tuned.

Returning a result


What if, instead of displaying the method result, we want to store it in a variable and pass it around? When a method terminates normally (it is not interrupted by an exception), it returns a value.

In the following example, we have included a return statement within the method.


def say_hello name
    return "Hello, #{name}!"
end

greeting = say_hello "dear visitor"
puts greeting
Output:

Hello, dear visitor!

Unlike many other programming languages, Ruby methods do not require a return statement in order to return something. If the return statement is omitted, the method returns the value of the last evaluated expression (last statement).

Both the examples below will have the same result as the one above:


def say_hello name
    "Hello, #{name}!"
end

Anoter example:


def clap hands
    if hands == 2
        "You can hear the sound of two hands when they clap together."
    else
        "Now show me the sound of one hand."
    end
end

koan = clap 2
puts koan
koan = clap 1
puts koan
Output:

You can hear the sound of two hands when they clap together.
Now show me the sound of one hand.

Methods can take and execute blocks


Please see the post Introduction to Blocks in Ruby to learn about using blocks with methods.

Optional parentheses on method calls

Unlike other languages, in Ruby parentheses are not required when calling a method, except when the passed arguments are ambiguous without parentheses. Suppose we have the following methods:


def method1 arg1, arg2
    return "#{arg1} #{arg2}"
end

def method2 a1
    return a1
end

Notice that the m1 method takes two arguments and m2 takes one argument. Now, we will try to call them like this:


method1 "foo", method2 "bar"
Output:

SyntaxError: (irb):175: syntax error, unexpected tSTRING_BEG, expecting keyword_do or '{' or '('
method1 "foo", method2 "bar"
                        ^

The error above happened because our method call was ambiguous. We intended to pass method2 (together with the argument "bar") as an argument to method1. However, method1 had no way of knowing if: "foo", method2 and "bar" are 3 distinct arguments passed to method 1 OR "foo" is a distinct argument passed to method 1 but "bar" is an argument for "method2", in which case m2 and "bar" should be treated by method1 as a single argument.

By using parentheses, we eliminate the ambiguity and the error:


method1("foo", method2("bar"))
Output:

 => "foo bar" 

In Ruby, all functions are actually methods


Generally speaking, a method is a function defined inside a class. In Ruby, all functions are methods.

When our code is not defined inside any specific context/scope, such as a class or a module, we are in the "top-level context". In Ruby, this context is called main and it is an instance of a built-in class called Object. This can be easily verified:


puts self.class
 => Object

All functions defined in the top-level context are private instance methods of the built-in Object class. So all functions inevitably belong to a class, hence they are actually methods. Let's verify:


def some_method
end

Object.method_defined?(:some_method)
Output:

 => true  # This proves that some_method was implicitly associated with the built-in Object class. 

Methods are not objects


In his book The Ruby Programming Language, Yukihiro Matsumoto (the creator of Ruby, AKA Matz) explains that "Methods are a fundamental part of Ruby’s syntax, but they are not values that Ruby programs can operate on. That is, Ruby’s methods are not objects in the way that strings, numbers, and arrays are. It is possible, however, to obtain a Method object that represents a given method, and we can invoke methods indirectly through Method objects."

So, methods are not objects in Ruby. This means a method cannot be stored in a variable, nor take another method as an argument or return another method. However, a method can be represented as an instance of the built-in Method Class, created by a built-in method called method. That is actually easier done than said:


def say_hello
    puts "Hello"
end

m = method(:say_hello)  # Create object representation of the say_hello method
m.call  # Call the say_hello method object
Output:

Hello

Notice in the example above we have passed the name of the say_hello method to the method method as a symbol, hence the colon in :say_hello. As we already learned, Ruby stores the names of all methods as symbols.

If we call the method method multiple times, a new method object with a distinct object ID is created each time. Therefore, the method objects are not references to the original method (which is not an object and does not have an object ID of its own), they are distinct objects, created on the fly.


method(:say_hello).object_id # Output: => 7604420
method(:say_hello).object_id # Output: => 6886380
method(:say_hello).object_id # Output: => 6840900

After creating the method object above, we can change the original method but the Method object remains unaltered:


def say_hello
    puts "Goodbye"
end

m.call
Output:

Hello

The examples above serve only to illustrate that methods are not objects, but Ruby provides an object representation for them. Calling a method through its object representation is less efficient than calling it directly. Most of the times, when we need to assign a method to a variable and pass it around, a Lambda will do the job in a simpler and more efficient way.

Methods are not closures


A closure has the following characteristics:
  • It remembers the values of all variables that were available in the scope it was defined, even when called on another scope or when those variables are no longer available.
  • It is an object. Hence it can be assigned to a variable, passed around, called from within different scopes, etc.
In Ruby, methods are not closures, neither are the Method objects. As stated by Yukihiro Matsumoto (Matz) in the book as mentioned earlier "One important difference between Method objects and Proc objects is that Method objects are not closures. Ruby's method are intended to be completely self-contained, and they never have access to local variables outside of their scope. The only binding retained by a Method object, therefore, is the value of self - the object on which the method is to be invoked."

In short, methods do not meet either of the two criteria above required to be a closure.

Naming conventions


Conventionally, method names begin with a lowercase letter and, when containing more than one word, use an underscore to separate words, as in "my_method" (AKA snake case).

Methods with names ending in question mark (?) usually return boolean values (true or false) and those ending in exclamation point (!) are generally mutator methods, which means they alter the received object instead of returning a new object. There are exceptions, though. In the context of a method name, question mark and exclamation point are just regular characters.

Thank you for reading.

2 comments:

  1. Maybe it's because I'm a sole developer, but I completely missed the introduction of mandatory keyword arguments in 2.1. That's a feature I've missed from other languages. Thanks for highlighting that.

    Is there a situation where the Method Class would be useful? I can't think of how it would be helpful to store a Ruby method as a variable, especially if it isn't a closure. There are other ways to send a method from a symbol.

    ReplyDelete
    Replies
    1. Thank you for reading and providing feedback. I come from a Python background. In Python all methods are objects and this feature is used mostly in metaprogramming (e.g., factory methods that return other methods). Can't say I miss that in Ruby, as there are alternatives such as lambdas and procs. Also, as you said, we can dynamically call methods by passing their names as symbols.

      Delete