Proc 类是什么
Proc 就是使块对象化的类。创建 Proc 对象通过 Proc.new
方法或者 proc
方法制定块都行。
hello1 = Proc.new do |name|
puts "hello, #{name}."
end
hello2 = proc do |name|
puts "Hello, #{name}."
end
hello1.call("World")
hello2.call("Ruby")
通过调用 Proc#call
方法执行块。参数会作为块变量,块中最后一个表达式的值则为 Proc#call
的返回值。 Proc#call
还有一个名称叫 Proc#[]
。
leap = Proc.new do |year|
year % 4 == 0 && year % 100 != 0 || year % 400 == 0
end
p leap.call(2000)
p leap[2013]
将块变量设置为 |*数组|
形式后,就可以像方法参数一样,以数组的形式接受可变数量的参数。除此以外,普通方法可使用的参数形式,比如默认参数,关键字参数等,也都支持。
double = Proc.new do |*args|
args.map { |i| i * 2 }
end
p double.call(1, 2, 3) #=> [2, 4, 6]
p double[2, 3, 4] #=> [4, 6, 8]
lambda
通过 lambda 创建的 Proc 的行为更接近方法。如果参数数量不正确,程序就会报错。我们叫它匿名方法。
prc1 = Proc.new do |a, b, c|
p [a, b, c]
end
prc1.call(1, 2) #=> [1, 2, nil]
prc2 = lambda do |a, b, c|
p [a, b, c]
end
prc2.call(1, 2) #=> ArgumentError: wrong number of arguments (2 for 3)
- lambda 可以使用
return
将值从块中返回。power_of 方法会利用参数 n 返回 计算 x 的 n 次幂的 Proc 对象。
- 使用 Proc.new 方法时,在块中使用
return
后,程序会跳过当前执行快,直接从创建这个块的方法中返回。
- 普通的代码块中的
return
,会从正在执行循环的方法返回。
def power_of(n)
lambda do |x|
return x * n
end
end
cube = power_of(3)
p cube.call(4) #=> 12
def power_of(n)
Proc.new do |x|
return x ** n
end
end
cube = power_of(3)
p cube.call(4) #=> LocalJumpError: unexpected return
通过 Proc 参数接受块
在调用带块的方法时,通过 Proc 参数的形式指定块后,该快就会作为 Proc 对象被方法接受。
def total(form, to, &block)
result = 0
from.upto(to) do |num|
if block
result += block.call(num)
else
result += num
end
end
return result
end
p total(1, 10) #=> 55
p total(1, 10) { |num| num ** 2 } #=> 385
to_proc 方法
有些对象有 to_proc
方法。在方法中指定块时,如果以 &对象
的形式传递参数,对象.to_proc
方法就会自动调用,进而生成 Proc 对象。
例如,对符号 :to_i
使用 Symbol#to_proc
方法,就会生成下面的 Proc 对象。
%w(42 39 56).map { |i| i.to_i }
%w(42 39 56).map(&:to_i)
Proc 的特征和闭包
- Proc 对象会将处理内容、本地变量的作用域等定义块时的状态一起保存。而像 Proc 对象这样,将处理内容、变量等环境同时进行保存的对象,叫闭包(closure)。
- 使用闭包后,程序就可以将处理内容和数据作为对象来操作。这一点看起来和类很像,但是类可以有更多的功能。
- 因此 Proc 对象可被用来对少量代码实现的功能做对象化处理。
def counter
c = 0
Proc.new { c += 1 } # 计数器加1,并返回 Proc 对象。
end
c1 = counter
p c1.call #=> 1
p c1.call #=> 2
p c1.call #=> 3
c2 = counter
p c2.call #=> 1
p c2.call #=> 2
def accumlator
total = 0
Proc.new do |num|
total += num
end
end
acc = accumlator
p acc.call(1) #=> 1
p acc.call(2) #=> 3
p acc.call(6) #=> 9
Proc 类的实例方法
下面的方法都执行 Proc 对象 prc。
prc.call(args, ...), prc[args, ...], prc.yield(args, ...), prc.(args, ...), prc === args
语法限制,通过 ===
指定的参数只能为 1 个,请 牢记 这个方法会在 Proc 对象作为 case
语句的条件时使用。在创建这样的 Proc 对象时,只接受一个参数,并返回 true 或 false。
prc = Proc.new { |a, b| a + b }
p prc.call(1, 2) #=> 3
p prc[3, 4] #=> 7
p prc.yield(5, 6) #=> 11
p prc.(7, 8) #=> 15
p prc === [9, 10] #=> 19
fizz = proc {|n| n % 3 == 0}
buzz = proc {|n| n % 5 == 0}
fizzbuzz = proc {|n| n % 3 == 0 && n % 5 == 0}
(1..100).each do |i|
case i
when fizzbuzz then puts "Fizz Buzz"
when fizz then puts "Fizz"
when buzz then puts "Buzz"
else puts 1
end
end
prc.arity
返回作为 call
方法的参数的块变量的个数。以 |*args|
形式指定块变量时,返回 -1。
prc.parameters
返回关于块变量的详细信息。返回值为 [种类,变量名]
形式的数组的列表。
prc.lambda?
判断 prc 是否为通过 lambda 定义的方法。
prc.source_location
返回定义 prc 的程序代码的位置。返回值为 [代码文件名,行编号]
形式的数组。脚本不存在时候返回 nil。
prc0 = Proc.new { nil }
prc1 = Proc.new { |a, b| a + b }
prc2 = Proc.new { |*args| args }
p prc0.arity #=> 0
p prc1.arity #=> 2
p prc2.arity #=> -1
prc0 = Proc.new { nil }
prc1 = Proc.new { |a, b| a + b }
prc2 = Proc.new { |*args| args }
prc3 = Proc.new { |a: 1, **b| [a, b] }
p prc0.parameters #=> []
p prc1.parameters #=> [[:opt, :a], [:opt, :b]]
p prc2.parameters #=> [[:rest, :args]]
p prc3.parameters #=> [[:key, :a], [:keyrest, :b]]
Proc#parameters
返回的变量种类
符号 |
意义 |
:opt |
可省略的变量 |
:req |
必需的变量 |
:rest |
以 *args 形式表示的变量 |
:key |
关键字参数形式的变量 |
:keyrest |
以 **args 形式表示的变量 |
:block |
块 |