ZetCode

Ruby 函数

最后修改于 2025 年 4 月 2 日

函数(方法)是 Ruby 编程中的基本构建块。本指南涵盖了从基本方法定义到块、proc 和 lambda 等高级技术的所有内容。学习使用正确的参数处理和作用域管理来创建灵活、可重用的代码。掌握 Ruby 函数对于编写干净、可维护的代码至关重要。

基本方法定义

Ruby 方法使用 def 关键字后跟方法名称来定义。参数的括号在定义和调用中是可选的。除非使用显式 return,否则方法将返回其最后一个表达式的值。此示例展示了基本方法语法和调用约定。理解这些基础知识对于 Ruby 开发至关重要。

basic_methods.rb
# Simple method without parameters
def greet
  "Hello, world!"
end

# Method with parameters (parentheses optional)
def add_numbers num1, num2
  num1 + num2
end

# Method with explicit return
def max a, b
  return a if a > b
  b
end

puts greet                # => "Hello, world!"
puts add_numbers(3, 4)    # => 7
puts add_numbers 5, 6     # => 11 (parentheses optional)
puts max(10, 20)          # => 20

# Additional example: Method with default return
def no_return
  "This string is returned"
end
puts no_return            # => "This string is returned"

Ruby 方法在其定义和调用语法上都很灵活。greet 方法展示了一个无参数定义,而 add_numbers 演示了参数处理。max 方法使用显式 return 语句提前退出。

方法调用可以使用括号,或者在不产生歧义时省略它们。最后一个表达式的值会自动返回,如 no_return 示例所示。这种隐式返回行为使 Ruby 代码简洁而清晰。

方法参数

Ruby 提供了几种参数类型:必需、可选、默认值和可变长度。正确的参数处理使方法更加灵活和健壮。此示例演示了 Ruby 方法中的不同参数模式。理解这些选项有助于创建适应性强的接口。

parameters.rb
# Required parameters
def full_name(first, last)
  "#{first} #{last}"
end

# Default parameter values
def greet(name, greeting = "Hello")
  "#{greeting}, #{name}!"
end

# Variable-length arguments
def sum(*numbers)
  numbers.sum
end

# Keyword arguments (Ruby 2.0+)
def create_person(name:, age:, email: nil)
  { name: name, age: age, email: email }
end

puts full_name("John", "Doe")         # => "John Doe"
puts greet("Alice")                   # => "Hello, Alice!"
puts greet("Bob", "Hi")               # => "Hi, Bob!"
puts sum(1, 2, 3)                     # => 6
puts sum(4, 5, 6, 7)                  # => 22

person = create_person(name: "Eve", age: 30)
puts person.inspect                   # => {:name=>"Eve", :age=>30, :email=>nil}

# Additional example: Mixed parameters
def mixed(a, b = 2, *c, d:, e: 5)
  { a: a, b: b, c: c, d: d, e: e }
end
puts mixed(1, 3, 4, 5, d: 6).inspect # => {:a=>1, :b=>3, :c=>[4, 5], :d=>6, :e=>5}

firstlast 这样的必需参数必须始终提供。默认参数(greeting = "Hello")使参数成为可选。星号运算符 (*) 将可变参数收集到数组中,如 sum 所示。

关键字参数(Ruby 2.0+)提供带可选默认值(email: nil)的命名参数。mixed 示例结合了所有类型:必需、默认、可变长度和关键字参数。这种灵活性允许高度可定制的方法签名。

返回值和多个返回值

Ruby 方法可以返回多个值,这些值会自动转换为数组。return 关键字是可选的,但对于提前退出很有用。此示例演示了各种返回值模式。理解返回值行为有助于编写更具表现力的方法。

returns.rb
# Single value return
def square(x)
  x * x
end

# Multiple values return (as array)
def min_max(numbers)
  [numbers.min, numbers.max]
end

# Early return
def safe_divide(a, b)
  return "Cannot divide by zero" if b == 0
  a / b
end

# Implicit vs explicit return
def implicit_return
  "This is returned"
end

def explicit_return
  return "This is returned"
  "This is not"
end

puts square(5)                    # => 25
puts min_max([3, 1, 4, 2]).inspect # => [1, 4]
puts safe_divide(10, 2)           # => 5
puts safe_divide(10, 0)           # => "Cannot divide by zero"
puts implicit_return               # => "This is returned"
puts explicit_return               # => "This is returned"

# Additional example: Destructuring multiple returns
min, max = min_max([8, 3, 5, 2])
puts "Min: #{min}, Max: #{max}"   # => "Min: 2, Max: 8"

