これは知らなかった... F#ドキュメントには未掲載のコレクション関数(List編)

> * ※以下はF#のドキュメントが更新されれば不必要になるでしょう。
> * 前置きをスキップするときは「Listモジュールの未掲載関数」からお読みください。

F#はMicrosoftの[Don Syme](https://twitter.com/dsyme "https://twitter.com/dsyme")氏が開発したプログラミング言語で、命令型、オブジェクト指向、関数型の要素を併せ持つマルチパラダイム言語と[位置付けられています](https://msdn.microsoft.com/visualfsharpdocs/conceptual/visual-fsharp "https://msdn.microsoft.com/visualfsharpdocs/conceptual/visual-fsharp")。実装はオープンソースの[MITライセンス](https://github.com/fsharp/fsharp/blob/master/License.txt "https://github.com/fsharp/fsharp/blob/master/License.txt")のもと、[githubのリポジトリ](https://github.com/fsharp/fsharp "https://github.com/fsharp/fsharp")で公開されています。

実行環境は.NET Frameworkおよび.NET CoreやMonoといったマルチプラットフォームに対応していますが、サポート状況については各プラットフォームごとの情報を参照してください(「[F# の概要](https://docs.microsoft.com/ja-jp/dotnet/fsharp/get-started/index "https://docs.microsoft.com/ja-jp/dotnet/fsharp/get-started/index")」など)。

# ドキュメントは豊富だが...
F#はMicrosoftが開発しただけあって、ドキュメントが豊富に揃っており、F#に関するさまざまな情報をWebで参照できます。

* [F# のガイド](https://docs.microsoft.com/ja-jp/dotnet/fsharp/ "https://docs.microsoft.com/ja-jp/dotnet/fsharp/")
* [F# のツアー](https://docs.microsoft.com/ja-jp/dotnet/fsharp/tour "https://docs.microsoft.com/ja-jp/dotnet/fsharp/tour")
* [F# 言語リファレンス](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/index "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/index")

また、F#の実行環境となる.NET Frameworkについても、多くのドキュメントが揃っていますので、バージョンの違いさえ把握していれば、充分な情報を得られます。

* [.NET Framework の開発ガイド](https://msdn.microsoft.com/ja-jp/library/hh156542(v=vs.110).aspx "https://msdn.microsoft.com/ja-jp/library/hh156542(v=vs.110).aspx")
* [.NET Framework クラス ライブラリ](https://msdn.microsoft.com/ja-jp/library/mt472912(v=vs.110).aspx "https://msdn.microsoft.com/ja-jp/library/mt472912(v=vs.110).aspx")
* [.NET API ブラウザー](https://docs.microsoft.com/ja-jp/dotnet/api/index "https://docs.microsoft.com/ja-jp/dotnet/api/index")

それでもF#の実装がオープンソースで進められているせいか、ドキュメントの情報が(現時点では)実装に追いついていないところもあるようです。実際、[ドキュメント](https://msdn.microsoft.com/visualfsharpdocs/conceptual/collections.list-module-%5bfsharp%5d "https://msdn.microsoft.com/visualfsharpdocs/conceptual/collections.list-module-%5bfsharp%5d")上はF# 4.0が最新ですが、[実装](https://github.com/fsharp/fsharp "https://github.com/fsharp/fsharp")はF# 4.1がリリースされています。Linuxのfsharpモジュールのバージョンもディストリビューションによってサポートされるバージョンにばらつきがあるようです。日本語版ドキュメントは更に情報が遅くなる場合があります。

そこで、ここではF#のプログラミングでよく使われるリストを扱うListモジュール(リファレンス:[日本語](https://msdn.microsoft.com/ja-jp/library/ee353738(v=vs.120).aspx "https://msdn.microsoft.com/ja-jp/library/ee353738(v=vs.120).aspx"), [英語](https://msdn.microsoft.com/visualfsharpdocs/conceptual/collections.list-module-%5bfsharp%5d "https://msdn.microsoft.com/visualfsharpdocs/conceptual/collections.list-module-%5bfsharp%5d"))で実装されているのにドキュメント([英語版2017/8/18更新分](https://msdn.microsoft.com/visualfsharpdocs/conceptual/collections.list-module-%5bfsharp%5d "https://msdn.microsoft.com/visualfsharpdocs/conceptual/collections.list-module-%5bfsharp%5d"))には掲載されていない関数(以下、**未掲載関数**という)を抜粋して取り上げます。F#のバージョンは4.1([FSharp.Core](https://github.com/fsharp/fsharp/tree/master/src/fsharp/FSharp.Core "https://github.com/fsharp/fsharp/tree/master/src/fsharp/FSharp.Core") 4.4.1.0)とします。

# リストとListモジュール
未掲載関数を紹介する前に、[F#のリスト](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/lists "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/lists")と[Listモジュール](https://msdn.microsoft.com/ja-jp/library/ee353738(v=vs.120).aspx "https://msdn.microsoft.com/ja-jp/library/ee353738(v=vs.120).aspx")について簡単に整理しておきます。

## F#のリスト
F#のリストは`[要素; 要素; ...]`で表されます。同一リスト内で要素の型はすべて同じです。型名は`'T list`で表されます(`'T`は`int`や`string`などリストに対応した型)。

```fsharp
[2; 5; 8]           // int list
["F#"; "fsharp"]    // string list
// ["F#"; 4.1] などは無効
```

F#ではリスト以外にも複数の要素を持つ型がいくつかありますが、リストで特徴的なのは`::`という演算子です。これは`h::t`のようにすると、`h`に先頭の要素、`t`にそれ以外の要素を持つリストが割り当てられることを表します。

```fsharp
let a = [1; 2; 3]    // letは値の定義(再代入不可)を表す。let mutableで再代入可能になる
match a with         // aの内容に応じて処理を振り分ける
| h::t -> printfn "h = %A, t = %A" h t    // "h = 1, t = [2; 3]"と表示
| _    -> ()         // (ここではこの部分は実行されない)
```

これにより、再帰関数による繰り返し処理が可能になります。

```fsharp
let rec sum a =           // recは再帰関数を表す
    match a with
    | h::t -> h + sum t   // ここで再帰処理(先頭の要素 + sum 残りの要素を持つリスト)
    | [] -> 0             // 再帰処理の終結(リストの要素がなくなったら再帰終了)
// sum [2; 5; 8] = 15
```

## F#のListモジュール
Listモジュールでは、リストを扱うさまざまな関数が定義されているので、知っていると改めて関数を定義する手間が省けます。上記のsum関数も、実は`List.sum`としてあらかじめ定義されています。

```fsharp
List.sum [2; 5; 8]        // 15
List.sum [2.2; 5.5; 8.8]  // 16.5
```

ただ、Listモジュールの関数を扱うには注意すべき点もあります。それは例外処理が必要な場合があることです。

たとえば`List.find`はリストから条件に適合する最初の要素を抽出する関数ですが、そうした要素が存在しないときは例外が発生(F#では[raiseなど](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/ "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/"))します。

```fsharp:例外処理がない場合
List.find (fun (s: string) -> s.IndexOf("g") >= 0) ["F#"; "fsharp"]
// (fun...)に適合するリストの要素が存在しないため、System.Collections.Generic.KeyNotFoundExceptionが発生
```

このように、条件に適合する要素を見つけられない可能性があるときは`List.tryFind`関数に置き換えると、戻り値が'T option型になるため例外処理を回避できます。'T option型の戻り値は、条件に適合する値aがあるときは`Some a`を、そうした値がないときは`None`となります。

```fsharp:List.tryFind関数の実行例
List.tryFind (fun (s: string) -> s.IndexOf("f") >= 0) ["F#"; "fsharp"]
// Some "fsharp"
List.tryFind (fun (s: string) -> s.IndexOf("g") >= 0) ["F#"; "fsharp"]
// None
```

`'T option`型の`Some a`からaを取り出すには、`match-with`や`Option`モジュールが使えます。

```fsharp
let v = Some 3
match v with
| Some a -> a    // Some a からaを取り出す
| None   -> 0

Option.get (Some 3)    // 3 - Some aからaを取り出す
Option.iter (fun s -> printfn "%s" s) (Some "F#")    // "F#"と表示
```

# Listモジュールの未掲載関数
前置きが長くなりましたが、ここからListモジュールの未掲載関数を抜粋で紹介していきます。
## List.except リスト1 リスト2
リスト2の要素からリスト1の要素を削除します。同じ値であれば、重複する要素もすべて取り除かれます。

```fsharp:List.except関数の実行例
List.except [2; 5] [5; 2; 8; 5; 2; 11]    // [8; 11]
```

## List.findBack 条件 リスト / <br/> List.tryFindBack 条件 リスト
`List.findBack`は条件に適合する要素のうち、リストの末尾に最も近いものを抽出します。`List.find`とは逆方向からの探索になります。適合する要素が見つからないときは`List.find`と同じく`System.Collections.Generic.KeyNotFoundException`が発生します。

```fsharp:List.findとList.findBackの実行例
let a = [(1, "F#"); (2, "F#"); (3, "F#")]
List.find     (fun (n, s) -> s = "F#") a    // (1, "F#")
List.findBack (fun (n, s) -> s = "F#") a    // (3, "F#")
List.findBack (fun (n, s) -> s = "C#") a    // 例外発生「System.Collections.Generic.KeyNotFoundException」
```

`List.tryFindBack`は`findBack`と同様の処理をしますが、結果は'T option型となります。条件に適合する要素aが見つかった時は`Some a`、見つからなかったときは`None`です。

```fsharp:List.tryFindBackの実行例
let a = [(1, "F#"); (2, "F#"); (3, "F#")]
List.tryFindBack (fun (n, s) -> s = "F#") a    // Some (3, "F#")
List.tryFindBack (fun (n, s) -> s = "C#") a    // None
```

## List.findIndexBack 条件 リスト / <br/> List.tryFindIndexBack 条件 リスト
`List.findIndexBack`は条件に適合する最後の要素の位置を得られます。リストの先頭は0とします。条件に適合する最初の要素の位置は`List.findIndex`で得られます。どちらの関数でもそうした要素を見つけられなかったときは`System.Collections.Generic.KeyNotFoundException`が発生します。

```fsharp:List.findIndexとList.findIndexBackの実行例
let a = [(1, "F#"); (2, "F#"); (3, "F#")]
List.findIndex     (fun (n, s) -> s = "F#") a    // 0
List.findIndexBack (fun (n, s) -> s = "F#") a    // 2
List.findIndexBack (fun (n, s) -> s = "C#") a    // 例外発生「System.Collections.Generic.KeyNotFoundException」
```

`List.tryFindIndexBack`は`List.findIndexBack`と同様の処理をしますが、結果は'T option型となります。条件に適合する要素の位置aが見つかった時は`Some a`、見つからなかったときは`None`です。

```fsharp:List.tryFindIndexBackの実行例
let a = [(1, "F#"); (2, "F#"); (3, "F#")]
List.tryFindIndexBack (fun (n, s) -> s = "F#") a    // Some 2
List.tryFindIndexBack (fun (n, s) -> s = "C#") a    // None
```

## List.groupBy キー振り分け処理 リスト
振り分け処理に基づいてキーごとにリストの要素を振り分けます。結果はキーとリストをタプルで組にした要素のリストとなります。

```fsharp:List.groupByの実行例
List.groupBy (fun n -> if n % 2 = 0 then "even" else "odd") [1..9]
// [("odd", [1; 3; 5; 7; 9]); ("even", [2; 4; 6; 8])]
```

## List.last リスト / List.tryLast リスト
`List.last`はリスト内の最後の要素を抽出します。リストの要素数が0のときは`System.ArgumentException`が発生します。これを配列に対して行う`Array.last`も定義されています。

```fsharp:List.lastの実行例
List.last [1; 2; 3]  // 3
List.last<int> []    // 例外発生「System.ArgumentException: 入力リストが空でした。」
```

`List.tryLast`はリストの末尾に要素aが存在するときには`Some a`、存在しないときには`None`を得られます。これを配列に対して行う`Array.tryLast`も定義されています。リストの要素数が0になる可能性があるときは、こちらを使うと例外発生を回避できます。

```fsharp:List.tryLastの実行例
List.tryLast [1; 2; 3]  // Some 3
List.tryLast<int> []    // None
```

## List.pairwise リスト
隣同士の要素をタプルで組にした要素のリストを得られます。リストに要素がなければ、結果もそのままです。

```fsharp:List.pairwiseの実行例
List.pairwise [1; 2; 3; 4]    // [(1, 2); (2, 3); (3, 4)]
```

## List.skip 位置 リスト
指定した位置(先頭は0)以降の要素を持つリストを得られます。位置の値がリストの要素数と同じときは`[]`(要素なしのリスト)、それを超えるときは`System.ArgumentException`が発生します。位置の値が0より小さい時は0とみなされます。

```fsharp:List.skipの実行例
List.skip 2 [1; 2; 3; 4; 5]    // [3; 4; 5]
List.skip 6 [1; 2; 3; 4; 5]    // 例外発生「System.ArgumentException: インデックスは有効範囲外です。」
```

## List.skipWhile 条件 リスト
リストの先頭から条件に適合する要素が連続していれば、それらが取り除かれたリストを得られます。そうした要素が先頭から連続していないところに存在していても取り除かれません。そのため`List.sort`の実行後には特に有効でしょう。

```fsharp:List.skipWhileの実行例
List.skipWhile (fun n -> n < 3) [1; 2; 3; 4; 5]          // [3; 4; 5]

// ソートせずに実行
List.skipWhile (fun n -> n < 3) [1; 2; 3; 4; 1; 2; 5]    // [3; 4; 1; 2; 5]

// ソートしてから実行
[3; 1; 2; 5; 3; 4; 2; 1]
|> List.sort
|> List.skipWhile (fun n -> n < 3)    // [3; 3; 4; 5]
```

## List.splitInto 分割数 リスト
リストを指定された数のリストに分割します。結果は分割されたリストをネストしたリストになります。分割されたリストは先頭に近いほうが要素数が多くなります。分割数が0以下のときは`System.ArgumentException`が発生します。一方、分割数がリストの要素数より多い時は要素を1つ持つリストをネストしたリストになります。

```fsharp:List.splitIntoの実行例
List.splitInto 0 [0..4]    // 例外発生「System.ArgumentException: 入力は正である必要があります。」
List.splitInto 1 [0..4]    // [[0; 1; 2; 3; 4]]
List.splitInto 2 [0..4]    // [[0; 1; 2]; [3; 4]]
List.splitInto 3 [0..4]    // [[0; 1]; [2; 3]; [4]]
List.splitInto 4 [0..4]    // [[0; 1]; [2]; [3]; [4]]
List.splitInto 5 [0..4]    // [[0]; [1]; [2]; [3]; [4]]
List.splitInto 6 [0..4]    // [[0]; [1]; [2]; [3]; [4]]
```

## List.take 要素数 リスト
リストの先頭から指定された数の要素をもつリストを得られます。要素数が負の数の時は`System.ArgumentException`が発生します。また、要素数がリストの要素数より多い時は`System.InvalidOperationException`が発生します。要素数が0のときは`[]`(要素なしのリスト)となります。

これと同様の処理を行う`List.truncate`関数では例外が発生しません。必要に応じて使い分けてください。

```fsharp:List.takeの実行例
List.take -1 [0..3]    // 例外発生「System.ArgumentException: 入力は負以外である必要があります。」
List.take  0 [0..3]    // []
List.take  2 [0..3]    // [0; 1]
List.take  5 [0..3]    // 例外発生「System.InvalidOperationException: 入力シーケンスには十分な数の要素がありません。」
```

## List.takeWhile 条件 リスト
リストの先頭から連続して条件に適合する要素を持つリストを得られます。リストの途中だけにそうした要素があっても抽出されません。

```fsharp:List.takeWhileの実行例
List.takeWhile (fun n -> n < 3) [2; 1; 5; 8; 1; 2]    // [2; 1]
```

## List.truncate 要素数 リスト
`List.take`と同様に、先頭から要素数だけ連続した要素を持つリストを得られます。結果は、要素数が0以下のときは`[]`(要素なしのリスト)で、要素数がリストのそれより多いときは元のリストが得られるため、`List.take`のような例外は発生しません。必要に応じて使い分けてください。

```fsharp:List.truncateの実行例
List.truncate -1 [1; 2; 3]    // [] (例外は発生しない)
List.truncate  0 [1; 2; 3]    // []
List.truncate  1 [1; 2; 3]    // [1]
List.truncate  2 [1; 2; 3]    // [1; 2]
List.truncate  3 [1; 2; 3]    // [1; 2; 3] (元のリストと同じ)
List.truncate  4 [1; 2; 3]    // [1; 2; 3] (例外は発生しない)
```

## List.tryHead リスト
リストの先頭の要素aが存在するときは`Some a`、空のリストの時は`None`を得られます。

```fsharp:List.tryHeadの実行例
List.tryHead<int> [1; 2; 3]    // Some 1
List.tryHead<int> []           // None
```

## List.tryItem 位置 リスト
指定した位置(先頭は0)に要素aが存在すれば`Some a`、存在しなければ`None`を得られます。インデックスでリストの要素数を超えた位置(`[1; 2; 3].[4]`など)を指定すると`System.ArgumentException`が発生しますが、`List.tryItem`では結果が`'T option`型となり、例外が発生しません。

```fsharp:List.tryItemの実行例
List.tryItem  1 [1; 2; 3]    // Some 2
List.tryItem -1 [1; 2; 3]    // None (位置の指定が負の数)
List.tryItem  4 [1; 2; 3]    // None (位置が要素数を超えている)
```

## List.windowed 要素数 リスト
現在位置から指定した数だけ連続した要素を持つリストをネストしたリストを得られます。要素数が0以下のときは`System.ArgumentException`が発生します。

```fsharp:List.windowedの実行例
List.windowed 3 [1; 3; 5; 7; 9]    // [[1; 3; 5]; [3; 5; 7]; [5; 7; 9]]
List.windowed 0 [1; 3; 5; 7; 9]    // 例外発生「System.ArgumentException: 入力は正である必要があります。」
```
---
ここまでドキュメント([英語版2017/8/18更新分](https://msdn.microsoft.com/visualfsharpdocs/conceptual/collections.list-module-%5bfsharp%5d "https://msdn.microsoft.com/visualfsharpdocs/conceptual/collections.list-module-%5bfsharp%5d"))には掲載されていない未掲載関数を抜粋で紹介しました。ドキュメントの進捗状況は[githubのissue](https://github.com/MicrosoftDocs/visualfsharpdocs/issues/184 "https://github.com/MicrosoftDocs/visualfsharpdocs/issues/184")でまとめられていて、ドキュメントができているものにはチェックがつけられています。これによると、Listモジュール以外にもドキュメントが進んでいない関数を見て取れます。

ドキュメントが更新されたら、このページは不要になると思いますが、その時までご覧になった方々の参考になればと思います。