這篇文章會用不一樣的觀點來介紹Ruby and Rails
,寫過 Javascript
的讀者們,相信這篇文章對你們會比較有感覺。
Ruby
為正統的OOP
語言,但Ruby也 開了一扇functional programming
的窗,讓我們可以使用,而今天我就要為讀者們打開這扇窗戶。
首先要介紹什麼是區塊block
?block
為以 do ... end
或者以 {}
包裹住的區塊,以下面的例子來說,{|_| _ + 1}
就是 block
[1, 2, 3].map {|_| _ + 1}
在 Ruby 什麼東西都是物件,不過也有例外,例如Block
本身就不是物件。Block
沒有辦法單獨的存在,也沒辦法指定給某個變數。雖然是例外,但 block
卻是Ruby
常用的技巧。不僅面試常考,很多底層也很常使用block
{|_| _ + 1} #=> 不行這樣表示
a = {|_| _ + 1} #=> 當作變數也不行
#==== 取而代之,我們可以這樣寫
a = -> (v) { v + 1 }
下面的寫法為 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
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 Function
有一個最重要的特性是當輸入了相同的值,吐出的值都是相等的結果,也有人說這種特性是可快取的(cached)。Vue2
的 computed property
即為一種Pure function
,使用Pure function
不會有side effect,意即不會改變任何結果!
什麼是First Class Citizens?First Class Citizens 為將function
視為參數,指派到其他function
使用。以上例來說,->(x) { x + 1 }
為第一等公民,它將自己提供給別人給使用。
使用其他function
作為參數使用的function
,或者回傳結果為function
都為 Higher Order Function
。上述的map
使用了匿名函數,因此map
高階函數 higher order function。
我們舉個簡單的例子講解 Higher Order Function
和 First 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 Function
為 ask
,而 showOk
, showCancel
則為 First Class Citizens
。
以下列出 Javascript
常見的高階函數,其中hello
, square
是First Class Citizens
,而使用hello
, square
的setTimeout
, 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
上面的例子,毫無疑問的,expose
為 higher order function
,而build: -> {...}, scope: -> {...}
則為被調用的First Class Citizens
自從踏入了Functional Programming
的領域之後,Block
變成是我很喜歡的章節之一。今天介紹了 Ruby 和 Functional Programming 之間關聯,我們也會繼續在Day8-9 介紹Block
。