FP 的基本功

F# 的 List module 提供眾多 List 常用的 Higher Order Function,要能發揮 FP 的威力,首先必須能活用內建 function。

本文探討英文字母 Q ~ Z 開頭的 function。

Version


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

List.reduce


將 list 中每個 element 的值經過 reduction function 累加

reduction : ('T -> 'T -> 'T) -> list : 'T list -> 'T

1
2
3
4
[ 1; 2; 3 ]
|> List.reduce (fun acc elm -> acc + elm)
|> printf "%A"
// 6

Q : List.fold()List.reduce() 有何差異?

1
2
3
4
[ 1; 2; 3 ]
|> List.fold (fun elm acc -> acc + elm) 0
|> printf "%A"
// 6

相同

  • 都是 累加

相異

  • List.fold() 可以設定初始值;List.reduce() 不能設定初始值
  • List.fold() 可以不同型別,如 (string * int) list -> int;List.reduce() 從頭到尾必須相同型別,如 int lint -> int

List.reduceBack


將 list 中每個 element 的值從尾部經過 reduction function 累加
reduction : ('T -> 'T -> 'T) -> list : 'T list -> 'T

1
2
3
4
[ 1; 2; 3 ]
|> List.reduceBack (fun elm acc -> acc + elm)
|> printf "%A"
// 6

List.replicate


以相同 element 建立 list

count : int -> initial : 'T -> 'T list

1
2
3
List.replicate 3 0
|> printf "%A"
// [0; 0; 0]

List.rev


將 list 反過來表示

list : 'T list -> 'T list

1
2
3
4
[ 1; 2; 3 ]
|> List.rev
|> printf "%A"
// [3; 2; 1]

List.scan


類似 List.fold()List.reduce() 對 list 進行累加,但會將計算過程保留在新 list 的 element

folder : ('State -> 'T -> 'State) -> state : 'State -> list : 'T list -> 'State list

1
2
3
4
[ 1; 2; 3 ]
|> List.scan (fun acc elm -> acc + elm) 0
|> printf "%A"
// [0; 1; 3; 6]

List.scanBack


List.scan(),但從 list 尾部開始累加,並將最後結果先顯示

folder : ('T -> 'State -> 'State) -> list : 'T list -> state : 'State -> 'State list

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

List.scanBack (fun elm acc -> acc + elm) list1 0
|> printf "%A"
// [6; 5; 3; 0]

List.singleton


將整個 list 成為新 list 的第一個 element

value : 'T -> 'T list

1
2
3
4
[ 1; 2; 3 ]
|>List.singleton
|> printf "%A"
// [[1; 2; 3]]

List.skip


忽略前 n 個 element 並傳回新 list

count : int -> list : 'T list -> 'T list

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

List.skipWhile


忽略前 n 個符合 predicate function 的 element,若第一個 element 不符合則停止 skip

predicate : ('T -> bool) -> list : 'T list -> 'T list

1
2
3
4
[ 1 .. 10 ]
|>List.skipWhile (fun elm -> elm < 5)
|> printf "%A"
// [5; 6; 7; 8; 9; 10]

List.sort


將 list 排序 (由小到大)

list : 'T list -> 'T list

1
2
3
4
[ 3; 2; 1 ]
|>List.sort
|> printf "%A"
// [1; 2; 3]

List.sortDescending


將 list 排序 (由大到小)

list : 'T list -> 'T list

1
2
3
4
[ 1; 2; 3 ]
|>List.sortDescending
|> printf "%A"
// [3; 2; 1]

List.sortBy


將 list 的 element 透過 projection function 轉換過,再根據 key 排序 (由小到大)

projection : ('T -> 'Key) -> list : 'T list -> 'T list

1
2
3
4
[ 3; 2; 1 ]
|>List.sortBy (fun elm -> elm)
|> printf "%A"
// [1; 2; 3]

List.sortByDescending


將 list 的 element 透過 projection function 轉換過,再根據 key 排序 (由大到小)

projection : ('T -> 'Key) -> list : 'T list -> 'T list

1
2
3
4
[ 1; 2; 3 ]
|>List.sortByDescending (fun elm -> elm)
|> printf "%A"
// [3; 2; 1]

List.sortWith


將 list 的 element 透過 comparer function 排序

comparer : ('T -> 'T -> int) -> list : 'T list -> 'T list

1
2
3
4
[ 3; 2; 1 ]
|>List.sortWith (fun elm1 elm2 -> elm1 - elm2)
|> printf "%A"
// [1; 2; 3]

List.splitAt


將 list 根據指定 index 分成兩個 list

index : int -> list : 'T list -> 'T list * 'T list

1
2
3
4
[ 1 .. 10 ]
|>List.splitAt 5
|> printf "%A"
// ([1; 2; 3; 4; 5], [6; 7; 8; 9; 10])

List.splitInto


將 list 分成指定 chunk 數

count : int -> list : 'T list -> 'T list list

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

List.sum


將 list 中所有 element 相加

list : 'T list -> 'T

1
2
3
4
[ 1; 2; 3 ]
|>List.sum
|> printf "%A"
// 6

List.sumBy


將 list 先將過 projection function 運算後再相加,相當於 List.map() + List.sum()

projection : ('T -> 'U) -> list : 'T list -> 'U

1
2
3
4
[ 1; 2; 3 ]
|>List.sumBy (fun elm -> elm * elm)
|> printf "%A"
// 14

相當於

1
2
3
4
5
[ 1; 2; 3 ]
|> List.map (fun elm -> elm * elm)
|> List.sum
|> printf "%A"
// 14

List.tail


將 list 除了第 1 個 element 外,剩下的 list 傳回,相當於 list 的 Tail property

list : 'T list -> 'T list

1
2
3
4
[ 1; 2; 3 ]
|>List.tail
|> printf "%A"
// [2; 3]

List.take


回傳 list 前 n 個 element

count : int -> list : 'T list -> 'T list

1
2
3
4
[ 1 .. 10 ]
|>List.take 3
|> printf "%A"
// [1; 2; 3]

Q : List.truncate()List.take() 有何差異?

當 n 大於 Length 時,List.truncate() 不會報錯,只是將整個 list 傳回;但 List.take() 拋出 InvalidOperationException

List.takeWhild


回傳前 n 個符合 predicate function 的 element,若第一個 element 不符合則停止 take

predicate : ('T -> bool) -> list : 'T list -> 'T list

1
2
3
4
[ 1 .. 10 ]
|>List.takeWhile (fun elm -> elm < 5)
|> printf "%A"
// [1; 2; 3; 4]

List.toArray


將 list 轉成 array

list : 'T list -> 'T []

1
2
3
4
[ 1; 2; 3 ]
|>List.toArray
|> printf "%A"
// [|1; 2; 3|]

List.toSeq


將 list 轉成 sequence

list : 'T list -> seq<'T>

1
2
3
4
[ 1; 2; 3 ]
|>List.toSeq
|> printf "%A"
// [1; 2; 3]

List.transpose


將 sequence 內的 list 加以 transpose

list : seq<'T list> -> 'T list list

1
2
3
4
[[ 1; 2; 3]]
|>List.transpose
|> printf "%A"
// [[1]; [2]; [3]]

List.truncate


回傳 list 最多 n 的 element

count : int -> list : 'T list -> 'T list

1
2
3
4
[1; 2; 3]
|>List.truncate 6
|> printf "%A"
// [1; 2]

Q : List.truncate()List.take() 有何差異?

當 n 大於 Length 時,List.truncate() 不會報錯,只是將整個 list 傳回;但 List.take() 拋出 InvalidOperationException

List.tryFind


找出 list 中符合 predicate function 條件的第一個 element

predicate : ('T -> bool) -> list : 'T list -> 'T option

1
2
3
4
[ 1 .. 10 ]
|> List.tryFind (fun elm -> elm % 2 = 0)
|> printf "%A"
// Some 2

List.find()List.tryFind() 有何差異?

相同

  • 都使用 predicate function 為搜尋條件

相異

  • List.find() 傳回 'T;而 List.tryFind() 傳回 'T option

  • 若找不到資料,List.find() 會拋出 KeyNotFoundException;但 List.tryFind() 只會回傳 None

實務上建議使用 List.tryFind() 取代 List.find(),較為安全

List.tryFindBack


從 list 尾部找出符合 predicate function 條件的第一個 element

predicate:('T -> bool) -> list : 'T list -> 'T option

1
2
3
4
[ 1 .. 10 ]
|> List.tryFindBack (fun elm -> elm % 2 = 0)
|> printf "%A"
// Some 10

Q:List.findBack()List.tryFindBack() 有何差異?

相同

  • 都使用 predicate function 為搜尋條件

相異

  • List.findBack() 傳回 'T;而 List.tryFindBack() 傳回 'T option

  • 若找不到資料,List.findBack() 會拋出 KeyNotFoundException;但 List.tryFindBack() 只會回傳 None

實務上建議使用 List.tryFindBack() 取代 List.findBack(),較為安全

List.tryFindIndex


找出 list 中符合 predicate function 條件的第一 element 的 index

predicate : ('T -> bool) -> list : 'T list -> int option

1
2
3
4
[ 1 .. 10 ]
|> List.tryFindIndex (fun elm -> elm % 2 = 0)
|> printf "%A"
// Some 1

Q:List.findIndex()List.tryFindIndex() 有何差異?

相同

  • 都使用 predicate function 為搜尋條件

相異

  • List.findIndex() 傳回 int;而 List.tryFindIndex() 傳回 int option

  • 若找不到資料,List.findIndex() 會拋出 KeyNotFoundException;但 List.tryFindIndex() 只會回傳 None

實務上建議使用 List.tryFindIndex() 取代 List.findIndex(),較為安全

List.tryFindIndexBack


從 list 尾部找出符合 predicate function 條件的第一 element 的 index

predicate : ('T -> bool) -> list : 'T list -> int option

1
2
3
4
[ 1 .. 10 ]
|> List.tryFindIndexBack (fun elm -> elm % 2 = 0)
|> printf "%A"
// Some 9

Q:List.findIndexBack()List.tryFindIndexBack() 有何差異?

相同

  • 都使用 predicate function 為搜尋條件

相異

  • List.findIndexBack() 傳回 int;而 List.tryFindIndexBack() 傳回 int option

  • 若找不到資料,List.findIndexBack() 會拋出 KeyNotFoundException;但 List.tryFindIndexBack() 只會回傳 None

實務上建議使用 List.tryFindIndexBack() 取代 List.findIndexBack(),較為安全

List.tryHead


傳回 list 第一個 element,相當於 list 的 Head property

list : 'T list -> 'T option

1
2
3
4
[ 1; 2; 3 ]
|> List.tryHead
|> printf "%A"
// Some 1

Q:List.head()List.tryHead() 有何差異?

相同

  • 都傳回 list 第一個 element

相異

  • List.head() 傳回 'T;而 List.tryHead() 傳回 'T option

  • 若找不到資料,List.head() 會拋出 ArgumentException;但 List.tryHead() 只會回傳 None

實務上建議使用 List.tryHead() 取代 List.head(),較為安全

List.tryItem


傳回 list 指定 index 的 element 值,相當於 list 的 Item property

index : int -> list : 'T list -> 'T option

1
2
3
4
[ 1; 2; 3 ]
|> List.tryItem 2
|> printf "%A"
// Some 3

Q:List.item()List.tryItem() 有何差異?

相同

  • 都傳回 list 指定 index 的 element 值

相異

  • List.item() 傳回 'T;而 List.tryItem() 傳回 'T option

  • 若找不到資料,List.head() 會拋出 ArgumentException;但 List.tryHead() 只會回傳 None

實務上建議使用 List.tryItem() 取代 List.item(),較為安全

List.tryLast


傳回 list 最後一個 element

list : 'T list -> 'T option

1
2
3
4
[ 1; 2; 3 ]
|> List.tryLast
|> printf "%A"
// Some 3

Q:List.last()List.tryLast() 有何差異?

相同

  • 都傳回 list 最後一個 element 值

相異

  • List.last() 傳回 'T;而 List.tryLast() 傳回 'T option

  • 若找不到資料,List.last() 會拋出 ArgumentException;但 List.tryLast() 只會回傳 None

實務上建議使用 List.tryLast() 取代 List.last(),較為安全

List.tryPick


找出 list 中符合 chooser function 條件的第一個 element

chooser : ('T -> 'U option) -> list : 'T list -> 'U option

1
2
3
4
5
6
7
8
9
let chooser elm = 
match elm with
| elm when elm % 2 = 0 -> Some elm
| _ -> None

[ 1; 2; 3 ]
|> List.tryPick chooser
|> printf "%A"
// Some 2

Q:List.pick()List.tryPick() 有何差異?

相同

  • 都使用 chooser function 為搜尋條件

相異

  • List.pick() 傳回 'U;而 List.tryPick() 傳回 'U option

  • 若找不到資料,List.pick() 會拋出 KeyNotFoundException;但 List.tryPick() 只會回傳 None

實務上建議使用 List.tryPick() 取代 List.pick(),較為安全

List.unfold


根據 generator function 建立 list,其中 generator function 可指定目前 state 與下一個 state

generator : ('State -> ('T * 'State) option) -> state : 'State -> 'T list

1
2
3
4
5
6
7
8
let generator state =
match state with
|_ when state < 10 -> Some(state, state + 1)
|_ -> None

List.unfold generator 0
|> printf "%A"
// [0; 1; 2; 3; 4; 5; 6; 7; 8; 9]

List.unzip


將所有 element 為 pair 的 list,分解成兩個 list

list : ('T1 * 'T2) list -> 'T1 list * 'T2 list

1
2
3
4
[ (1, 4); (2, 5); (3, 6) ]
|> List.unzip
|> printf "%A"
// ([1; 2; 3], [4; 5; 6])

List.unzip3


將所有 element 為 triple 的 list,分解成三個 list

list : ('T1 * 'T2 * 'T3) list -> 'T1 list * 'T2 list * 'T3 list

1
2
3
4
[ (1, 4, 7); (2, 5, 8); (3, 6, 9) ]
|> List.unzip3
|> printf "%A"
// ([1; 2; 3], [4; 5; 6], [7; 8; 9])

List.where


找出 list 中符合 predicate function 條件的所有 element

predicate : ('T -> bool) -> list : 'T list -> 'T list

1
2
3
4
[ 1; 2; 3]
|> List.where (fun elm -> elm % 2 = 1)
|> printf "%A"
// [1; 3]

List.windowed


依照 list 的 element 順序,依序取出 n 個 element 的 list

windowSize : int -> list : 'T list -> 'T list list

1
2
3
4
[ 1 .. 5 ]
|> List.windowed 3
|> printf "%A"
// [[1; 2; 3]; [2; 3; 4]; [3; 4; 5]]

List.zip


將兩個 list 合併成 element 為 pair 的 list

list1 : 'T1 list -> list2 : 'T2 list -> ('T1 * 'T2) list

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

List.zip list1 list2
|> printf "%A"
// [(1, 4); (2, 5); (3, 6)]

List.zip3


將三個 list 合併成 element 為 triple 的 list

list1 : 'T1 list -> list2 : 'T2 list -> list3 : 'T3 list -> ('T1 * 'T2 * 'T3) list

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

List.zip3 list1 list2 list3
|> printf "%A"
// [(1, 4, 7); (2, 5, 8); (3, 6, 9)]

Conclusion


  • List module 所提供的 function 都必須非常熟練,因為這些都是常用的 Higher Order Function,算是 FP 的基本功

Reference


MSDN, Collection.List Module (F#)

2018-04-07