多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
## 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`传递给了它。 `&&lt;name_of_proc&gt;`将为数组中的每个元素调用 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 = -&gt; (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`。 &lt;sup class="footnote"&gt;[ [49](#_footnotedef_49 "View footnote.") ]&lt;/sup&gt; ### 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` &lt;sup class="footnote"&gt;[ [50](#_footnotedef_50 "View footnote.") ]&lt;/sup&gt; 变量中。 现在看到以`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`并打印出来。