Ruby 2.6 的新功能

·

2 min read

Ruby 2.6 除了主打的 JIT 之外,還引進了一些有趣的新功能。其中有一些可以讓 Ruby 在函數式風格的寫法上更加自然流暢。想來稍微展示一下這些功能的用法。

1. Compose operator:
Proc.>>Proc.<<

雖然 Ruby 天生在某些用法上就採用了函數式風格(例如沒有 for 迴圈,而是用 each + block),但由於高階函式接受的是 block 而非 lambda,lambda 本身也無法簡單組合起來。所以一直以來匿名函式在 Ruby 中除了拿來當參數傳遞之外,用法相當受限。

在正統的函數式編程中,用 function composition 的方式將多個小函式組合在一起是很基本的操作,舉個之前的例子,假設你想要這樣的連續調用:

# 注意,因為假設這些函數都是 lambda,所以要用 `.()` 調用
request = generate_request.()
response = get_response.(request)
body = parse_body.(response)
html = render.(body)

其實除了 html 這個最終結果外,我們不需要中間的臨時變數,而可以改寫成這樣(然後被同事記恨):

html = render.(parse_body.(get_response.(generate_request.())))

Ruby 2.6 開始你可以用 << (compose) 或是 >> (pipe) 來組合 Proc (或 lambda)。假設我們有兩個 lambda fg,這兩個運算子的作用如下:

(f >> g).(x)
# 等同於
g(f(x))


(f << g).(x)
# 等同於
f(g(x))

我自己的記憶方式是把這兩個 operator 看成函式呼叫流程的箭頭。f >> g 就是先調用 f ,再將結果傳進 g。反之 f << g 則是先調用 g 再傳給 f

那麼之前的例子就可以改成下列兩者之一:

get_html = generate_request >> get_response >> parse_body >> render
html = get_html.()

# 或是

get_html = render << parse_body << get_response << generate_request
html = get_html.()

Ruby 2.6 的文件範例如下:

f = proc {|x| x * x}
g = proc {|x| x + x}

(f << g).call(2) # => 16
(f >> g).call(2) # => 8

再進階一點配合 Ruby 原本就有的 Object#methodMethod#curry,想來可以組合出非常有意思的寫法。

2. Enumerable#filter

函數式編程最著名的三個高階函式就屬 mapreducefilter 了。但在 Ruby 中,filter 這個高階函式被改名為 select,這一版加上了 filter 的別名,跟其它語言的慣用法一致,再也不會等到測試爆掉才想起函式名稱不一樣了。

[1, 2, 3, 4].filter {|i| i % 2 == 0} # => [2, 4]

3. Endless range

現在範圍運算子 .. 可以不加結束的參數:

[:a, :b, :c, :d][2..] # => [:b, :c, :d]


(:b..).lazy.zip(10..).first(3) # => [[:b, 10], [:c, 11], [:d, 12]]

4. Array#union and Array#difference

Array#union 是聯集,Array#difference 是差集。直接上範例:

[1].union([2, 3], [1, 2, 4, 5]) # => [1, 2, 3, 4, 5]


[1, 1, 2, 2, 3, 3, 4, 5].difference([1, 2, 4]) # => [3, 3, 5]

結語

除了這些之外,Proc.call 調用快了大約 1.4 倍。另外還有 Object#thenHash#mergeEnumerable#to_h 等等,就找機會試玩看看吧。Happy hacking!