編碼 · 程式語言
F# 入門教學
簡答
F# 是以函數式為優先、靜態型別的 .NET 語言。它會替你推論大部分型別,用縮排而非大括號,並把不可變資料、模式比對與管線運算子 |> 當成撰寫程式碼的常態,同時仍能完整使用 .NET 函式庫。安裝 .NET SDK 後,用 dotnet fsi hello.fsx 即可執行腳本。
F# 是什麼、誰在使用
F# 由 Microsoft Research 的 Don Syme 設計,現在是由 Microsoft 與 F# Software Foundation 社群共同維護的開放原始碼語言。它屬於 ML 語言家族(與 OCaml 類似),運行在 Windows、macOS 與 Linux 的 .NET 上。
團隊使用 F# 進行金融建模、資料分析、領域邏輯繁重的業務邏輯、網頁後端與腳本撰寫。因為它與 C# 編譯成同樣的 .NET 中介語言,F# 專案可以使用任何 NuGet 套件,也能被 C# 程式碼呼叫。
安裝 F# 並執行 Hello, world
從 Microsoft 安裝 .NET SDK。F# 已包含在內,不需另外安裝。若只是快速實驗,可以寫一個副檔名為 .fsx 的腳本檔,用 F# Interactive(dotnet fsi)執行。
// hello.fsx
printfn "Hello, world!"dotnet fsi hello.fsx
# Hello, world!
dotnet new console -lang "F#" -o HelloApp
cd HelloApp
dotnet run第二組指令會建立一個完整的主控台專案,包含 .fsproj 檔案與 Program.fs。在專案中,.fsproj 裡列出的檔案順序很重要:一個檔案只能使用排在它前面的檔案中的程式碼。
值與基本型別
let 把名稱繫結到一個值。除非標記為 mutable,否則繫結是不可變的;標記後要用 <- 而不是 = 來修改。
| 型別 | 範例 | 說明 |
|---|---|---|
| int | 42 | 32 位元整數 |
| float | 3.14 | 64 位元浮點數(.NET 的 double) |
| decimal | 19.99m | 精確的十進位數,適合處理金額 |
| string | "hello" | 不可變的 .NET 字串 |
| char | 'a' | 單一字元 |
| bool | true, false | 邏輯值 |
| unit | () | 「沒有值」,類似 void |
let name = "Ana"
let age = 30
let mutable count = 0
count <- count + 1
printfn "%s is %d, count = %d" name age count
// Ana is 30, count = 1
let total = float age + 0.5 // explicit conversion neededF# 絕不會隱含轉換數字:1 + 2.0 是編譯錯誤。請用 float、int、string 等函式轉換。
流程控制與函式
if 是會回傳值的運算式,所以兩個分支必須有相同的型別。迴圈(for、while)雖然存在,但大部分程式碼改用集合函式。
let sign n =
if n > 0 then "positive"
elif n < 0 then "negative"
else "zero"
for i in 1 .. 3 do
printfn "%d squared is %d" i (i * i)
let add x y = x + y // int -> int -> int
let add5 = add 5 // partial application
printfn "%d" (add5 10) // 15
let square = fun x -> x * x
[1; 2; 3; 4]
|> List.filter (fun x -> x % 2 = 0)
|> List.map square
|> printfn "%A" // [4; 16]引數以空格分隔,而不是逗號。每個函式一次只接收一個引數(柯里化),所以 add 5 會回傳一個新函式。管線運算子 x |> f 的意思是 f x,讓你能由上往下撰寫資料轉換。縮排具有語意:函式或迴圈的主體必須縮排在它的下方。
集合、record 與 option
| 集合 | 字面值 | 說明 |
|---|---|---|
| List | [1; 2; 3] | 不可變的鏈結串列,預設的選擇 |
| Array | [| 1; 2; 3 |] | 大小固定、元素可變、索引存取快速 |
| Sequence | seq { 1 .. 10 } | 惰性,按需計算 |
| Map | Map ["a", 1; "b", 2] | 不可變的字典 |
| Set | set [1; 2; 2] | 由不重複值組成的不可變集合 |
type Person = { Name: string; Age: int }
let ana = { Name = "Ana"; Age = 30 }
let older = { ana with Age = 31 } // copy with one change
let people = [ ana; { Name = "Ben"; Age = 17 } ]
let adult = people |> List.tryFind (fun p -> p.Age >= 18)
match adult with
| Some p -> printfn "First adult: %s" p.Name
| None -> printfn "No adults"
// First adult: AnaRecord 是具有具名欄位與結構相等性的不可變型別。F# 自己的型別沒有 null;缺值以 Option 表示:Some value 或 None。List.tryFind 這類函式會回傳 option,編譯器會強制你處理兩種情況。
招牌特色:判別聯集與模式比對
判別聯集描述一個恰好屬於數個具名案例之一的值,每個案例各有自己的資料。match 會檢查各個案例,如果漏掉其中一個,編譯器會發出警告。
type Shape =
| Circle of radius: float
| Rectangle of width: float * height: float
let area shape =
match shape with
| Circle r -> System.Math.PI * r * r
| Rectangle (w, h) -> w * h
[ Circle 1.0; Rectangle (2.0, 3.0) ]
|> List.map area
|> List.iter (printfn "%.2f")
// 3.14
// 6.00其他知名特色包括型別推論(你很少需要寫型別標註)、度量單位(float<m> 不能與 float<s> 相加),以及用於非同步程式碼的計算運算式,如 task { ... } 與 async { ... }。
錯誤處理
對於可預期的失敗,慣用的 F# 會回傳 Result:Ok value 或 Error info。對於非預期的失敗,則可使用 .NET 例外,搭配 try ... with、failwith 與 raise。
let parseAge (s: string) =
match System.Int32.TryParse(s) with
| true, n when n >= 0 -> Ok n
| true, _ -> Error "Age must not be negative"
| false, _ -> Error "Not a number"
match parseAge "abc" with
| Ok n -> printfn "Age %d" n
| Error msg -> printfn "Error: %s" msg
// Error: Not a number
let safeDivide a b =
try Some (a / b)
with :? System.DivideByZeroException -> None
printfn "%A" (safeDivide 10 0) // NoneTryParse 在 .NET 中有一個 out 參數;F# 把它轉成元組 (success, value),可以直接比對。
完整的小程式:字詞頻率
let text = "the cat and the hat and the bat"
text.Split(' ')
|> Array.countBy id
|> Array.sortByDescending snd
|> Array.truncate 2
|> Array.iter (fun (word, n) -> printfn "%s %d" word n)
// the 3
// and 2Array.countBy id 會把相同的字詞分組,並回傳 (word, count) 配對;snd 取出每個配對的第二個元素來排序;最後以拆解元組的 lambda 印出結果。
接下來可以學什麼
- Microsoft Learn:F# 文件,包括「Tour of F#」與語言參考。
- 在
dotnet fsi中練習,可以貼上程式碼並立即看到結果;輸入以;;結束。 - 學習
List、Array、Seq、Option與Result模組;日常的大部分程式碼都是用它們組成的。 - 基礎熟悉後,可以看看 NuGet 上的 F# 網頁框架與函式庫。
常見問題
F# 只能用在 Windows 嗎?
不是。F# 運行在跨平台的 .NET 上,所以你可以在 Windows、macOS 與 Linux 上建置並執行 F# 程式。
F# 與 C# 有什麼差別?
兩者都運行在 .NET 上,也能使用相同的函式庫。C# 以物件導向為優先,再加入函數式特性;F# 則以函數式為優先,預設就是不可變資料、型別推論與模式比對。
F# 中的 |> 是什麼意思?
x |> f 是管線運算子,意思是 f x。它會把左邊的值當成右邊函式的最後一個引數傳入,所以一連串的轉換可以由上往下閱讀。
F# 和 OCaml 有關係嗎?
有。F# 屬於 ML 家族,核心語法與 OCaml 相近,但它是為 .NET 打造的,預設使用具語意的縮排,並有自己的特色,例如計算運算式。