square 方法展示了简单的单返回值。min_max 以数组形式返回多个值,调用者可以对其进行解构。safe_divide 演示了用于错误处理的提前返回。

Ruby 始终返回最后评估的表达式,这使得 return 成为可选的。但是,显式返回可以提高复杂方法的清晰度。该示例显示了如何方便地将多个返回值分配给单独的变量。

块和 Yield

块是传递给方法的匿名代码块,可实现强大的自定义。yield 关键字执行传递的块。此示例演示了块的使用和控制流。掌握块是利用 Ruby 表现力的关键。

blocks.rb
# Simple block usage
def with_logging
  puts "Starting execution"
  yield
  puts "Completed execution"
end

with_logging { puts "Inside the block" }

# Block with parameters
def repeat(times)
  times.times { yield }
end

repeat(3) { puts "Hello!" }

# Block with return value
def transform(number)
  yield(number)
end

result = transform(5) { |x| x * 2 }
puts "Transformed result: #{result}" # => 10

# Checking for block given
def optional_block
  if block_given?
    yield
  else
    puts "No block provided"
  end
end

optional_block                  # => "No block provided"
optional_block { puts "Block!" } # => "Block!"

# Additional example: Block with multiple statements
def benchmark
  start = Time.now
  yield
  finish = Time.now
  puts "Execution time: #{finish - start} seconds"
end

benchmark { sleep(1) } # => "Execution time: ~1.0 seconds"

with_logging 方法用消息包装块执行。repeat 演示了向块传递参数。transform 显示了块如何将值返回到调用方法。

block_given? 检查是否提供了块,从而实现可选块。benchmark 示例测量块执行时间,显示块可以包含多个语句。块是 Ruby 迭代器和 DSL 功能的基础。

Procs 和 Lambdas

Procs 和 lambdas 是对象化的块,可以存储和传递。它们在参数处理和返回行为方面有所不同。此示例比较了这些可调用对象。理解它们可以解锁高级 Ruby 模式。

procs_lambdas.rb
# Creating a Proc
square = Proc.new { |x| x * x }
puts square.call(5)      # => 25

# Creating a Lambda
cube = lambda { |x| x ** 3 }
puts cube.call(3)        # => 27

# Alternative Lambda syntax
double = ->(x) { x * 2 }
puts double.call(4)      # => 8

# Differences in return behavior
def proc_return
  my_proc = Proc.new { return "Exiting proc" }
  my_proc.call
  "This won't be reached"
end

def lambda_return
  my_lambda = lambda { return "Exiting lambda" }
  my_lambda.call
  "This will be reached"
end

puts proc_return         # => "Exiting proc"
puts lambda_return       # => "This will be reached"

# Additional example: Passing procs to methods
def apply_operation(x, operation)
  operation.call(x)
end

puts apply_operation(10, square)  # => 100
puts apply_operation(10, cube)    # => 1000
puts apply_operation(10, double)  # => 20

Procs 使用 Proc.newproc 方法创建。Lambdas 使用 lambda 或 stabby 箭头语法 (->)。两者都可以使用 call[] 调用。

主要区别:lambdas 检查参数数量,而 procs 不检查;return 在 proc 中会退出封闭方法,而不仅仅是 proc。该示例展示了如何将两者都作为参数传递给其他方法。

方法对象

Ruby 方法可以使用 method 转换为 Method 对象,从而允许将它们像 procs 一样传递。此示例演示了方法对象及其用法。此技术支持高阶函数模式。

method_objects.rb
class Calculator
  def add(a, b)
    a + b
  end

  def multiply(a, b)
    a * b
  end
end

calc = Calculator.new
add_method = calc.method(:add)
multiply_method = calc.method(:multiply)

puts add_method.call(3, 4)        # => 7
puts multiply_method.call(3, 4)   # => 12

# Converting to proc
add_proc = add_method.to_proc
puts [1, 2, 3].map(&add_proc.curry(2).call(10)) # => [11, 12, 13]

# Additional example: Unbound methods
unbound_add = Calculator.instance_method(:add)
new_calc = Calculator.new
bound_add = unbound_add.bind(new_calc)
puts bound_add.call(5, 6)         # => 11

方法对象会保留其接收者,并可以稍后使用 call 调用。该示例展示了从 Calculator 实例中提取方法并在以后调用它们。

方法可以使用 to_proc 转换为 procs,从而可以与 map 等方法一起使用。未绑定的方法(没有接收者)可以绑定到新实例。这种灵活性支持高级元编程模式。

作用域和可见性

Ruby 方法具有不同的可见性级别:public、protected 和 private。实例变量具有对象作用域,而局部变量具有方法作用域。此示例演示了作用域规则和可见性修饰符。

