04 · Methods¶
🎥 Video walkthrough¶
Defining methods¶
def add(a, b)
a + b # the last evaluated expression is returned automatically
end
def greet(name = "friend") # default argument
"Hello, #{name}!"
end
puts add(2, 3) # 5
puts greet # Hello, friend!
puts greet("Ada") # Hello, Ada!
Ruby methods return the value of their last evaluated expression by
default — an explicit return is only needed to exit early.
Explicit return¶
def safe_divide(a, b)
return "Cannot divide by zero" if b == 0 # early return, guard clause
a / b
end
puts safe_divide(10, 2) # 5
puts safe_divide(10, 0) # Cannot divide by zero
The question mark and bang naming conventions¶
Methods ending in ? are a strong convention (not enforced by the language)
for methods returning true/false. Methods ending in ! conventionally
indicate a "dangerous" variant — usually one that mutates the receiver in
place or could raise, as opposed to a safer non-bang counterpart:
name = "ada"
puts name.upcase # "ADA" -- returns a NEW string, `name` itself is unchanged
puts name # "ada" -- still lowercase
name.upcase! # mutates `name` itself, in place
puts name # "ADA"
Splat args (*args) and keyword args¶
def total(*numbers) # collects any number of positional args into an array
numbers.sum
end
puts total(1, 2, 3, 4) # 10
def make_profile(name:, city: "Unknown") # keyword args, `city` has a default
"#{name} from #{city}"
end
puts make_profile(name: "Ada", city: "London") # Ada from London
puts make_profile(name: "Grace") # Grace from Unknown
Keyword arguments must be passed by name (make_profile(name: "Ada")), which
makes call sites self-documenting compared to positional arguments — very
common in idiomatic Ruby method signatures with more than 1-2 parameters.
Double splat (**kwargs) — collecting arbitrary keyword args¶
def describe(**options)
options.each { |key, value| puts "#{key}: #{value}" }
end
describe(name: "Ada", role: "Engineer")
# name: Ada
# role: Engineer
Methods are called on objects (even top-level ones, implicitly on self)¶
def square(x)
x * x
end
puts square(5) # 25 -- implicitly called on the top-level `self`
# Methods can be referenced as objects with `method(...)`
op = method(:square)
puts op.call(4) # 16
Blocks as implicit method arguments (preview — full coverage in Module 8)¶
def apply_twice
yield(yield(5)) # `yield` calls the block passed to this method
end
result = apply_twice { |n| n * n }
puts result # 625 (5*5=25, then 25*25=625)
yield is how a method invokes a block passed to it without needing an
explicit parameter for it — this is the foundation for each, map, and
most of Ruby's iteration idioms, covered fully in
Module 8.
How It Actually Works¶
Calling obj.greet("world") is not direct dispatch to a function pointer —
MRI performs method lookup by walking obj's ancestor chain: first
obj's singleton class (if it has one), then its class, then each module
the class includes (in reverse inclusion order), then the superclass and
its included modules, and so on up to BasicObject. You can print this
chain yourself with obj.class.ancestors. Once found, MRI caches the
lookup result (an inline method cache keyed on the class) so repeated calls
on objects of the same class skip re-walking the chain — this is why
monkey-patching a class after methods have already been JIT-cached can
occasionally require cache invalidation, which MRI handles automatically
by bumping a global "class serial" number whenever any class is reopened.
Arguments themselves are passed by reference to the object, matching
ordinary variable assignment — Ruby has no separate call-by-value
semantics for method arguments.
Cheat sheet¶
| Feature | Syntax |
|---|---|
| Basic method | def name(a, b) ... end |
| Default argument | def name(a, b = 10) ... end |
| Keyword argument | def name(a:, b: 10) ... end |
| Splat (variadic) | def name(*args) ... end |
| Double splat | def name(**kwargs) ... end |
| Implicit return | Last expression's value, or explicit return |
| Predicate convention | name? returns true/false |
| Dangerous/mutating convention | name! |
🔀 See this in another language¶
Exercise¶
Write a method summarize(*values, precision: 2) that returns a hash with
:min, :max, and :average (rounded to precision decimal places) of the
given values. Then write a method shout!(s) that mutates a string in place
to be uppercase with an exclamation point appended (hint: use upcase! and
concat, or reassign via replace).