特集:C#開発者のためのF#入門(前編)

F#で初めての関数型プログラミング

bleis-tift
2012/04/12
Page1 Page2 Page3

リストとタプル

 リストとタプルはF#でよく使うデータ構造だが、タプルに関しては誤解が多いように思う。この2つは本来、並べて説明するようなものではないのだが、ここでは違いをはっきりとさせるためにあえて順番に説明する。

リスト

 リストというと、連結リストとして以下の図を用いて説明されることが多い。

一般的なリストのイメージ図

 しかし、この図では「次の要素への参照」を持っており、つなぎ替えなどが行えるように錯覚してしまう。

 F#でのリストは、再帰的に定義されており、どちらかというと以下のようなイメージを持ってほしい。

F#でのリストのイメージ図

 この図から分かるように、リストはその中に長さが1つ小さいリストを保持するような構造になっている。リストの最後は空のリストで、空の角カッコ(=ブラケット)として表現する。

 リストは「同じものを複数個」扱いたいときに使うデータ構造で、例えば「文字列のリスト」や「Person型のリスト」などが実現できる。以下は文字列のリストの例である。

let strs = [ "hoge"; "piyo"; "foo"; "bar" ]
文字列のリストを定義するコード例(F#):セミコロン(;)で各要素を区切る

 このように、F#ではリストは角カッコ(=ブラケット)で表現する。リストの各要素をセミコロン(;)で区切る点に注意してほしい。

 またF#では、次のように、改行とインデントによって各要素を区切ることもできる。

// 開きカッコよりも深い位置に要素のインデントを合わせることで、
// セミコロンを省略できる
let strs =
  [
    "hoge"
    "piyo"
    "foo"
    "bar"
  ]
文字列のリストを定義するコード例(F#):改行とインデントで各要素を区切る

 このような(改行とインデントによる)セミコロンの省略は、リスト以外でも使うことができる。

 リストの型は、「string list」のように要素の型に続けて「list」と書いて表現する(つまり、例えば「let strs : string list = [ "hoge" ]」のように記述できる)。「list<string>」のようにC#風に表現することもできるが、リストの場合は前者の表現をするのが一般的である。使い分けの指針としては、小文字で始まる型は前者の表現を使い(例えば「int seq」)、それ以外の型はC#風に後者の表現を使えばいいだろう(例えば「Set<int>」)。

タプル

 タプルはリストとは異なり、そもそもコレクションではない。どちらかというと「フィールド名のないクラス」のようなものだ。例えば、名前と年齢をまとめて扱いたい場合、型を定義するまでもない状況であれば、string型の値とint型の値で構成される2要素のタプルを使えばいい。

 次のコードはその例で、タプルでまとめた値をリスト化している。

let people = [("aaa bbb", 24); ("ccc ddd", 30)]
2要素のタプルでまとめた値をリストにするコード例(F#)

 このようにタプルは、丸カッコ(=パーレン)の中にカンマ(,)で要素を区切って記述する。この変数「people」の要素の型は、「string * int」のように、各要素の型をアスタリスク(*)で区切って表す(つまり、例えば「let people : (string * int) list = [("aaa bbb", 24)]」のように記述できる)。

 苗字と名前を分けて3要素のタプルとする場合は、「string * string * int」という型になり、その値は以下のように記述できる。

let person = ("aaa", "bbb", 24)
3要素のタプルの記述例(F#)

 タプルを囲む丸カッコは省略できる場合もあるが、本記事では常に省略せずに記述している。

 タプルはコレクションではないので、タプルの「長さ」というものを考えることに意味はない。2要素のタプルと、3要素のタプルは全く別の型となっており、タプルの要素数はコンパイルする時点で決定しているため、実行時には「可変長のタプル」は扱えないのである。そして、要素ごとに別の型が持てるため、要素を走査して何か処理するような関数は記述できない(コレクションではないといったのはこれが理由だ)。そのため、リストがListモジュールに多くのリスト操作用関数を持つのに対して、タプルはそもそもTupleモジュールというものが存在しない。

 タプルは、複数の値をそれぞれの型を保持したまま1つの値として扱えるため、多引数関数や多値を返す関数を実現するために使うこともできる。例えば、ここまでは多引数関数は、

let f x y = x + y
let ans = f 10 20
通常の多引数関数のコード例(F#)

のように記述していたが、タプルを用いて、

let f (x, y) = x + y
let ans = f (10, 20)  // タプルで2つの値をまとめて渡す
タプルを用いた多引数関数のコード例(F#)

と記述することもできる。

 特別な理由がなければタプルを用いない前者の形式を推奨するが、多値を返す関数と組み合わせて使う場合は、タプルを用いた関数の方が便利である。例えば、

let f x = (x, x * 3)  // タプルを使って、多値を返す関数
let g (x, y) = x * y  // 通常、「g (2, 3)」のようにして呼び出す
「2要素のタプルの値を返す関数」と「2要素のタプルを引数に取る関数」の定義例(F#)

という関数「f」と「g」があった場合、f関数の結果をg関数に直接渡すことができる(次のコードを参照)。

// f関数の結果を直接g関数に渡す
let ans = g (f 10)
System.Console.WriteLine(ans);; // 300

// もしくは、以下のコードを記述する
let ans = f 10  // f関数の結果を受け取って
let ans = g ans // それをg関数に渡す
System.Console.WriteLine(ans);; // 300
「2要素のタプルの値を返す関数」の実行結果を、「2要素のタプルを引数に取る関数」に渡すコード例(F#)

 これと同じことをC#で実現しようとした場合、C#では多引数関数とタプルには何の関係もないため、次のように書く必要がある。

public static System.Tuple<int, int> F(int x)
{
  return System.Tuple.Create(x, x * 3);
}

// 「G(2, 3)」のようにして呼び出す用のGメソッド
public static int G(int x, int y)
{
  return x * y;
}

// 「G(F(10))」のようにFメソッドの結果を直接渡して呼び出す用のGメソッド
public static int G(System.Tuple<int, int> x)
{
  return G(x.Item1, x.Item2);
}
多値を返す関数とタプルを組み合わせて使う場合の各種メソッドの定義例(C#)

 比較してみると、F#ではタプルをより便利に、より気軽に扱えるようになっているといえる。

リストとタプルの違い

 F#でのリストとタプルを見てきたが、これらの違いが分かっただろうか? 両者の違いは、テーブル構造(次の図を参照)を思い浮かべてもらうと分かりやすいだろう。

表1 Person型の情報を格納するテーブル構造

 表1のような構造で、タプルは複数の「列」をまとめて扱いたい場合に使用し、リストは複数の「行」をまとめて扱いたい場合に使用する。コードで書くと次のようになる。

let person = ("hoge", 24)               // タプル
let xs = ["hoge"; "piyo"]               // リスト
let ps = [("hoge", 24); ("piyo", 20)]   // タプルのリスト
タプルとリストの違いを示すコード例(F#)

 これを図で表すと、次のようになる。

上記の「タプルとリストの違いを示すコード例(F#)」のイメージ図

 今回は、F#の概要や、関数型プログラミングの基礎、F#でよく使われるデータ構造のリストとタプルを説明した。次回後編では、F#でプログラムを書くに当たって必要最低限の基礎文法について説明する。end of article


 INDEX
  特集:C#開発者のためのF#入門(前編)
  F#で初めての関数型プログラミング
    1.F#とは
    2.関数型プログラミングの基礎
  3.リストとタプル
 
  特集:C#開発者のためのF#入門(後編)
  F#言語の基礎文法
    1.主要な文法: if式/letキーワード/レコード
    2.主要な文法: 判別共用体/パターン・マッチ


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間