++C++; // 未確認飛行 C ブログ

http://ufcpp.net/

C# 7に向けて(8): Tuples

with 6 comments

C# 7 Proposals な内容、あと Method Contracts だけになったと一息ついたところで、新しい Issue ページが立つわけですが。

タプル、つまり、型名を持たない構造化データ/データの一時的なグループ化の話。

これは結構気合の入った提案文章になっているので、全訳気味に紹介。

System.Tuple のおかげでタプルという言葉だけではあまり期待感が持てないかもしれませんが、C# 開発者的にインパクトのある言葉で表現すると「アセンブリをまたげる匿名型」「メソッド引数・戻り値やフィールドに使える匿名型」です。

背景

「複数の値の一時的にグループ化」が必要な最もよくある例は、メソッドの引数リストでしょう。C# をはじめ、多くのプログラミング言語がこれをサポートしています。要するに、複数の引数を持つメソッドには、F(1, “abc”) というように複数の値を渡すわけですが、ここで、(1, “abc”) というような値のグループ化が行われていると考えられます。

この次に値のグループ化を必要とするのは、引数の逆、つまり戻り値です。この需要、「多値戻り値」を楽に書けるプログラミング言語はとたんに少なくなります。最近では多値戻り値が使えるプログラミング言語が増えてきていますが、C# もC# 7でその流れを追従することになりそうです。

まず、C# の現状がどうかというと…

out 引数

戻り値を複数返したい場合の1つ目の選択肢は out 引数です。

利点

  • 複数の戻り値それぞれがちゃんと名前を持つ
    • しっかりした命名は下手なコメントや仕様書よりもよっぽど重要
    • どの値が何を意味するかわかりやすくなる

欠点

  • 非同期メソッドの戻り値に使えない
  • 使う側で、事前に変数の準備が必要
    • 無駄に行数が増える(複数のステートメントが必要)
    • var が使えない

System.Tuple

「無名の一時的な値のグループ化」を意味する型として、 .NET 4 で Tuple クラスが追加されました。

利点

  • 非同期メソッドの戻り値にもできる(Task<Tuple<T1, T2, …>>)

欠点

  • 名前が消える(Item1, Item2, … という意味のない名前になる)
  • ヒープ確保が必要(Tuple は参照型なので)

名前が消えるという結構嫌な欠点の割に、Tuple クラスは使いやすいかというと微妙… Tuple.Create(1, “abc”) とかが煩雑。

データ移送(transport)用の型を作る

現状、out 引数や System.Tuple の欠点を回避しようとすると、普通に明示的な型定義が必要になります。

各値の名前も消えず、非同期メソッドでも使えて、struct にしたのでヒープ確保も起きません。

型名にちゃんとした意味があれば、疑うべくもなく型を作るのがよい方法だと思います。「一時的」じゃなく、その後もずっと使うようなものであれば、もっと型を作る意義が出てくるでしょう。

ですが、この例の TallyResult (Tally メソッドの結果なんて、見りゃわかるだろ)なんて名前に特に意味もないわけで、あまりよい方法とは言えません。もちろん、SumAndCount (メンバーが Sum と Count なんだから見りゃわかる)なんて名前も論外。値も、だいたいすぐに sum と count に分解して使うようなものです。特定用途下に置いていい名前が付くことがあるかもしれませんが(我々のプログラム中では、A という概念が sum と count を必要とするので、これを A と命名しよう。とか)、それはそれで、別の用途に使えなくなります。

タプル構文

