F#和C#的語法差別
語法上,F#和C#有兩個主要差別:
- 用縮進而非花括號分隔代碼塊
- 用空白而非逗號分隔參數
F#常見語法元素
以下是F#代碼中常見的語法元素
注釋
// 這是單行注釋 (* 這是多行注釋 第二行 最后一行 *)
let 綁定
let myInt = 5 let myFloat = 3.14 let myString = "hello"
上面的語句沒有顯式指定 myInt, myFloat, myString 的類型,類型由編譯器推斷。
列表
let twoToFive = [2;3;4;5] // 方括號表示列表,元素用分號分隔 let oneToFive = 1 :: twoToFive // 符號 :: 將值添加到列表頭部,得到新列表,結果為 [1;2;3;4;5] let zeroToFive = [0;1] @ twoToFive // 符號 @ 連接兩個列表,得到新列表,結果為 [0;1;2;3;4;5]
務必注意,列表元素使用分號分隔,而非逗號分隔。
函數
命名函數用 let 關鍵字定義,匿名函數用 fun 關鍵字定義。
let square x = x * x // 使用 let 定義命名函數,函數形參不用小括號圍住 square 3 // 運行函數,實參也沒有小括號 let add x y = x + y // 不可使用 (x,y) add 2 3 // 運行函數 // 多行函數,用縮進,不用分號 let evens list = let isEven x = x%2 = 0 // 內部函數 List.filter isEven list // List.filter 是庫函數 evens oneToFive // 運行函數 // 用小括號指明優先級 let sumOfSquaresTo100 = List.sum (List.map square [1..100]) // 如果沒有小括號,那么 List.map 會是 List.sum 的參數 // 管道 |>,將操作的輸出傳給下一個操作 let sumOfSquaresTo100piped = [1..100] |> List.map square |> List.sum // 用 fun 關鍵字定義拉姆達(匿名函數) let sumOfSquaresTo100withFun = [1..100] |> List.map (fun x->x*x) |> List.sum
模式匹配
match..with 用於模式匹配
// 類似於 switch/case
let simplePatternMatch = let x = "a" match x with | "a" -> printfn "x is a" | "b" -> printfn "x is b" | _ -> printfn "x is something else" // 下划線匹配任意值 // Some(..) 和 None 有點像可空類型(Nullable) let validValue = Some(99) let invalidValue = None // match..with 匹配 "Some" 和 "None",同時從 Some 中取出值 let optionPatternMatch input = match input with | Some i -> printfn "input is an int=%d" i | None -> printfn "input is missing" optionPatternMatch validValue optionPatternMatch invalidValue
復雜類型
復雜類型是指元組,記錄和聯合
// 元組,包含有序但未命名的值,值之間用逗號分隔 let twoTuple = 1,2 // 二元組 let threeTuple = "a",2,true // 三元組 // 記錄,包含命名的字段,字段之間用分號分隔 type Person = {First:string; Last:string} let person1 = {First="john"; Last="Doe"} // 聯合,包含選項,選項之間用豎線分隔 type Temp = | DegreesC of float | DegreesF of float let temp = DegreesF 98.6 // 類型可以遞歸的組合,例如,下面的 Employee 聯合包含 Employee 類型的列表 type Employee = | Worker of Person | Manager of Employee list let jdoe = {First="John";Last="Doe"} let worker = Worker jdoe
格式化輸出
printf 和 printfn 向控制台輸出,類似於 C# Console 類的 Write 和 WriteLine 方法。sprintf 輸出到字符串,類似於 String 類的 Format 方法。
printfn "Printing an int %i, a float %f, a bool %b" 1 2.0 true printfn "A string %s, and something generic %A" "hello" [1;2;3;4] // 復雜類型具有內置的美觀輸出格式 printfn "twoTuple=%A,\nPerson=%A,\nTemp=%A,\nEmployee=%A" twoTuple person1 temp worker let str1 = sprintf "Printing an int %i, a float %f, a bool %b" 1 2.0 true let str2 = sprintf "A string %s, and something generic %A" "hello" [1;2;3;4]
比較F#和C#:計算平方和
C#版,不使用 linq
public static class SumOfSquaresHelper { public static int Square(int i) { return i * i; } public static int SumOfSquares(int n) { int sum = 0; for (int i = 1; i <= n; i++) { sum += Square(i); } return sum; } }
F#版
let square x = x * x let sumOfSquares n = [1..n] // 創建 1 到 n 的列表 |> List.map square // 對每個元素求平方,得到新列表 |> List.sum // 將平方列表求和
F# 語法噪音小,沒有花括號,分號,並且在這里不需要顯式指定類型。
C#版,使用 linq
public static class FunctionalSumOfSquaresHelper { public static int SumOfSquares(int n) { return Enumerable.Range(1, n) .Select(i => i * i) .Sum(); } }
使用 linq 改寫的 C# 代碼簡潔很多,但不能消除語法噪音,參數和返回值的類型也是必須的,仍然不如 F# 版簡潔。
例子:快速排序算法
快速排序算法的步驟是:
- 從列表中取出一個數作為基准
- 將小於基准的數放在左邊,不小於基准的數放在右邊
- 對基准兩邊的部分進行快速排序
下面是 F# 實現快速排序的例子
let rec quicksort list = match list with | [] -> [] | firstElem::otherElements ->
// 小於第一個元素的元素 let smallerElements = otherElements |> List.filter (fun e -> e < firstElem) |> quicksort
// 不小於第一個元素的元素 let largerElements = otherElements |> List.filter (fun e -> e >= firstElem) |> quicksort
// 連接成新列表
List.concat [smallerElements; [firstElem]; largerElements] printfn "%A" (quicksort [1;5;23;18;9;1;3])
如果應用庫函數和一些技巧,代碼可壓縮成:
let rec quicksort = function | [] -> [] | first::rest -> let smaller,larger = List.partition ((>=) first) rest List.concat [quicksort smaller; [first]; quicksort2 larger]