#---Functions---

Functions allow you to write reusable code that can be called multiple times with different inputs. They form the basic blocks of writing useful programs.

Basic Syntax

A function is defined using the def keyword. It can be defined to take parameters and return something. Once defined, it can be called by its name.

def add(a, b)
  puts "adding #{a} and #{b}…"
  a + b
end

Call the function by its name, passing the arguments:

result = add(41, 1)
p result # => 42

Parameters and Arguments

Default Parameters

You can provide default values for parameters in 2 ways:

  • By using the = in the function definition:
def greet(name = "World")
  "Hello #{name}!"
end
p greet # => "Hello World!"
p greet("Joe") # => "Hello Joe!"
  • By using the keyword argument syntax:
def greet(name: "World")
  "Hello #{name}!"
end
p greet # => "Hello World!"
p greet(name: "Joe") # => "Hello Joe!"

Positional and Keyword Parameters

The positional parameters way is available in all versions of Ruby. The downside is that you have to remember the order of the parameters when calling:

def greet(name, greeting)
  "#{greeting} #{name}!"
end
p greet("Joe", "Hello") # => "Hello Joe!"

It’s easy to get the order wrong here:

p greet("Hello", "Joe") # => "Joe Hello!"

The keyword parameters way is available since Ruby 2.0. Use it when you have multiple parameters and want to avoid confusion. You can pass arguments in any order:

def greet(name:, greeting:)
  "#{greeting} #{name}!"
end
p greet(name: "Joe", greeting: "Hello") # => "Hello Joe!"
p greet(greeting: "Hello", name: "Joe") # => "Hello Joe!"

Returning from a function

Ruby has a return keyword that can be used to exit a function and optionally send a value back to the caller.
But return is only used when you want to exit the function early. Otherwise, the function’s return value is always its last evaluated expression. return is optional there.

Here, greet returns “Hello Joe!” if you call it like greet("Joe").
But if you call it with “Bad Luck”, it won’t even return an empty string, it’ll just return nil. (We don’t welcome bad luck in here.)

def greet(name)
  return if name == "Bad Luck"
  return "OMG!" if name == "Exceptional Luck"
  "Hello #{name}" # `return` is optional here
end
p greet("Bad Luck") # => nil
p greet("Exceptional Luck") # => "OMG!"
p greet("Joe") # => "Hello Joe!"

What can a function return?

A Ruby function:

  • always returns a value:
    The first line in the greet function above looks like it returns nothing (empty return). But it returns nil.
  • always returns just one value:
    It can’t return multiple values. Well, it can. But it returns them as a single array.
def multiple_values
  return 1, 2, true, {} # same as: return [1, 2, true, {}]
end

The caller can capture the return value in a single variable:

result = multiple_values
p result # => [1, 2, true, {}]

Or it can unpack them into separate variables:

a, b, c, d = multiple_values
p a # => 1
p b # => 2
p c # => true
p d # => {}

Array Destructuring

Now, what if the number of variables on the left side don’t match the number of values returned by the function on the right side?

Here, the last 2 return values are ignored.

a, b = multiple_values
p a # => 1
p b # => 2

Here, all items except the first one are collected into an array:

a, *b = multiple_values
p a # => 1
p b # => [2, true, {}]

Here, all items except the last one are collected into an array:

*a, b = multiple_values
p a # => [1, 2, true]
p b # => {}
a, *b, c = multiple_values
p a # => 1
p b # => [2, true]
p c # => {}

Optional brackets

You can define a function without using brackets to enclose the parameters. It looks neat:

def add a, b
  puts "adding #{a} and #{b}…"
  a + b
end

But if the method calling involves a block, then this might raise an exception. So it’s best to always use brackets when defining a function.

You can also call a function without using brackets:

multiple_values
## is the same as
multiple_values()
add 41, 1
## is the same as
add(41, 2)

This allows for some nice expressive ruby code. Like:
has_one :account_history, through: :account
which is just:
has_one(:account_history, through: :account)
where has_one is a function that takes two arguments.

Methods

A function defined in a class or a module is called as a method.

class Robot
  def hi
    "Robot says hi"
  end
end
marvin = Robot.new
## `hi` is available on all instances of `Robot`:
meth = marvin.method(:hi)
p meth.owner # => Robot

Technically, you can only write methods in Ruby, not functions because a function defined outside a class or a module is still a part of the top-level Object class.

## This method looks like it doesn't belong to any class.
## But it belongs to the top-level `Object` class.
def hi
  "A different hi"
end
p method(:hi).owner # => Object

Notes

  • It’s idiomatic to name a function that returns true or false as a question, like even? or has_permission?
  • It’s also idiomatic to name a function with the exclamation mark ! suffix if it modifies the object on which it is called, like sort! or reverse!.
s = 'Hello'
p s.reverse # => "olleH"
p s # => "Hello"
p s.reverse! # => "olleH"
p s # => "olleH"
  • The words ‘parameter’ and ‘argument’ are often used interchangeably, but they are different: A parameter is a variable defined in the function to accept a value. An argument is the actual value passed to the function when it is called.