## 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.策略模式
- 贊助商
- 捐
- 人們怎么說
- 版權
- 取得這本書