[ruby]Ruby中的方法

183 阅读5分钟

方法声明

采用如下语法:

def output_something(value)
  puts value
end 

也可以省略参数列表周围的括号:

def output_something value
  puts value
end

方法体中的最后一个表达式的结果会作为函数的返回值。因此,return关键字并不是必须得。不过,return关键字仍有用处,如表示满足某些条件下,提前从方法体中退出。需要注意的是,在Block内使用return,会直接从方法中退出,而不是从Block中退出,如果需要从Block中退出,需要使用break,例子:

six = (1..10).each {|i| break i if i > 5} # six值为6

方法调用

采用如下语法:

method_name(parameter1, parameter2)
同样,括号可以省略:
method_name parameter1, parameter2

默认参数

声明方法时,可以指定参数的默认值:

def foo( j, i = 7)
  return i + j
end

变长参数列表

声明方法时,在参数前添加星号(*),表示该方法接受变成的参数。变长参数会以数组的形式存储,如:

def calculate_value(x,y,*otherValues)
  puts otherValues
end
  
calculate_value(1,2,'a','b','c') # otherValues的值为['a', 'b', 'c']

在调用方法时,可以用数组作为参数传入方法,并在数组前添加星号(*)。这样,数组会被打开,并将元素逐个赋值给方法参数,如:

arr = [1, 2, 'a', 'b', 'c']
calculate_value(*arr) # x的值为1,y的值为2,otherValues的值为['a', 'b', 'c']

调用方法时,还可以传递哈希,这样,我们同时拥有了命名参数及可变长的参数列表,如:

def accepts_hash( var )
  print "got: ", var.inspect
end
  
accepts_hash :arg1 => 'giving arg1', :argN => 'giving argN'
# 输出 {:argN=>"giving argN", :arg1=>"giving arg1"}

关键字参数

从Ruby 2.0开始,方法声明/调用支持关键字参数,如:

def test_method(a, b, c:true, d:false) 
   puts  a,b,c,d
end

调用的时候,我们可以:
test_method(1, 2)
test_method(1, 2, c:someValue, d:someOtherValue)
test_method(1, 2, d:someValue, c:someOtherValue)

关键字参数的应用场景就是,如果有很多可选的参数,那么应用关键字参数调用方法,可读性更高。

理解Ruby中的Block,Proc和方法

Ruby借鉴了函数式编程中的闭包(Closure),高阶函数(High-Order Function)及头等函数(First-Class Function)。在Ruby中,这几个概念分别称作:Block,Proc及方法(Method)。这3个概念相近但又有区别,容易混淆。

Proc

Proc就是绑定了一组局部变量的代码块。一旦绑定完成,代码块可以在其他上下文中调用,而且仍能访问到那些绑定的局部变量。Proc充当Ruby中函数(Function)的角色,或者更准确的叫法-函数对象,社区中又称为functor。

Wikipedia中的闭包(Closure)被定义为能够访问自己语义上下文中的变量的函数。因此,Proc也可视作Ruby中的闭包。

Ruby中创建Proc有2种方法:

  1. 通过Proc.new方法
  2. 通过Kernel方法 - lambda

这2个方法在创建Proc的时候,有如下2个区别:

  1. 参数校验:Proc.new不会进行参数校验;lambda方式会对参数个数进行校验。
  2. return行为:对Proc.new创建的Proc 代码块内添加return语句,会直接作为包含的函数本身的return行为;lambda创建的Proc代码块中的return语句更符合直观的理解,返回给调用者。

针对区别1,举例子:

pnew = Proc.new {|x, y| puts x + y}
lamb = lambda {|x, y| puts x + y}

# 输出 6
pnew.call(2, 4, 11)
  
# 抛异常 ArgumentError
lamb.call(2, 4, 11)

针对区别2,举例子:

def try_ret_procnew
  ret = Proc.new { return "Baaam" }
  ret.call
  "This is not reached"
end
  
# 打印 "Baaam"
puts try_ret_procnew

def try_ret_lambda
  ret = lambda { return "Baaam" }
  ret.call
  "This is printed"
end
  
# prints "This is printed"
puts try_ret_lambda

针对Proc的调用,除了使用call方法外,还可以使用[]方法,如:

say = lambda {|something| puts something}
say.call("Hello")

# 如下调用会与call方法效果一样
say["Hello"]

方法(Method)

方法也是代码块,但是不会与同语义下的变量绑定。想法,方法会绑定到对象实例上,并可访问对象实例的属性。

class Boogy
  def initialize
    @id = 15
  end
  
  def arbo
    puts "The id is #{@id}"
  end
end

# 初始化Boogy实例
b = Boogy.new
  
b.arbo # 输出 "The id is 15

可以将方法调用理解为向对象发送消息。实际上,Ruby中的Object对象提供了send方法,以下3种调用arbo方法的方式,效果是一样的。

# 直接针对对象实例调用方法
b.arbo

# 通过Object#send方法向对象发送消息
b.send("arbo")
  
# 方法名既可以用字符串传递,也可以用Symbol传递
b.send(:arbo)

实际上,Ruby中我们可以定义top-level层级的方法,这些方法看似没有被放到任何用户定义的类里,其实Ruby会将它们放入Object类中。不过,日常使用中,我们仍然可以将这种top-level的方法理解为独立的方法,在其他语言(C或者Perl)中,这类方法称作函数。

def say (something)
  puts something
end
  
say "Hello"
Object.send(:say, "Hello") # 该行与上一行效果一样

Block

也是代码块,和Proc很类似。区别在于,Block不能独立存在,必须在绑定并转换为Proc后,才可以独立存在,并被执行。形象的比喻:Block像虫卵,Proc像昆虫。

将Block作为参数传递

当将Block作为最后一个参数传递给Ruby中的方法的时候,Ruby会将Block转换为匿名的Proc并传递给方法。方法内部,可以通过yield关键字执行Proc。

def do_twice
  yield 
  yield
end
do_twice {puts "Hola"}

也可以显示的将Proc作为参数传递给方法,如:

def do_twice(what)
  what.call
  what.call
end
do_twice lambda {puts "Hola"}

上述2种将Proc作为参数传递的方法皆可。

&符号的使用

在方法调用时,将Block追加在方法末尾,在方法体内部是没有办法索引到这个转换后的Proc的。不过,可以在参数列表的最后一个参数前加&符号,这样,末尾追加的Block在被转换成Proc后,方法体内部可以通过这个&参数访问,如:

def contrived(a, &f)
  # 可以通过f索引到Block
  f.call(a)
      
  # 同样可以通过yield传递参数给Block
  yield(a)
end
# this works
contrived(25) {|x| puts x}
  
# 下面将Proc作为参数传递,会导致抛出ArgumentError
# 因为&f并不算是一个参数,只是为了转换Block
contrived(25, lambda {|x| puts x})

Content mainly from 「Ruby Programming