探討 List 的各種語法

F# 除了使用 .NET Framework 所提供的型別外,自己還定義了不少適合 FP 的型別,其中最常用的就是 List,但這與 C# 的 List<T> 不同,反而類似 C# 的 ImmutableList<T>

Version


macOS High Sierra 10.13.3
.NET Core SDK 2.1.101
Rider 2017.3.1
F# 4.1

Definition


List

將相同型別的 value 整合成單一 value,且每個 element 必須為 immutable

簡單來說,F# 的 List 就是 linked list 資料結構,為了實現 FP 的 Immutability,每個 element 建立後就不可再修改。

建立 List


List Literal

1
2
3
4
5
let list1 = [ 1; 2; 3 ]

list1
|> printf "%A"
// [1; 2; 3]

List 使用 [] 中括號表示,element 之間以 ; 隔開。

list000

  1. List 天生就是泛型,不過並不需要如 C# 宣告泛型型別 List<int>,Type Inference 會自動推導出 int list

F# 的型別都是寫在後面,不過 List 例外,element 型別寫在 list 前面

1
2
3
4
5
6
7
8
9
let list1 = [
1 // Number one
2 // Number two
3 // Number three
]

list1
|> printf "%A"
// [1; 2; 3;]

List 也可以使用換行的方式,此時可以不用加 ; 隔開,適用於 element 量較多時,或需要在 element 加上註解時。

1
let list1 : Control list = [ new Button(); new CheckBox() ]

List 的 element 必須相同型別,若 element 是物件時,則有例外。基於 里氏替換原則,若 element 的型別為 父類別interface 時,則 element 允許基於相同 父類別interface 的物件。

1
let list1 = []

空 list 則以 [] 表示。

List Range

1
2
3
4
5
let list1 = [ 1 .. 10 ]

list1
|> printf "%A"
// [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

若 list 的資料有規律性,則不必全部列出,可使用 ..,只要列出頭跟尾即可。

1
2
3
4
5
let list1 = [ 1 .. 2 .. 10 ]

list1
|> printf "%A"
// [1; 3; 5; 7; 9]

若 list 資料為 等差級數,則可在 .... 之間加上 step value,其中 step value 也可以是 負數

List Comprehension

若資料的變化性更大,已經無法單純使用 List Range 描述,則可使用更進階的 List Comprehension 表示。

1
2
3
4
5
6
7
8
let list1 = [
for i in 1..10 do
yield i * i
]

list1
|> printf "%A"
// [1; 4; 9; 16; 25; 36; 49; 64; 81; 100]

直接將 for ... in … do 寫在 [] 之內,yield 的 expression 即為 element。

1
2
3
4
5
let list1 = [for i in 1..10 -> i * i]

list1
|> printf "%A"
// [1; 4; 9; 16; 25; 36; 49; 64; 81; 100]

亦可將 do yield 省略,直接將 for ... in ... -> 寫在 [] 內,將 expression 寫在 -> 之後。

對於簡單的 expression,建議使用 for ... in ... -> 語法較精簡,若還需更複雜的描述,則必須使用 for ... in ... do yield

List.init

根據 index 值建立新的 list

1
2
3
4
5
let list1 = List.init 10 (fun idx -> idx * idx)

list1
|> printf "%A"
// [0; 1; 4; 9; 16; 25; 36; 49; 64; 81]

List Operator


:: Operator

1
2
3
4
5
6
let list1 = [ 1; 2; 3 ]
let list2 = 0 :: list1

list2
|> printf "%A"
// [0; 1; 2; 3]

將 value 加到 : 之前, 將加到 list 最前面。

@ Operator

1
2
3
4
5
6
7
let list1 = [ 1; 2; 3 ]
let list2 = [ 4; 5; 6 ]
let list3 = list1 @ list2

list3
|> printf "%A"
// [1; 2; 3; 4; 5; 6]

@ 將兩個 list 合併為一個 list。

Property

List 本身提供了不少 property 可用。

獲得 List 第 1 個 element。

'T

1
2
3
4
let list1 = [ 1 .. 3 ]

printf "%d" list1.Head
// 1

Tail

獲得第 1 個 element 除外,剩下的 list

'T list

1
2
3
4
5
let list1 = [ 1 .. 3 ]

list1.Tail
|> printf "%A"
// [2; 3]

就算 Tail 只剩下 1 個 element,仍然是 1 個 element 的 list。

IsEmpty

判斷 list 是否為空

bool

1
2
3
4
let list1 = [ 1 .. 2 ]

printf "%b" list1.IsEmpty
// false

Item

傳回指定 index 的 element 值

'T

1
2
3
4
let list1 = [ 1 .. 3 ]

printf "%d" (list1.Item 2)
// 3

List 的 index 為 zero-based。

Length

獲得 list 的 element 個數

int

1
2
3
let list1 = [ 1 .. 3 ]

printf "%d" list1.Length

Static Method


List.Cons

將 value 加到 list 最前面,產生新的 list,相當於 :: operator

head: 'T * tail: 'T list -> 'T list

1
2
3
4
5
6
let list1 = [ 1 .. 3 ]

List.Cons (0, list1)
|> printf "%A"

// [0; 1; 2; 3]

List.Cons() 傳入為一 tuple,第 1 個 element 為 value,第 2 個 element 為 list。

List.Empty

根據 list type 回傳一個 empty list

'T list

1
2
3
4
5
type myList = int list

myList.Empty
|> printf "%A"
// []

Q : List.Empty 與 List.empty 有何差異?

1
2
3
List.empty<int>
|> printf "%A"
// []

相同

  • 均回傳 empty list

相異

  • List.EmptyList type 的 static method;List.empty() 定義在 List module
  • List.Empty 須先定義 type;List.empty() 可由泛型直接指定 element type

Recursive Function


1
2
3
4
5
6
7
8
9
let rec sum list =
match list with
| head :: tail -> head + sum tail
| [] -> 0

[ 1 .. 3 ]
|> sum
|> printf "%d"
// 6

List 在配合 recursive function 時,在 Pattern Matching 常搭配 head :: tail,直接將 list 拆成兩半,然後將 tail 傳入 recursive function 繼續運算。

Conclusion


  • List 為 F# 最常用的 collection,概念雖然不難,但有一些 F# 獨特的語法,還是得花一點時間熟悉

Reference


Microsoft Docs, Lists
Wikibooks, F Sharp Programming/Lists
Chris Smith, Mastering F# Lists
Chris Smith, Pramming F# 3.0 2nd

2018-04-05