iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 12
1

四月是最殘酷的季節,
讓死寂的土原迸出紫丁香,
摻雜著追憶與慾情,
以春雨撩撥萎頓的根莖

-- T·S·艾略特, 荒原


當我們站到城的門口,有個低沉而安穩的聲音,與我們開始對話:

"嗨,我認得你,來過很多次了呢。但是規定上測驗是必要的。
那麼,鍊成陣的元件崩毀時,如何讓整個陣式繼續運作呢?"

我正想說"當然是找到壞掉的部份修好啊"時,小動物帶著微笑著說:"沒關係,讓它炸。" 城市的大門於是無聲的向旁滑開,小動物信步走了進去。

等等等等我剛剛聽到了什麼?


這座城會在這時出現在我們面前,大概也算是命運吧。Elixir 之城,也是建立於「函式是一等公民」概念上的語言。在這裡我們可以用比較輕鬆的方式來習慣這一類的做事方式。來得及的話可以說說這座城特別的地方,也或許研究一下如何同時施展多個法術。來吧!


跟 JS 莊園相比,這裡可以說的上是冷清了,路上幾無人煙,但一棟棟的高樓宛如有生命般的不斷生長與崩落。建築內外許多透著光的管線貫穿纏繞,而無數不同形狀的光的物體在其中穿梭,每經過一段路,物體會變成另一種形狀…


冗餘的變數

不管在哪個都市,我們常常重覆下面這種「呼叫函式,用一個變數接住結果,接著再用它當做參數,呼叫下一個函式」的過程:

# 通用語法
request = generate_request()
response = get_response(request)
body = parse_body(response, "html")
html = render(body)

但是仔細看一下,其實我們想要的只有最後那個 html 變數而己。其它的變數都只是用來拿到上一個函式呼叫的回傳值,並作為參數直接傳遞給下一個函式的。其實我們可以不要這些變數,直接寫成這樣:

# 通用語法
html = render(parse_body(get_response(generate_request), "html"))

「這樣整個更難讀吧!一眼根本看不出來從哪裡開始啊!」 這樣寫一定會被罵的,我想著。

沒錯,這樣寫一定會被罵的。我們想要有個更棒的方式來表達直接把上一步的回傳值,當做下一個函式的參數呼叫。而這就是這座城市最有名的函式 |>,魔法師們稱它為 pipe operator

連續的傳遞

pipe operator 的用法,就是用 x |> f() 代替 f(x) 來進行函式呼叫。這個運算子,會把前方的值,當做後面的函式的第一個參數進行呼叫。而 pipe operator,是可以一直串下去的

# Elixir 語法
html =
  generate_request()
  |> get_response()
  |> parse_body(:html)
  |> render()

集合的連續傳遞

而 Elixir 裡,也內建了 map, filter, reduce 函式,搭配上 |> 會這樣使用:

# Elixir 語法
[1, 2, 3]
|> Enum.map(fn i -> i + 1 end)
|> Enum.filter(fn i -> i % 2 == 0 end)
|> Enum.reduce(fn (accu, i) -> accu + i end) #=> 6

為什麼這樣比一直點下去好?

我們常常在 JS 莊園裡這樣使用陣列:

// JavaScript 語法
let result =
  [1, 2, 3, 4, 5, 6].map(i => i + 1)
                    .filter(i => i % 2 == 0)
                    .reduce((accu, i) => accu + i, 0) //=> 12
                    // 因為變成數字,沒辦法再 . 下去了
                    
let list2 = expand(result) //=> [12, 13, 14]

list2.filter(i => i % 3 === 0) // => [12]

//函式的定義
function expand(i) { return [i, i + 1, i + 2] }

但是就如上面的範例所示,一旦 reduce 成不是陣列的東西,就沒有辦法繼續用 .,而是必須用個變數接起來,再傳遞給其它函式。

而同樣的操作,在 Elixir 裡,由於 |> 只是把值傳遞給函式當參數來呼叫,不管是什麼值都可以一直傳下去。因此可以寫成這樣:

# Elixir 語法
[1, 2, 3, 4, 5, 6]
|> Enum.map(fn i -> i + 1 end) #=> [2, 3, 4, 5, 6, 7]
|> Enum.filter(fn i -> i % 2 == 0 end) #=> [2, 4, 6]
|> Enum.reduce(0, fn i, accu -> accu + i end) #=> 12
|> expand() #=> [12, 13, 14]
|> Enum.filter(fn i -> i % 3 == 0 end) #=> [12]

#函式的定義
def expand(i), do: [i, i + 1, i + 2]

|> 是單純的函式呼叫這件事,表示中間就算出現 nil 也可以很流暢的一路傳下去,只要下一個函式知道怎麼處理接收到 nil 的情況,就能繼續下去。


於是我終於看清楚這城市,是由成千上萬的轉換的函式,以及連接它們的眾多管道所構成,而無數的資料,在管道穿過一個個的函式,從一種形狀變換成另一種形狀再變換成下一種形狀直到它們想成為的樣態。而整座城市依此不斷的運算,生長,形變…

[to be continue]


上一篇
mostly:functional 第十章:自我指涉的藝術
下一篇
mostly:functional 第十二章:鐫刻的真相,狀態的琥珀
系列文
mostly:functional 從零開始的異世界程式觀 --- 函數式程式設計的試煉35

1 則留言

0
Ken
iT邦新手 5 級 ‧ 2020-10-14 16:00:51

太感謝了!!
看了這邊之後對 pipe 跟 compose 有感覺了

taiansu iT邦新手 5 級 ‧ 2020-10-14 16:47:33 檢舉

讚讚讚。有提到 compose 看起來是有開悟到了 XD

Ken iT邦新手 5 級 ‧ 2020-10-15 16:51:51 檢舉

感恩 QwQ

我要留言

立即登入留言