Ruby - block概念與相關寫法

張凱喬
11 min readMay 17, 2018

--

Ruby寫久了
就會看到越來越多前輩
利用一些方式能將程式碼縮得很簡潔

我覺得這個就是融會貫通之後
會幫助思考邏輯更輕巧、更有效率

以下整理一些應該要學沒學好的

從基礎的block談起

block是用來暫存及傳遞程式碼的地方
它本身不是物件(但有方法可以讓它變物件,最後說明)
所以必須掛在別人身上(可能是別的方法、別的物件)

我們在rails裡面常寫以下這種代碼

@users.each do |u|
...
end

這種結構我們稱作為block
目的是用來傳一段程式碼給.each方法
代表@users的每一個物件都會執行一次block的內容

另外一種簡化的寫法(當你的程式碼很短的時候可以縮成一行)

@users.each{|u| ...}

所以可以看的出來 do…end 就是代表 {...}
然後裡面的|u| 就是在程式碼中利用yeild方法傳遞過來的值

參考為你自己學ruby on rails 一書的內容
來自己創一個.times方法

def my_times(n)
i = 0
while n > i
i += 1
yield i*2 #對應到block裡面的||
end
end
my_times(3) { |num| #i*2就會yield到這邊
puts "hello, #{num}xRuby"
}
#輸出 2xRuby 4xRuby 6xRuby

再提供一個例子
多個yield值 與 傳值 互動

def test_method(pass_value)
puts "pass: #{pass_value}" #下方呼叫之後 pass: 2
yield("a statement:", pass_value*3) #傳2*3給下面的block
end
test_method(2){|a,b| puts a+"#{b*4}"} #a statement: 24 (2*3*4)

可以自由回傳任意值,再讓使用者自訂動作

each是我們最常用的,其他的還有哪些
舉例一些比較難的

Map 方法

Map會對 list 這個陣列裡的每一個元素做某件事之後再收集成一個新的陣列,對map也可以使用

直接上程式碼

array=["a","b","c"]
puts array.map{|a| a+"p"} #返回一個陣列["ap","bp","cp"]

然後對hash也有用,第一個返回的值是key,第二個返回的值是value
如果只有設一個值 他就會餵一個[key,value]陣列給你

hash={"a"=>1,"b"=>2,"c"=>3}
puts hash.map{|a| a} #返回一個陣列["a",1,"b",2,"c",3]
puts hash.map{|a| a.to_s}
#返回一個陣列["["a",1]","["b",2]","["c",3]"]
#代表|a| 是一個含有key與value的陣列

這邊常見的問題,有些人想要用hash.map來做一個新的hash
但這就違背了map原本的設定(map就是用來做新的陣列)

但還是有方法可以達成

pair={"a"=>1,"b"=>2,"c"=>3}
Hash[pair.map{|key,value| [key, value*2] } ]
#產生一個新的hash {"a"=>2,"b"=>4,"c"=>6}

至於block的應用是很靈活的
可以利用do end做block,再餵新的值來做陣列

array=["a","b","c"]
array_2 = array.map do
|v|
if v=="a" or v=="b" #如果值是a或b,就產生true,否則false
true
else
false
end
end
#array_2產生新的陣列 [true,true,false]

這邊要留意的是block不是function,不適用return的方法
block就只是預設回傳最後一個值

Reduce方法

Reduce覺得又更難了一些,他是一個用來做疊代、累加的工具

參考上面的連結,來思考下面的程式碼
reduce(initial) { |memo, obj| block } → obj

最常用、最完整的用法就是上面這串
reduce方便的用法就是有一個暫存值 memo,在疊代過程中保留,最後返回
然後最前面initial就是這個暫存值的初始值(也常用來設定型態)

最基本的應用就是把值全部sum起來

(1..8).reduce(0) { |sum, num| sum += num }
#最後返回 36 ,也就是從1加到8
["a","b","c"].reduce("") { |sum, num| sum+=num+"p" }
#最後返回字串 "apbpcp"
#這邊的initial不可省略,不然會出錯

當然,Reduce也會拿來處理array或hash
舉例如下

a = {"a"=>100, "b"=>200, "c"=>300}
#想要把每個value加總
a.reduce(0) { |sum, e| sum += e[1] } #600
#這邊返回的e 代表[key,value]陣列
array = [{"a"=>100}, {"b"=>200}, {"c"=>300}]
#想要把每個hash合併起來
array.inject({}) { |sum, e| sum.merge(e) }
#先inintial 讓block知道sum是雜湊型態
#e就代表一個雜湊 {key=>value}
#返回一個完整的hash {"a"=>100, "b"=>200, "c"=>300}

然後還有更極端的作法 就是reduce搭配symbol
(就是寫完之後 非ruby的工程師可能都不太懂?)

Proc

在講ruby block的時候,常常會看到Proc
但這兩個是不同層級的東西

block就只是一串程式碼的容器,他不能獨立運作
block可以藉由掛在function 或是 Proc,讓他執行容器中的程式碼

再繼續深入談,Proc是可以讓程式碼轉換成物件的方法
(廢話好多,總結就是Proc是物件、block不是)

p = Proc.new { puts "Hello World" } #使block物件化
p.call #用call來呼叫 # prints 'Hello World'
p.class # returns 'Proc'
a = p # a now equals p, a Proc instance
a # returns a proc object '#<Proc:0x007f96b1a60eb0@(irb):46>'

上面這個案例展現了p成為proc物件
而且可以傳遞,也就是a = p
(這邊要記住function不能傳遞,這也是proc的優勢)

再來,我們在上面有用block示範times的簡單寫法
但其實ruby原生寫法並不是傳block而已
而是傳proc進去

我們會常看到&block這個寫法
譬如在rails使用的form_tag,就是會掛一個&block在裡面

這個&的意思,就是把掛在方法後面的block轉成proc
通常會再搭配 block_given?的方法,來判斷block存在與否

def f2(n, &p)
if block_given?
p[n] # call proc p
# 'p[n]' can be alternated with 'p.call(n)'
# 'yield n' also works
else
puts 'no block'
end
end

f2(2){|n| n.times{puts 'iam block'}} #印2次'iam block'
f2(345) #印出 'no block'(無論傳甚麼n進去都不影響)

提醒這個&block 都會放在參數的最後

以下參考大神

另一個會一起談的就是lambda
lambda跟proc一樣都是屬於proc物件

lam = lambda { puts “Hello World” }

只是在使用上有些不一樣
譬如lambda會要求傳入的值須符合要求
(lam = lambda { |x| puts x },你就只能傳一個值進去)

附上簡單說明

更多實用技巧 參考JC大

--

--

No responses yet