iT邦幫忙

2021 iThome 鐵人賽

DAY 8
2
Modern Web

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

Day8. functional programming in Ruby - Block Part1

這篇文章會用不一樣的觀點來介紹Ruby and Rails,寫過 Javascript 的讀者們,相信這篇文章對你們會比較有感覺。

Ruby 為正統的OOP語言,但Ruby也 開了一扇functional programming的窗,讓我們可以使用,而今天我就要為讀者們打開這扇窗戶。

Block

首先要介紹什麼是區塊blockblock 為以 do ... end 或者以 {} 包裹住的區塊,以下面的例子來說,{|_| _ + 1} 就是 block

[1, 2, 3].map {|_| _ + 1}

在 Ruby 什麼東西都是物件,不過也有例外,例如Block本身就不是物件。Block沒有辦法單獨的存在,也沒辦法指定給某個變數。雖然是例外,但 block卻是Ruby常用的技巧。不僅面試常考,很多底層也很常使用block

{|_| _ + 1}      #=> 不行這樣表示
a = {|_| _ + 1}  #=> 當作變數也不行

#==== 取而代之,我們可以這樣寫
a = -> (v) { v + 1 }

Procedure

下面的寫法為 Proc

a = -> (v) { v + 1 }

Block 沒有辦法單獨存在,但我們可以使用 Proc 類別,把 Block 物件化。Proc全名稱為 Proceduce,而Proc一共有兩種,一種為lambda, 而另外一種為 Proc

#======= lambda
-> { 'foo' }
#=> #<Proc:0x00007f883d48dfd8@(irb):2 (lambda)>
lambda { 'foo' }
#=> #<Proc:0x00007f883d49f0f8@(irb):3 (lambda)>

#======= Proc
Proc.new { 'foo' }
#=> #<Proc:0x00007f883d4afc50@(irb):4>
lambda { 'foo' }    #=> Proc
-> { 'foo' }.class  #=> Proc

Proc.new { 'foo' }  #=> Proc

接著,講講如何執行procedure

adder = -> (x) {x+1}

# 4種 notation
adder.call(1)

adder.(1)
adder.=== 1
adder[1]

一般來說,除了call 這種 notation 以外,其他用法長相太奇異,一般來說都不建議使用,但如果想要用proc,又想要在code review通關的話,建議使用最後一種notation,因為可以被當作使用陣列矇混過去。

def arb_mtd
  # 程式碼邏輯(多行)
  adder = -> (x) {x+1}
  # 程式碼邏輯(多行)
  adder[1]
  # 程式碼邏輯(多行)
end

functional Programming

Ruby on Rails是很物件導向的語言,而漢漢老師曾經有一段時間因前端部門的人太少,我被轉調到前端部門。聽到大家討論在前端專案實作什麼寫法的時候,第一次聽到functional programming這個名詞。起初對於什麼是functional programming一頭霧水,看到同事的程式碼嚇到,原來functional programming在專案上竟然能夠呈現得那麼好看!

主要也是同事也好看。

總之開始看下列例子。以下的例子為將[1, 2, 3] 的陣列分別加1,回傳新的值

[1, 2, 3].map {|_| _ + 1}

我們使用Javascript來詮釋以上的寫法

/* es6 map */
[1, 2, 3].map(e => e + 1)

/* map in lodash */
map([1, 2, 3], e => e + 1)

依樣畫葫蘆,我們也將原本Ruby的寫法進行改寫。這裏先不要理會&的寫法,我們只是盡量將JS, Ruby寫法達到一致。

[1, 2, 3].map(&->(x) { x + 1 })

Ruby, JS兩個語言使用了匿名函式如下

e => e + 1        # anonymous in js
->(x) { x + 1 }   # anonymous in ruby

並且,這兩個匿名函式皆為 Pure function,又為 First Class Citizens。今天突然講了很多新名詞,不過我會一一介紹給大家。

Pure function

我們先介紹Pure functionPure Function 有一個最重要的特性是當輸入了相同的值,吐出的值都是相等的結果,也有人說這種特性是可快取的(cached)Vue2computed property 即為一種Pure function,使用Pure function 不會有side effect,意即不會改變任何結果!

First Class Citizens

什麼是First Class Citizens?First Class Citizens 為將function 視為參數,指派到其他function使用。以上例來說,->(x) { x + 1 }為第一等公民,它將自己提供給別人給使用。

Higher Order Function

使用其他function作為參數使用的function,或者回傳結果為function都為 Higher Order Function。上述的map 使用了匿名函數,因此map高階函數 higher order function。

我們舉個簡單的例子講解 Higher Order FunctionFirst Class Citizens 之間的關係

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

function showOk() {
  alert( "OK" );
}

function showCancel() {
  alert( "Canceled" );
}

// usage: functions showOk, showCancel are passed as arguments to ask
ask("Do you agree?", showOk, showCancel);

上述例子我們可以知道 showOk, showCancel都被ask拿來取用,因此上述例子的 Higher Order Functionask,而 showOk, showCancel 則為 First Class Citizens

以下列出 Javascript 常見的高階函數,其中hello, squareFirst Class Citizens,而使用hello, squaresetTimeout, map則為 higher order function

// setTimeout
const hello = () => console.log("Hello!")
window.setTimeout((hello), 1000)

// map (用lodash的觀點來看)
const square = (n) => (n * n);
_.map([4, 8], square);
// => [16, 64]

Rails不難發現Higher Order Function 的蹤跡,以下舉一些複雜的例子。

# gem: AASM 
#=== 定義訂單完成動作
event(:complete, success: -> { update_status_time!(:done_at) }) do
  transitions from: [Status::WAITING, Status::PROCESSING], to: Status::DONE
end

首先我們看AASM的例子,它把 success: -> { update_status_time!(:done_at) } 傳進去event,因此event在這邊為 higher order function,而-> { update_status_time!(:done_at) }First Class Citizens

# gem: descent_exposure
#===== 取代 controller instance variable 的好用工具
module Admin
  class ReturnOrdersController < ApplicationController
    expose :return_orders, -> { ReturnOrder.all }
    expose :return_order, build: -> (return_order_params, scope) { scope.build(return_order_params) },
                          scope: -> { ReturnOrder.all }
    expose :order, -> { return_order.order }
  end
end

上面的例子,毫無疑問的,exposehigher order function,而build: -> {...}, scope: -> {...} 則為被調用的First Class Citizens

結語

自從踏入了Functional Programming的領域之後,Block變成是我很喜歡的章節之一。今天介紹了 Ruby 和 Functional Programming 之間關聯,我們也會繼續在Day8-9 介紹Block

參考資料


上一篇
Day7. 活用Ruby的Time,人人都可以成為時間魔術師
下一篇
Day9. functional programming in Ruby - Block Part2
系列文
初階 Rails 工程師的養成34
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言