scope.rb
class BankAccount
  def initialize(balance)
    @balance = balance
  end

  # Public method
  def deposit(amount)
    @balance += amount
    log_transaction("Deposit: #{amount}")
  end

  # Another public method
  def withdraw(amount)
    if amount <= @balance
      @balance -= amount
      log_transaction("Withdrawal: #{amount}")
    else
      puts "Insufficient funds"
    end
  end

  # Protected method
  protected
  def log_transaction(message)
    puts "[LOG] #{message}"
  end

  # Private method
  private
  def secret_processing
    puts "Secret bank processing"
  end
end

account = BankAccount.new(100)
account.deposit(50)      # Works (public)
# account.log_transaction("Test") # Error (protected)
# account.secret_processing      # Error (private)

# Additional example: Local variable scope
def scope_demo
  local_var = "I'm local"
  puts local_var
end

scope_demo               # => "I'm local"
# puts local_var         # Error: undefined local variable

公共方法在任何地方都可以访问,受保护的方法只能由类或其子类调用,私有方法只能在类上下文内调用。该示例展示了通过事务日志记录的正确封装。

实例变量(@balance)在对象内的多次方法调用中保持不变。局部变量(local_var)仅存在于其方法内。理解这些作用域规则可以防止变量泄漏和命名冲突。

函数式编程技术

Ruby 支持高阶函数、柯里化和组合等函数式编程模式。此示例演示了解决问题的函数式方法。这些技术能够实现更具声明性、可重用的代码。

functional.rb
# Higher-order function
def apply_math(x, y, operation)
  operation.call(x, y)
end

add = ->(a, b) { a + b }
multiply = ->(a, b) { a * b }

puts apply_math(3, 4, add)       # => 7
puts apply_math(3, 4, multiply)  # => 12

# Currying
power = ->(x, y) { x ** y }.curry
square = power.call(2)
cube = power.call(3)

puts square.call(5)              # => 25
puts cube.call(3)                # => 27

# Method composition
def double(x) = x * 2
def increment(x) = x + 1

composed = method(:double).to_proc >> method(:increment).to_proc
puts composed.call(5)            # => 11 (double then increment)

# Additional example: Recursion
def factorial(n)
  return 1 if n <= 1
  n * factorial(n - 1)
end

puts factorial(5)                # => 120

高阶函数接受或返回其他函数,如 apply_math 所示。柯里化(部分应用)从通用函数创建专用函数,例如从 power 创建 square

方法组合将操作链接在一起(>> 用于从右到左)。递归以 factorial 为例进行演示,尽管 Ruby 默认缺少尾调用优化。这些模式提供了强大的抽象工具。

Method Missing 和动态方法

Ruby 的 method_missing 钩子和 define_method 支持动态方法处理和创建。此示例展示了用于灵活 API 的元编程技术。这些高级功能为许多 Ruby DSL 提供支持。

dynamic.rb
class DynamicGreeter
  # method_missing for undefined methods
  def method_missing(name, *args)
    if name.to_s.start_with?('greet_')
      language = name.to_s.split('_').last
      "#{language.capitalize} greeting: Hello!"
    else
      super
    end
  end

  # respond_to_missing? should accompany method_missing
  def respond_to_missing?(name, include_private = false)
    name.to_s.start_with?('greet_') || super
  end

  # Dynamically define methods
  ['morning', 'afternoon', 'evening'].each do |time|
    define_method("greet_#{time}") do
      "Good #{time}!"
    end
  end
end

greeter = DynamicGreeter.new
puts greeter.greet_spanish      # => "Spanish greeting: Hello!"
puts greeter.greet_morning      # => "Good morning!"
puts greeter.greet_evening      # => "Good evening!"

# Additional example: send for dynamic dispatch
puts greeter.send(:greet_afternoon) # => "Good afternoon!"

method_missing 捕获对未定义方法的调用,从而实现诸如特定语言问候语之类的动态响应。respond_to_missing? 确保正确的方法内省。

define_method 以编程方式创建方法,如基于时间的问候语所示。send 以名称动态调用方法。这些技术支持灵活、适应性强的 API,但应谨慎使用。

最佳实践

使用有意义的方法名来指示其目的和副作用。偏好小型、单一职责的方法,而不是大型复杂的方法。对于参数多于两个的方法,请使用关键字参数。使用注释记录方法,描述其目的、参数和返回值。考虑可见性级别(public/protected/private)以强制执行正确的封装。

资料来源

从以下资源了解更多信息:Ruby 方法文档Proc 文档方法语法

作者

我的名字是 Jan Bodnar,我是一名充满激情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在教授编程方面拥有十多年的经验。