## 20\. Proc,Lambda 和块
如果你了解某些编程语言,则可能听说过闭包。 Proc 和 Blocks 是类似的事情。 你可以采用一段代码,将其粘贴在`do` `end`块之间,并将其分配给变量。 该变量包含一段代码,可以像对象一样进行操作并传递。
Proc 就像一个函数,但是它是一个对象。 让我们看一个例子来了解什么是 Proc。 在文本编辑器中输入程序 [proc.rb](code/proc.rb) 并执行。
```rb
#!/usr/bin/ruby
# proc.rb
say_hello = Proc.new do
puts "Hello world!"
end
say_hello.call
say_hello.call
```
这就是输出的样子
```rb
Hello world!
Hello world!
```
现在让我们遍历代码以理解它。 看一下以下几行
```rb
say_hello = Proc.new do
puts "Hello world!"
end
```
在这种情况下,你将使用单个 Ruby 语句`puts “Hello World!”`,并将其放在 do 和 end 之间。 通过将`Proc.new`附加在`do`(该块的开始)之前,可以使此代码成为 Proc。 你正在将 Proc 对象分配给名为`say_hello`的变量。 现在`say_hello`可以看作是包含一个程序的东西。
现在如何调用或执行代码? 当我们需要调用名为`say_hello`的 Proc 片段时,我们编写以下命令
```rb
say_hello.call
```
在 [proc.rb](code/proc.rb) 中,我们两次调用`say_hello`,因此得到两个`Hello World!`作为输出。
### 20.1。 传递参数
像函数一样,你可以将参数传递给 Proc。 要查看其工作方式,请键入程序 [proc_hello_you.rb](code/proc_hello_you.rb) 并执行它。
```rb
#!/usr/bin/ruby
# proc_hello_you.rb
hello_you = Proc.new do |name|
puts "Hello #{name}"
end
hello_you.call "Peter"
hello_you.call "Quater"
```
执行后,程序将提供以下输出。
```rb
Hello Peter
Hello Quater
```
看一下这段代码
```rb
hello_you = Proc.new do |name|
puts "Hello #{name}"
end
```
在上面的程序中。 请注意,我们在`do`关键字之后捕获了一些东西,通过给`do |name|``我们将已传递到`Proc`块的内容放入名为`name`的变量中。 现在,可以在 Proc 块中的任何位置使用此变量来执行某些操作。
在`puts "Hello #{name}"`中,我们打印`Hello`,后跟传递的参数`name`。 因此,我们编写了一个 Proc,可以接受传递给它的某些东西。 我们如何调用并传递一些东西? 好吧,请注意结尾处的语句,看看第一个,它说
```rb
hello_you.call "Peter"
```
`hello_you.call`调用要执行的 Proc 代码。 我们将字符串`“Peter”`传递给 Proc,将该字符串复制到 Proc 中的变量`name`中,因此得到输出`Hello Peter`。
当 Ruby 解释器遇到`hello_you.call "Quarter"`时,它以类似的方式打印`Hello Quater`。
### 20.2。 将 Proc 传递给方法
就像任何对象一样,我们可以将 Proc 传递给方法。 看看下面的代码( [proc_to_method.rb](code/proc_to_method.rb) )。 研究,编码并执行
```rb
#!/usr/bin/ruby
# proc_to_method.rb
# An exampleof passing a proc to method
def execute_proc some_proc
some_proc.call
end
say_hello = Proc.new do
puts "Hello world!"
end
execute_proc say_hello
```
这就是输出的样子。
```rb
Hello world!
```
现在让我们分析`execute_proc`功能的代码,其代码如下
```rb
def execute_proc some_proc
some_proc.call
end
```
我们接受一个称为`some_proc`的参数,我们将其假定为 Proc。 然后,我们使用其自己的`call`方法执行该函数,即在函数中,我们仅使用`some_proc.call`对其进行调用,以执行传递的 Proc。 如果你看一下接下来的几行,我们将创建一个名为`say_hello`的 Proc
```rb
say_hello = Proc.new do
puts "Hello world!"
end
```
我们在`say_hello` Proc 中所做的只是打印`Hello world!`。 现在我们调用方法`execute_proc`,并在下面的代码段中传递`say_hello`
```rb
execute_proc say_hello
```
通过 Proc 之后,它将复制到`some_proc`参数,并且在执行或调用`some_proc`时,将忠实地打印`Hello world!`。
### 20.3。 从函数返回 Proc
我之前写过,Proc 可以被视为对象。 实际上,Proc 是一个对象。 我们可以将其传递给我们在上一节中看到的函数,现在我们可以看到一个从函数返回 Proc 的示例。 仔细阅读下面给出的代码( [proc_returning_it.rb](code/proc_returning_it.rb) )。
```rb
#!/usr/bin/ruby
# proc_returning_it.rb
# Function that returns a proc
def return_proc
Proc.new do |name|
puts "The length of your name is #{name.length}"
end
end
name_length = return_proc
name_length.call "A.K.Karthikeyan"
```
执行后,程序将抛出以下输出
```rb
The length of your name is 15
```
查看功能`return_proc`。 在其中,我们创建一个新的 Proc,它接受一个名为`name`的参数。 假设名称是一个字符串,我们只需打印其长度即可。 现在考虑此函数之后的代码,如下所示:
```rb
name_length = return_proc
name_length.call "A.K.Karthikeyan"
```
在第一行`name_length = return_proc`中,`name_length`被分配了`return_proc`方法返回的内容。 在这种情况下,由于`Proc.new`是`return_proc`方法中的最后一个语句/块,因此它返回一个新的 Proc,该 Proc 被分配给`name_proc`。 因此,`name_proc`现在可以接受参数。 我们现在要做的就是先叫`name_proc`,然后叫`name`
```rb
name_length.call "A.K.Karthikeyan"
```
因此,`name_length`接受`name`作为参数,计算其长度并打印。 因此,此示例表明可以从函数返回 Proc。
### 20.4。 过程和数组
现在让我们看看如何将 Proc 与数组配合使用以对其进行过滤。 看下面的程序 [proc_and_array.rb](code/proc_and_array.rb) 。 在其中我们声明了一个名为`get_proc`的 Proc,它带有一个参数`num`。 在 Proc 中,如果`num`的偶数由以下语句`num unless num % 2 == 0`确保,则返回`num`。 运行程序并记下输出。
```rb
# proc_and_array.rb
get_odd = Proc.new do |num|
num unless num % 2 == 0
end
numbers = [1,2,3,4,5,6,7,8]
p numbers.collect(&get_odd)
p numbers.select(&get_odd)
p numbers.map(&get_odd)
```
输出量
```rb
[1, nil, 3, nil, 5, nil, 7, nil]
[1, 3, 5, 7]
[1, nil, 3, nil, 5, nil, 7, nil]
```
现在让我们考虑以下三个语句
```rb
p numbers.collect(&get_odd)
p numbers.select(&get_odd)
p numbers.map(&get_odd)
```
在其中,我们只考虑第一行`p numbers.collect(&get_odd)`,因此我们在变量`numbers`中存储了一个数字数组,在`numbers`中调用了`collect`,将参数`&get_odd`传递给了它。 `&<name_of_proc>`将为数组中的每个元素调用 Proc,Proc 返回的所有内容将被收集到一个新数组中。 `p`确保将值打印出来供我们验证。
如果仔细观察,`p numbers.collect(&get_odd)`和`p numbers.map(&get_odd)`都会返回其中包含`nil`值的数组,而 select 过滤掉`nil`并返回剩余的值。
#### 20.4.1。 拉姆达
Lambda 就像 Procs。 期望两个几乎没有区别。 我将在这里解释其中一个,另外一个将在[第二个区别](#_the_second_difference)部分中说明。
好吧,现在看一个小程序
```rb
# lambda.rb
print_hello = lambda do
puts "Hello World!"
end
print_hello.call
```
Output
```rb
Hello World!
```
要了解该程序的工作原理,请阅读 Proc,Lambda 和 Blocks。
### 20.5。 将参数传递给 Lambda
执行以下程序。
```rb
# lambda_passing_argment.rb
odd_or_even = lambda do |num|
if num % 2 == 0
puts "#{num} is even"
else
puts "#{num} is odd"
end
end
odd_or_even.call 7
odd_or_even.call 8
```
Output
```rb
7 is odd
8 is even
```
要了解其工作方式,请参见本章的[传递参数](#_passing_parameters)部分。
### 20.6。 具有函数的 Proc 和 Lambda
好的,Proc 和 Lambda 有什么区别。 它们之间有两个主要区别,这是第一个。 在下面的示例[中,calling_proc_and_lambda_in_function.rb](code/calling_proc_and_lambda_in_function.rb) 我们具有两个函数,即`calling_lambda`和`calling_proc`,请在你的计算机上键入并运行此文件
```rb
# calling_proc_and_lambda_in_function.rb
def calling_lambda
puts "Started calling_lambda"
some_lambda = lambda{ return "In Lambda" }
puts some_lambda.call
puts "Finished calling_lambda function"
end
def calling_proc
puts "Started calling_proc"
some_proc = Proc.new { return "In Proc" }
puts some_proc.call
puts "In calling_proc function"
end
calling_lambda
calling_proc
```
Output
```rb
Started calling_lambda
In Lambda
Finished calling_lambda function
Started calling_proc
```
你将看到如上所示的输出。 因此,让我们继续执行它。 首先调用`calling_lambda`功能时,程序将通过执行`puts "Started calling_lambda"`打印`Started calling_lambda`。 接下来,我们定义一个新的 lambda `some_lambda`,并使用以下几行代码进行调用
```rb
some_lambda = lambda{ return "In Lambda" }
puts some_lambda.call
```
因此,当调用`some_lambda`时,它将返回`"In Lambda”`,该行将在第`puts some_lambda.call`行中打印。
然后执行函数`puts "Finished calling_lambda function"`中的最终语句,为我们提供以下输出
```rb
Started calling_lambda
In Lambda
Finished calling_lambda function
```
函数完成时。
接下来,我们调用函数`calling_proc`,我们希望它的行为类似于`call_lambda`,但事实并非如此! 那会怎样呢? 从输出中我们只知道`puts "Started calling_proc"`被执行了,之后呢? 好吧,请看下一行`some_proc = Proc.new { return "In Proc" }`,注意它有一个 return 语句。 当在函数中调用 Proc 并具有 return 语句时,它将终止该函数并返回 return 的值,就好像函数本身正在返回它一样! 而 lambda 不会这样做。
即使在函数中调用的 lambda 且被调用的 lambda 具有 return 语句,它也会在控件被调用后将控件传递给函数的下一行,而在 proc 调用中则不然,它只是退出返回自身函数的函数 重视价值(因为该功能很难返回)。
### 20.7。 第二个区别
在上一节中,我写了 Proc 和 Lambda 之间的一个区别,让我们在这里看到第二个区别。 查看下面的代码(在 irb 中执行)。
```rb
>> lambda = -> (x) { x.to_s }
=> #<Proc:0x00000001f65b70@(irb):1 (lambda)>
>> lambda.call
ArgumentError: wrong number of arguments (0 for 1)
from (irb):1:in `block in irb_binding'
from (irb):2:in `call'
from (irb):2
from /home//karthikeyan.ak/.rvm/rubies/ruby-2.1.3/bin/irb:11:in `<main>'
```
我已经使用 irb 来演示该示例。 在上面的代码中,我们在以下语句`lambda = -> (x) { x.to_s }`中定义了一个 Lambda,现在我们可以使用以下语句 lambda.call 对其进行调用,如你所见,因为我们有一个参数`x`,并且没有传递任何内容给 Lambda 引发异常并对此进行抱怨。 现在让我们尝试一下
```rb
>> proc = Proc.new { |x| x.to_s}
=> #<Proc:0x00000001a17470@(irb):3>
>> proc.call
=> ""
```
因此,如你在上面看到的那样,是否应将参数传递给 Proc,如果不传递参数,则在不提供参数的情况下调用 Proc,Proc 不会抱怨,而是将其视为`nil`。 <sup class="footnote">[ [49](#_footnotedef_49 "View footnote.") ]</sup>
### 20.8。 Lambda 和数组
执行以下程序
```rb
# lambda_and_array.rb
get_odd = lambda do |num|
num unless num%2 == 0
end
numbers = [1,2,3,4,5,6,7,8]
p numbers.collect(&get_odd)
p numbers.select(&get_odd)
p numbers.map(&get_odd)
```
Output
```rb
[1, nil, 3, nil, 5, nil, 7, nil]
[1, 3, 5, 7]
[1, nil, 3, nil, 5, nil, 7, nil]
```
要了解其工作原理,请阅读本章的 [Proc 和数组](#_proc_and_arrays)部分。
#### 20.8.1。 块和功能
我们已经看到了 Procs 以及如何将它们传递给方法和函数。 现在让我们看一下块以及如何将它们传递给函数。 键入示例 [blocks_in_methods.rb](code/blocks_in_methods.rb) 并执行它
```rb
# blocks_in_methods.rb
def some_method *args, &block
p args
block.call
end
some_method 1, 3, 5, 7 do
puts "boom thata"
end
```
Output
```rb
[1, 3, 5, 7]
boom thata
```
现在让我们看一下代码。 在代码中,我们在以下行`def some_method *args, &block`中定义了一个名为`some_method`的方法。 请注意,我们正在接受`*args`中的所有参数,并且有一个名为`&block`的新名称将被包含在代码块中。 你可以将&块替换为其他变量,例如`&a`或`&something`或任何你喜欢的变量。
现在,忘记功能主体中的内容。 现在让我们看一下函数的调用,如下所示
```rb
some_method 1, 3, 5, 7 do
puts "boom thata"
end
```
因此,我们调用`some_method`并传递参数`1, 3, 5, 7`。 这将以数组形式存储在`*args` <sup class="footnote">[ [50](#_footnotedef_50 "View footnote.") ]</sup> 变量中。 现在看到以`do`开头和以`end`结尾,在这两者之间,你可以具有任意数量的语句,换句话说,它是一个代码块。 我们只有一个声明`puts "boom thata"`,就是这样。 此代码块将放入`&block`变量。 现在在 some_method 中注意以下语句
```rb
def some_method *args, &block
p args
block.call
end
```
我们仅使用`block.call`而不是`&block.call`来调用该块,这一点很重要。 当我们在块上使用`call`方法时,该块将被执行,并且输出`“boom thata”`被打印出来。
现在让我们看另一个例子,我们可以将变量传递给块调用。 输入以下示例并执行。
```rb
# blocks_in_methods_1.rb
def some_method *args, &block
p args
block.call 7
end
some_method 1, 3, 5, 7 do |number|
puts "boom thata\n" * number
end
```
Output
```rb
[1, 3, 5, 7]
boom thata
boom thata
boom thata
boom thata
boom thata
boom thata
boom thata
```
请注意,在`some_method`定义中,我们已使用`block.call 7`调用了 Block,数字 7 到哪里去了? 好吧,看下面几行
```rb
some_method 1, 3, 5, 7 do |number|
puts "boom thata\n" * number
end
```
之后,我们使用`|number|`捕获传递的变量,因此 7 被存储在`number`中。 在块内,我们将`"boom thata\n"`乘以`number`并打印出来。
- 前言
- 红宝石
- 先决条件
- 1.安装 Ruby
- 2.在线资源
- 3.入门
- 4.比较与逻辑
- 5.循环
- 6.数组
- 7.哈希和符号
- 8.范围
- 9.功能
- 10.可变范围
- 11.类&对象
- 12.安全导航
- 13.打破大型程序
- 14.结构和 OpenStruct
- 15. Rdoc
- 16. Ruby 样式指南
- 17.模块和混入
- 18.日期和时间
- 19.文件
- 20. Proc,Lambda 和块
- 21.多线程
- 22.异常处理
- 23.正则表达式
- 24.宝石
- 25.元编程
- 26.基准
- 27.测试驱动开发
- 28.观察者模式
- 29.模板模式
- 30.工厂模式
- 31.装饰图案
- 32.适配器模式
- 33.单例模式
- 34.复合模式
- 35.建造者模式
- 36.策略模式
- 赞助商
- 捐
- 人们怎么说
- 版权
- 取得这本书