#---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.
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
You can provide default values for parameters in 2 ways:
=
in the function definition:def greet(name = "World")
"Hello #{name}!"
end
p greet # => "Hello World!"
p greet("Joe") # => "Hello Joe!"
def greet(name: "World")
"Hello #{name}!"
end
p greet # => "Hello World!"
p greet(name: "Joe") # => "Hello Joe!"
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!"
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!"
A Ruby function:
greet
function above looks like
it returns nothing (empty return
). But it returns
nil
.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 # => {}
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 # => {}
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.
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
even?
or has_permission?
!
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"