F# 的特色與歷史簡介
F# 並不是一個新語言,早在 2005 年就已經發行 1.0,隨著 .NET Core 的跨平台,也將 F# 帶進了 .NET Core,既然在 .NET Core 我們已經有了 C#,為什麼要關注 F# 呢 ?
Version
macOS High Sierra 10.13.3
.NET Core 2.1.4
F# 4.1
FSharp 的歷史
F# 並不像 C-style 語言,反而比較像 Python,事實上 F# 是從 ML、OCaml、Python、Haskell、Scala、Erlang … 等語言獲得靈感,除了 Python 大家較為熟悉外,剩下的都是 Functional Programming Language,由此可見 F# 的 F 就是 F
untional,所以 F# 號稱是 function first language,也就是 F# 雖然也支援 OOP,但 FP 是其主要特色。
個人大概在 2010 年曾經接觸過 F#,當初的感覺 F# 是個 外星語言
,很難體會 F# 的優點在哪裡,學沒多久就放棄了,但經過這幾年 Laravel Collection、JavaScript 、Linq 、Rx.js 與 AWS Lambda 的轟炸,越來越覺得 FP 的可愛,OOP 也能藉由 FP 手法,產生出現更優雅的實作方式。
這幾年一直想尋找一個 FP 語言來練習,期間摸過 Elixir 、Scala、Clojure,但成效一直有限,一直到最近複習 F#,才發現 F# 是一個很簡單的 FP 語言,以前看不懂的地方,瞬間都看懂了,只是當年還無法欣賞 F#。
Functional Programming 定義
一個語言要能實現 FP,必須有 4 個條件:
- 能將 function 指定為變數
- 能將 function 存到 collection 內
- 能將 function 以參數型式傳入 function
- 能在 function 回傳 function
簡單來說,function 要能如一般變數與 object 一樣使用。
能將 function 以參數型式傳入 function
導致了 Higher Order Function 的觀念出現,如 Rx.js 一堆 operator,就是 Higher Order Function。
能在 function 回傳 function
則導致了 Pipeline、Compose 與 Currying 的觀念出現。
一般 OOP 語言都會某種程度的支援 FP,如 Higher Order Function 在 C#、JavaScript、PHP … 都可以實現。
但 Pipeline、Compose 與 Currying 在一般 OOP 語言則沒有,或者要另外安裝其他 package 才能實現,但這些觀念在 F# 都是原生支援。
User Story
我們想將陣列 1, 2, 3, 4, 5
的資料中,將所 奇數
平方再加 1
。
Task
根據 JavaScript、Linq 、Rx.js 的經驗,我們不再使用迴圈,而會使用 Higher Order Function 來解決問題。
Higher Order Function
Program.fs
1 | open System |
12 行
1 | let odds = List.filter isOdd values |
使用 List.filter
先找出所有 奇數
。
isOdd
: 傳入判斷奇數
的 functionvalues
: 傳入欲處理資料odds
: 回傳所有奇數
第 7 行
1 | let isOdd x = x % 2 <> 0 |
定義 isOdd
,當 % 2
餘數不為 0
時為 奇數
。
在 F#,因為已經將 function 視為一般變數,所以無論是 value 或 function,都統一使用
let
13 行
1 | let squares = List.map square odds |
既然已經找出所有 奇數
,接下來就是使用 List.map
計算 平方
。
square
: 傳入計算平方
的 functionodds
: 傳入所有奇數
squares
: 回傳所有平方
第 8 行
1 | let square x = x * x |
定義 square
,計算平方。
14 行
1 | let result = List.map addOne squares |
既然已經計算出所有 奇數的平方
,接下來就是使用 List.map
計算 +1
。
square
: 傳入計算+1
的 functionodds
: 傳入所有奇數的平方
squares
: 回傳所有平方+1
第 9 行
1 | let addOne x = x + 1 |
定義 addOne
,計算 +1
。
15 行
1 | result |
要回傳的變數, F# 不用寫 return
。
相對於 C-style 語言,我們發現 F# 有幾個特色
- 沒有
{}
,完全用縮排表示,類似 Python- Function 傳入參數不需
()
,只需空白隔開即可- 變數與 function 統一使用
let
- 回傳值不需要
return
這些只是語法的差異,只要習慣即可,不過平心而論,C-style 語言寫久,會發現 code 都一堆
()
{}
與return
都是贅字,F# 這種 coding style 乾淨很多
Pipeline 與 Currying
由於 List.filter
、List.map
與 List.map
是依序處理,因此我們要不斷定義中繼變數 : odds
與 squares
傳入,事實上這些也是多餘的,若能省略則更好,這就是 Pipeline。
Program.fs
1 | open System |
12 行
1 | values |
|>
為 F# 的 Pipeline 符號,表示將 function 的 output 作為下一個 function 的 input。
因此我們可以透過 |>
表示先執行 List.flter
,然後再將結果傳入 List.map
,最後再將結果傳入 List.map
,這樣可以很清楚的表示流程,語意比 imperative 寫法更清楚。
Q : 可以明明
List.map
與List.filter
是 2 個參數,第 1 個參數是 function,第 2 個參數是 value,但為什麼 value 都不用傳呢 ?
當 function 參數沒有傳完全時,F# 將回傳一個新的 function,新的 function 只要傳入剩下的參數即可,這稱為 Currying。
當 List.filter isOdd
只傳入 1 個參數時,由於參數沒有傳完整,將回傳一個新的 function,然後 |>
再將 values
傳入新的 function,如此 List.filter
才算完整,才能回傳 所有奇數
,最後再將 所有奇數
透過 |>
傳給下一個 List.map
,剩下以此類推。
Compose
Pipeline 雖然已經夠清楚,但 pipeline 基本上仍然是回傳 value,若我們能將所有 function 先組合好,最後統一透過一個 function 執行,那就更好了,這就是 Compose。
Program.fs1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16open System
[<EntryPoint>]
let main argv =
let numbers = [1; 2; 3; 4; 5]
let isOdd x = x % 2 <> 0
let square x = x * x
let addOne x = x + 1
let sqaureAddOne = square >> addOne
let func = List.filter isOdd >> List.map sqaureAddOne
printfn "%A" (func numbers)
0
12 行
1 | let func = List.filter isOdd >> List.map sqaureAddOne |
>>
為 F# 的 compose 符號,專門負責組合 function。
因為連續兩個 List.map
,因此我們先將 square 與 addOne 組合成新的 sqaureAddOne
function,再交給 List.map
執行。
由於是先執行 List.fiter
,再執行 List.map
,因此使用 List.filter >> List.map
。
>>
不只代表 compose,也代表執行方向,所以也有 <<
。
Q&A
Q : 學 F# 目前能做什麼 ?
- F# 可跑在 .NET Core,所以能用 F# 寫 ASP.NET MVC、ASP.NET Web API、UWP 與 Console App
- F# 可用來寫 Azure Function
Conclusion
- 本文簡單的展示 F# 最關鍵的 Pipeline、Currying 與 Compose,這些都是 OOP 語言很難見到的強悍功能,透過 F# 的簡單實作,讓我們在練習 FP 時更加方便
F# 並不是要取代 C#,事實上在 .NET Core,C# 仍是必學的語言,只是透過學習 F#,能訓練自己 FP 的思維,進而用在 C# 與 TypeScript 上
若語言間的 paradigm 相同,只是 syntax 不同,則沒有學習新語言的必要;但若透過更好的 syntax,讓你學到不同的 paradigm,這就有意義了,這就是學習 F# 的原因
Sample Code
完整的範例可以在我的 GitHub 上找到