Ruby 基础教程第4版读书笔记 9 - Proc 类

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 对象。

Proc.new { |i| i.to_i }
%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

如果觉得我的文章对您有用,请在支付宝公益平台找个项目捐点钱。 @Victor Dec 9, 2014

奉献爱心