そこで、(System.Tuple ではなく、C# の言語機能として)タプル型を導入しようという提案が出ています。

書き方は、以下のような理由から、引数リストと似た構文になるようです。

  • 引数リストは、背景での説明通り、概念的にはタプルに近い
  • タプルを導入したい一番の理由が多値戻り値
    • 引数と戻り値は、入ってくるものと出ていくもの、表裏一体
    • 「新構文の追加」というより、「引数と戻り値の対称性の改善」と考えればよくて、現状の構文になじみやすい

タプル型

C# では、いわゆる「名前付きタプル」だけ導入するつもりみたいです。一般にタプルというと、(int, string) みたいな、メンバー名のない、ほんと単なる値のグループ化を指したりするものですが、こういう「メンバー名のないタプル」はなし。

この例の (int sum, int count) で、 struct Anonymous { public int sum; public int count; } みたいな構造体が(型名なしで)作られる感じ。

タプルは、非同期メソッドの戻り値にも使えます。

タプル リテラル

リテラル用の構文を追加しなくても、一応、var t = new (int sum, int count) { sum = 0, count = 0 } とかいう書き方はできるでしょうが、あまりにも煩雑なので、ちゃんとリテラル構文も。

タプル型自体が「引数リストの定義」(仮引数リスト)みたいな書き方なんだから、タプル リテラルは「引数リストへの値の渡し方」(実引数リスト)みたいであるべきです。

タプルの分解

値を(無名のまま)構造化(construction)するのがタプルなら、その逆、脱構造化(deconstruction)操作も欲しくなります。タプルはほんとに一時的なグループ化に使うことが多く、値を受け取る側は即座に、値を別々の変数に入れて使いたかったりします。var t = (sum: 1, count: 2) でタプル化できるんなら、(var sum, var count) = t;でタプルの分解をしたい。ちゃんとそのための構文も用意する予定です。ただし、分解は、変数宣言時のみ認めるのか、既存の変数に対する代入でも認めるのかはまだ未定だそうです。

詳細

大まかな文法の提案に加えて、細かにもう少し詰めて考えないと行けないことがあります。

クラスか構造体か

構造体(値型)は、「ヒープ確保が必要ないけども、値のコピーが発生する」という性質上、一般的には、小さいデータや、他の型に埋め込んで使う(他の変数にコピーすることが少ない)場合に有効です。用途を絞らないと、逆に性能的に不利になることが多いです。そのためか、System.Tuple 型はクラス(参照型)ですし、F# のタプルはやっぱりクラスです。

しかし、C# 7でのタプルは多値戻り値を主な動機にしているのもあって、おそらくは非常に短命(構築して、戻り値として返して、すぐに分解して複数の変数で受けとる)になると予想されます。この場合であれば、構造体(値型)が有利に働くはず。なので今のところ、C# 7のタプルは構造体になりそうです。

書き換え可能性

C# 7に向けて(1): 変数やフィールドの書き換え」で説明したように、値が書き変わらない保証がほしいときがあります。もちろん書き替えたい場合もあるわけで、選べるのが望ましいです。

同リンク先で話しているように、構造体の場合であれば、readonly 修飾(浅い不変性)を付けるだけで、フィールドの書き換えができなくなります(一方、クラスの場合はreadonlyだけではダメ。深い不変性が必要)。

なので、タプルには、構造体を採用した上で、何もつけなければ mutable (書き換えできる)、readonly を付けることで immutable (書き換え不能)にするようです。

値セマンティクス

構造体のような値型の場合、Equals と GetHashCode が、全フィールドの比較 / 全フィールドのハッシュ値からの計算で自動的に実装されます。これも、タプルを構造体にしたい動機になります。

ただ、この自動実装された Equals や GetHashCode は必ずしも効率的な結果にならないので、もしかしたらコンパイラーに(既定動作ではない)GetHashCode の生成をさせる必要があるかもしれません(要計測・要確認)。

タプルをフィールドとして利用

最たる動機が多値戻り値とはいえ、他にフィールドとして使いたい用途もあるでしょう。直接そういうフィールドを作ることは少ないと思いますが、例えば、ジェネリック型引数にタプルを指定することはあります。例えば複合キーや、複数値を持つ辞書を作りたいとき、Dictionary<(int key1, int key2), (int value1, int value2)> みたいな型を作る方法が有効ですが、この時、タプル型のフィールドができます。

ただ、タプルは mutable にするつもりなので、マルチスレッド動作の際には注意が必要です。他のオブジェクトのフィールドになることで、複数のスレッドが同時にタプルの値を書き替えようとして競合する場合がありえます。

変換

あるタプル型から別のあるタプル型に、「メンバーごとの変換」(member-wise conversions)が考えられます。

(issue ページでは「やる」とは名言されておらず、「できない理由はないよね」「やれるけど」「たぶんできるとよさそう」的書き方)

アセンブリをまたいでの単一化

こんな便利そうなタプルですが、これまで入らなかった主な理由というか、最大の課題は、アセンブリをどうやってまたぐか。

.NET では、異なるアセンブリの中に、完全に同名・同名前空間・同機能のクラスを定義できますが、これはアセンブリごとにそれぞれ別の型と認識されます。赤の他人が同名の別クラスを定義してしまったことによって、クラス利用者のコードが壊れるようなことがあってはいけないので当然といえば当然の処置。しかしこれが、アセンブリをまたいだ匿名クラス・匿名構造体の利用を難しくしています。タプルは実装上、匿名構造体になるわけで、この問題をクリアしないと、アセンブリを超えた利用が面倒です。

あり得る解決策は例えば、

  • .NET ランタイムに手を入れて、別アセンブリ同名の型をほんとに同一視できるような機能を入れるか
  • コンパイラーが頑張って、相互変換コードを生成するか

とかになります。

ここはほんとに最大のネックなので、更なる検討が必要なところ。

匿名型との関係

C# 3.0で匿名型という構文が導入済みなわけですが、コンパイラーによる型生成という点でタプルはよく似ています。導入の動機(LINQ 用 ⇔ 多値戻り値用)や実装方法(クラス ⇔ 構造体)が違うので完全に統一するのは無理でしょうが、親和性を高めることはできます。そのために、匿名型の方にも手を入れることが考えられます。

  • { string Name, int Age } みたいな書き方で型を明示的に書けるようにする(今は var (型推論)で受けるしかない)
    • 戻り値の型やフィールドにも使えるようにする
    • タプル同様、アセンブリをまたいだ利用についての検討が必要
  • 分解(deconstruction) できるようにする

付加的な機能充実

タプル型を多値戻り値のために使うことを考えると、いくつか利便性のための機能が考えられます。

タプルのメンバーをメソッド本体内で使う

多値戻り値を、return (x, y); みたいに返すんじゃなくて、out 引数であるかのような書き方で返す構文(書き方が近いだけで実際には、ちゃんとタプルが作られる)が考えられます。

out 引数同様、return ステートメント不要。その代り、やはり同様に、メソッドを抜けるまでに必ず値の代入が必要。

引数のスプラッティング

複数の引数に対して、1つのタプルで値を渡せるようにする(コンパイラーがタプルを分解して、各引数に渡す)ことをスプラッティング(splatting)というみたい。元々は3D CG方面の用語で、複数のテクスチャを1枚に合成する手法から来ているっぽい。おそらくは元の用途が、地面や壁にはねた飛沫(splat。要するにスプラッター)の表現だったからこの名前。

C# でも、タプル型を導入するのであればスプラッティングも欲しくなるでしょう。

Tally の戻り値は (int sum, int count) というタプル型。一方で、Average は引数が2つのメソッド。2つのメソッドをつなぐためにはタプルの分解が必要になります。この例のように、多値戻り値をそのまま別のメソッドの多値引数にしたいことは当然あるわけで、多値戻り値のためにタプルを導入するのであれば欲しい機能です。

この逆のパターン(unsplatting)もあります。普通、タプル型の引数を1つ受け取るようなメソッドは作らないでしょうが、ジェネリック型引数にタプル型を指定した場合には起こり得る話です。この場合に、複数の引数を渡して、1つのタプルに構築してからメソッドを呼び出してもらいたいです。

Written by ufcpp

2015年2月11日 @ 03:37

カテゴリー: C#

Tagged with

コメント / トラックバック6件

Subscribe to comments with RSS.

  1. […] その(8)で書いた通り、C#7 に向けて、タプル型も提案されたわけですが。タプル型が入ることによって、レコード型やパターン マッチングについても再検討が必要という話。 […]

  2. メソッドの戻り値として空のタプルが許されると面白いカモ。

    Hiroaki SHIBUKI (@hidori)

    2015年2月11日 at 16:20

  3. あっ、それ、僕も思いました。void をどうにかしろよって話もありますし。
    () ReturnsVoid() => (); とか Action x = () => (); とか。

    ufcpp

    2015年2月11日 at 16:36

  4. ですよねw

    Hiroaki SHIBUKI (@hidori)

    2015年2月14日 at 16:12

  5. […] C# 7に向けて(8): Tuples […]

  6. データの受け渡しだけのためにクラスを定義するのやだなー、でもTupleは使いたくない、みたいな人に朗報ですね!

    秋田剣

    2016年7月29日 at 11:53


コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。