Proc类
Proc 类
https://www.kancloud.cn/imxieke/ruby-base/107309
Proc 类是什么
所谓 Proc
,就是使块对象化的类。Proc
与块的关系非常密切,下面,我们来看看如何创建与执行 Proc
对象。
Proc.new(...)
proc{...}
创建
Proc
对象的典型方法是通过Proc.new
方法,或者对proc
方法指定块。利用
Proc.new
方法,或者对proc
方法指定块,都可以创建代表块的Proc
对象。通过调用
Proc.call
方法执行块。调用Proc.call
方法时的参数会作为块变量,块中最后一个表达式的值则为Proc.call
的返回值。Proc.call
还有一个名称叫Proc#[]
。将块变量设置为 |* 数组 | 的形式后,就可以像方法参数一样,以数组的形式接收可变数量的参数。
除此以外,定义普通方法时可使用的参数形式,如默认参数、关键字参数等,几乎都可以被用于块变量的定义,并被指定给
Proc.call
方法。
lambda
Proc.new
、proc
等有另外一种写法叫 lambda
。与 Proc.new
、proc
一样,lambda
也可以创建 Proc
对象,但通过 lambda
创建的 Proc
的行为会更接近方法。
第一个不同点是,lambda
的参数数量的检查更加严密。对用 Proc.new
创建的 Proc
对象调用 call
方法时,call
方法的参数数量与块变量的数量可以不同。但通过 lambda
创建 Proc
对象时,如果参数数量不正确,程序就会产生错误。
第二个不同点是,lambda
可以使用 return
将值从块中返回。下面的代码中 power_of
方法会利用参数 n
返回“计算 x 的 n 次幂的 Proc 对象”。请注意,返回值并不是数值,而是进行运算的 Proc
对象。调用 power_of(3)
后,结果就会得到 call 方法参数值的 3 次幂的 Proc
对象。从 lambda
中返回值时使用了 return
,这里的 return
会将 lambda
中的值返回。
接下来,我们尝试用 Proc.new
方法改写代码。使用 Proc.new
方法时,在块中使用 return
后,程序就会跳过当前执行块,直接从创建这个块的方法返回。在本例中,即虽然块内的 return
应该从 power_of
方法返回,但由于程序运行时 power_of
方法的上下文会消失,因此程序就会出现错误。
不是 lambda
的普通块中的 return
,会从正在执行循环的方法返回。下面代码中的 prefix
方法会比较参数 ary
中的元素是否与 obj
相等,相等就返回在此之前的所有元素,不相等则返回空数组。第 6 行中的 return
并不会从块返回,而是跳过块,并作为 prefix
方法整体的返回值返回。
break
被用于控制迭代器的行为。这个命令会向接收块的方法的调用者返回结果值。如下所示,break []
会马上终止 Array.collect
方法,并将空数组作为 collent
方法的整体的返回值返回。
用
Proc.new
方法或者proc
方法创建的Proc
对象的情况下,由于这些方法都接收块,在调用Proc.call
方法的时候并没有适当的返回对象,因此就会发生错误。而lambda
的情况下则与return
一样,将值返回给Proc.call
方法。另一方面,由于next
方法的作用在于中断 1 次块的执行,因此无论如何创建Proc
对象,都可以将值返回给call
方法。
lambda
有另外一种写法——“->( 块变量 ){ 处理 }
”。块变量在 { ~ }
之前,看上去有点像函数。使用 ->
的时候,我们一般会使用 { ~ }
而不是 do ~ end
。
通过 Proc 参数接收块
在调用带块的方法时,通过 Proc
参数的形式指定块后,该块就会作为 Proc
对象被方法接收。下面代码在 total2
方法中,调用 total2
方法时指定的块,可以作为 Proc
对象从变量 block
中获取。
to_proc 方法
有些对象有 to_proc
方法。在方法中指定块时,如果以 & 对象的形式传递参数,对象 .to_proc
就会被自动调用,进而生成 Proc
对象。
其中,Symbol.to_proc
方法是比较典型的,并且经常被用到。例如,对符号 :to_i
使用 Symbol.to_proc
方法,就会生成下面那样的 Proc
对象。
这个对象在什么时候使用呢?例如,把数组的所有元素转换为数值类型时,一般的做法如下:
执行示例
上述代码还可以像下面这样写:
执行示例
按照类名排序的程序,也可以写成:
执行示例
熟悉这样的写法可能需要一定的时间,但这种写法不仅干净利索,而且意图明确。
Proc 的特征
虽然 Proc
对象可以作为匿名函数或方法使用,但它并不只是单纯的对象化。
第 1 行到第 6 行为 counter
方法的定义。该方法首先把作为计数器的本地变量 c
初始化为 0。然后每调用 1 次 Proc.call
方法,就将计数器加 1,并返回该 Proc
对象。在第 9 行中,调用 counter
方法,将 Proc
对象赋值给 c1
。可以看到,c1
调用 call
方法后,proc
对象引用的本地变量 c
开始计数了。在第 15 行中,以同样的方法创建新的计数器,之后计数器被重置。在最后的第 20 行中,再次调用最初创建的 c1
的 call
方法,计数器开始接着之前的结果计数。
通过这个例子我们可以看出,变量 c1
与变量 c2
引用的 Proc
对象,是分别保存、处理调用 counter
方法时初始化的本地变量的。与此同时,Proc
对象也会将处理内容、本地变量的作用域等定义块时的状态一起保存。
像 Proc
对象这样,将处理内容、变量等环境同时进行保存的对象,在编程语言中称为闭包(closure)。使用闭包后,程序就可以将处理内容和数据作为对象来操作。这和在类中描述处理本身、在实例中保存数据本质上是一样的,只是从写程序的角度来看,使用类的话当然也就意味着可以使用更多的功能。
就像刚才的计数器的例子那样,Proc
对象可被用来对少量代码实现的功能做对象化处理。另外,由于 Ruby 中大量使用了块,因此在有一定规模的程序开发中,我们就难免会使用到 Proc
对象。特别是像调用和传递带块的方法时的方法、通过闭包保存数据等功能,我们都需要透彻理解才行。
Proc 类的实例方法
prc.call(args, ...)
prc[args, ...]
prc.yield(args, ...)
prc.(args, ...)
prc === arg
上述方法都执行
Proc
对象 prc。由于受到语法的限制,通过
===
指定的参数只能为 1 个。大家一定要牢记这个方法会在Proc
对象作为case
语句的条件时使用。因此,在创建这样的Proc
对象时,比较恰当的做法是,只接收一个参数,并返回true
或者false
。下面的例子实现的是,从 1 到 100 的整数中,当值为 3 的倍数时输出
Fizz
,5 的倍数时输出Buzz
,15 的倍数时输出Fizz Buzz
,除此以外的情况下则输出该值本身。prc.arity
返回作为
call
方法的参数的块变量的个数。以|*args|
的形式指定块变量时,返回 -1。prc.parameters
返回关于块变量的详细信息。返回值为 [ 种类 , 变量名 ] 形式的数组的列表。
符号 | 意义
| -
:opt
| 可省略的变量:req
| 必需的变量:rest
| 以 *args 形式表示的变量:key
| 关键字参数形式的变量:keyrest
| 以 **args 形式表示的变量:block
| 块
prc.lambda?
判断 prc 是否为通过
lambda
定义的方法。prc.source_location
返回定义 prc 的程序代码的位置。返回值为 [ 代码文件名 , 行编号 ] 形式的数组。prc 由扩展库等生成,当 Ruby 脚本不存在时返回
nil
。执行示例