- ※以下はF#のドキュメントが更新されれば不必要になるでしょう。
- 前置きをスキップするときは「Listモジュールの未掲載関数」からお読みください。
F#はMicrosoftのDon Syme氏が開発したプログラミング言語で、命令型、オブジェクト指向、関数型の要素を併せ持つマルチパラダイム言語と位置付けられています。実装はオープンソースのMITライセンスのもと、githubのリポジトリで公開されています。
実行環境は.NET Frameworkおよび.NET CoreやMonoといったマルチプラットフォームに対応していますが、サポート状況については各プラットフォームごとの情報を参照してください(「F# の概要」など)。
ドキュメントは豊富だが...
F#はMicrosoftが開発しただけあって、ドキュメントが豊富に揃っており、F#に関するさまざまな情報をWebで参照できます。
また、F#の実行環境となる.NET Frameworkについても、多くのドキュメントが揃っていますので、バージョンの違いさえ把握していれば、充分な情報を得られます。
それでもF#の実装がオープンソースで進められているせいか、ドキュメントの情報が(現時点では)実装に追いついていないところもあるようです。実際、ドキュメント上はF# 4.0が最新ですが、実装はF# 4.1がリリースされています。Linuxのfsharpモジュールのバージョンもディストリビューションによってサポートされるバージョンにばらつきがあるようです。日本語版ドキュメントは更に情報が遅くなる場合があります。
そこで、ここではF#のプログラミングでよく使われるリストを扱うListモジュール(リファレンス:日本語, 英語)で実装されているのにドキュメント(英語版2017/8/18更新分)には掲載されていない関数(以下、未掲載関数という)を抜粋して取り上げます。F#のバージョンは4.1(FSharp.Core 4.4.1.0)とします。
リストとListモジュール
未掲載関数を紹介する前に、F#のリストとListモジュールについて簡単に整理しておきます。
F#のリスト
F#のリストは[要素; 要素; ...]
で表されます。同一リスト内で要素の型はすべて同じです。型名は'T list
で表されます('T
はint
やstring
などリストに対応した型)。
[2; 5; 8] // int list
["F#"; "fsharp"] // string list
// ["F#"; 4.1] などは無効
F#ではリスト以外にも複数の要素を持つ型がいくつかありますが、リストで特徴的なのは::
という演算子です。これはh::t
のようにすると、h
に先頭の要素、t
にそれ以外の要素を持つリストが割り当てられることを表します。
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]"と表示
| _ -> () // (ここではこの部分は実行されない)
これにより、再帰関数による繰り返し処理が可能になります。
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
としてあらかじめ定義されています。
List.sum [2; 5; 8] // 15
List.sum [2.2; 5.5; 8.8] // 16.5
ただ、Listモジュールの関数を扱うには注意すべき点もあります。それは例外処理が必要な場合があることです。
たとえばList.find
はリストから条件に適合する最初の要素を抽出する関数ですが、そうした要素が存在しないときは例外が発生(F#ではraiseなど)します。
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
となります。
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
モジュールが使えます。
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の要素を削除します。同じ値であれば、重複する要素もすべて取り除かれます。
List.except [2; 5] [5; 2; 8; 5; 2; 11] // [8; 11]
List.findBack 条件 リスト /
List.tryFindBack 条件 リスト
List.findBack
は条件に適合する要素のうち、リストの末尾に最も近いものを抽出します。List.find
とは逆方向からの探索になります。適合する要素が見つからないときはList.find
と同じくSystem.Collections.Generic.KeyNotFoundException
が発生します。
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
です。
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 条件 リスト /
List.tryFindIndexBack 条件 リスト
List.findIndexBack
は条件に適合する最後の要素の位置を得られます。リストの先頭は0とします。条件に適合する最初の要素の位置はList.findIndex
で得られます。どちらの関数でもそうした要素を見つけられなかったときはSystem.Collections.Generic.KeyNotFoundException
が発生します。
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
です。
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 キー振り分け処理 リスト
振り分け処理に基づいてキーごとにリストの要素を振り分けます。結果はキーとリストをタプルで組にした要素のリストとなります。
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
も定義されています。
List.last [1; 2; 3] // 3
List.last<int> [] // 例外発生「System.ArgumentException: 入力リストが空でした。」
List.tryLast
はリストの末尾に要素aが存在するときにはSome a
、存在しないときにはNone
を得られます。これを配列に対して行うArray.tryLast
も定義されています。リストの要素数が0になる可能性があるときは、こちらを使うと例外発生を回避できます。
List.tryLast [1; 2; 3] // Some 3
List.tryLast<int> [] // None
List.pairwise リスト
隣同士の要素をタプルで組にした要素のリストを得られます。リストに要素がなければ、結果もそのままです。
List.pairwise [1; 2; 3; 4] // [(1, 2); (2, 3); (3, 4)]
List.skip 位置 リスト
指定した位置(先頭は0)以降の要素を持つリストを得られます。位置の値がリストの要素数と同じときは[]
(要素なしのリスト)、それを超えるときはSystem.ArgumentException
が発生します。位置の値が0より小さい時は0とみなされます。
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
の実行後には特に有効でしょう。
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つ持つリストをネストしたリストになります。
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
関数では例外が発生しません。必要に応じて使い分けてください。
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 条件 リスト
リストの先頭から連続して条件に適合する要素を持つリストを得られます。リストの途中だけにそうした要素があっても抽出されません。
List.takeWhile (fun n -> n < 3) [2; 1; 5; 8; 1; 2] // [2; 1]
List.truncate 要素数 リスト
List.take
と同様に、先頭から要素数だけ連続した要素を持つリストを得られます。結果は、要素数が0以下のときは[]
(要素なしのリスト)で、要素数がリストのそれより多いときは元のリストが得られるため、List.take
のような例外は発生しません。必要に応じて使い分けてください。
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
を得られます。
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
型となり、例外が発生しません。
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
が発生します。
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更新分)には掲載されていない未掲載関数を抜粋で紹介しました。ドキュメントの進捗状況はgithubのissueでまとめられていて、ドキュメントができているものにはチェックがつけられています。これによると、Listモジュール以外にもドキュメントが進んでいない関数を見て取れます。
ドキュメントが更新されたら、このページは不要になると思いますが、その時までご覧になった方々の参考になればと思います。