iT邦幫忙

2021 iThome 鐵人賽

DAY 9
1
Modern Web

初階 Rails 工程師的養成系列 第 9

Day9. functional programming in Ruby - Block Part2

  • 分享至 

  • xImage
  •  

初來乍到Ruby世界的讀者們,絕對想不到原來Ruby 也有 curry, bind 等用法。這些語法對於JS的使用者應該很熟悉,在今年的IT鐵人賽就看到很多介紹bind, curry

closure

Closure是程式語言中很基本的概念,首先要先講Procclosure 的關係

https://ithelp.ithome.com.tw/upload/images/20210908/20115854RM85WJeGEs.png

Ruby的Block則是用{}do ... end 來做領域的展開,領域裡和領域外是完全不同的世界。外面的值沒有辦法干預到裡面,而裡頭的數值和運算也不會影響到外界。這就是closure的概念,我們稱為閉包。

沒有術式就不會有領域,而以 Ruby 來說,一共有下列幾種術式能夠展開領域。

#====== Proc
proc {}
Proc.new {}

#====== Proc(Lambda)
lambda {}
-> {}

#====== map, each...
[1, 2, 3].each {}

我們也可以自己做術式,而我們使用的術式為yield

yield

block_given?yield 的好朋友,所以我們先介紹block_given?

def apple_machine
  block_given? && 'block_used' || 'block_unused'
end

apple_machine    #=> block_unused
apple_machine {} #=> block_used

由上面的例子我們可以知道, block_given? 可以偵測apple_machine是否使用blockblock_given? 可以讓我們在有block或沒有block的情況下做應對,接著我們開始講yield

# 情境1
def apple_machine
  yield if block_given?
end

apple_machine { 1 }      #=> 1
apple_machine { 1 + 1 }  #=> 2
apple_machine { [1, 2, 3].sum }  #=> 6

# 情境2
def washing_machine
  yield if block_given?
  
  2
end

apple_machine { 1 }      #=> 2
apple_machine { 1 + 1 }  #=> 2
apple_machine { [1, 2, 3].sum }  #=> 2

yield字面上的意思為讓路。上述的情境1、情境2,想要傳達給讀者的訊息為不管是簡單的1、稍微複雜的1+1,或者更複雜的[1, 2, 3].sum。在進入block以後,程式碼的會先被Block拿走主控權,結束block以後再回歸原來的程式。

# 情境3
def itday9_method(num = 0)
  yield(num + 1) if block_given?
end

itday9_method                    #=> nil
itday9_method(2) { |n| n*100 }   #=> 300
[1, 2, 3].map { |n| itday9_method(n) { |p| p*100 } } #=> [200, 300, 400]

我們可以利用yield將方法裡頭的值傳出去給外面的Block,給外面的Block計算以後再回傳運算的結果回來。情境3的用法很重要

實際的狀況下,我們傳的不會只是num+1,可以是綠界回傳的付款結果、貨運單狀態、POS錯誤訊息等等。

yield或許不是個好寫法,因為使用太多的Block會造成維護上的不易讀性,不過個人在專案上用的地方還蠻多的。使用在只有自己在維護的專案裡面,寫了Block就可以少想很多設計流程。

以下有幾個例子,可以正大光明在Rails使用Block

  • view:如視窗畫面、卡片、頁籤
  • layout :搭配content_for,如使用Action Mailer
  • 設計流程裡面的decorator pattern,可以用yield實現 ➡️ Day32 提到攤提

Bind

Bind 其實是 React 使用 class component 比較常見的寫法,但 bind 不是只能綁定 this,還可以綁任何物件。下面的例子為,javascriptbind 將裡面的this 與外面的[1, 2, 3] 綁在一起。

function Point() {
  return [...this, 4, 5, 6]
}

point = Point.bind([1, 2, 3])

point()
// [1, 2, 3, 4, 5, 6]

bind是一種可以和外部世界聯絡的橋樑,而這個方法在Ruby 也有。

a = 123              # 外部變數
block = proc { a }   # Procedure
block.binding.local_variable_get(:a)  # 綁定外部的變數
block.call                            #=> 123

Curry

CurryJavascriptfunction的一種進階用法,在JS中也是漢漢老師常用的寫法。此外,我也常常在面試中看到使用Curry的題目

/* 公司後輩的面試題目 */
function adder(a) {
  /* function 可以當作變數,因此我們可以將 function 回傳出去。 
     意即為: function return 出來的結果還是 function */
  return function(b) {
    return a + b
  }
}

/* 公司專案常見的寫法,寫法等同於上方 */
const adder = a => b => (a + b)

/* 範例1: a = 1, b = 2 */
adder(1)(2)                        // 3

/* 範例2: 用在map */
[1, 2, 3].map(e => adder(1)(e))    // [2, 3, 4]

範例1用了兩個括號把值回傳出來,範例2為使用map 這個 Higher Order Function,我們在使用Higher Order Function時,裡面的adder必須也是function。利用curry的特性,做一個匿名函數 e => adder(1)(e)提供給map使用。

curry絕對不是炫技用,而是為了做到把function可以變成變數的特性而衍生出的技巧。以下為漢漢老師實際上使用的curry實例

/* 實際應用上使用的 curry (看不懂沒關係) */
export const ajaxReload = (table) => () => table.api().ajax.reload();
export const multiAjaxReload = (tables) => () => tables.forEach(table => table.api().ajax.reload())
export const dataWithStatus = (selectedValue, type = null) => e => ({...e, [checkedOrSelected(type)]: is_in(e.value, selectedValue)})

Ruby 也有一樣的方法,我們來看以下例子

divisible_by = ->(x,y) { (y % x).zero? }.curry

(1..10).select(&divisible_by.call(5))  #=> [5, 10]
(1..10).select(&divisible_by.call(2))  #=> [2, 4, 6, 8, 10]

我們分別傳5, 2 給divisible_by 這個物件對,接著select會使用divisible_by.call作為篩選。至於 &符號,我們會在Day10解釋

Day10 我們會介紹&,以及詳細說明yield的運作原理!

參考資料


上一篇
Day8. functional programming in Ruby - Block Part1
下一篇
Day10. 深入瞭解 Block - Block Part3
系列文
初階 Rails 工程師的養成34
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言