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

http://ufcpp.net/

Archive for 2月 11th, 2015

C# 7に向けて(9): Method Contracts

with one comment

今 issue ページができてて大きめのものはこれが最後のはず。Method Contracts。メソッドに対する「契約」。

結構長かった… 結局、全9回に。今後は、「週刊ブログ」程度でよくなるはず。きっと。

契約プログラミング

そもそも「contracts」とは何か的な話は、昔書いたスライド貼って済まそう。

(補足: このスライドでは非null制約を例に挙げて説明していますが、非null制約は契約でやるよりも、「非null参照型」を作る方がいいと思います。C# 7向けの提案の中にはこの「非null参照型」も含まれています。)

一応、.NET 4の頃から、ライブラリとツールでのサポートはありました。

また、研究的な位置づけのプロジェクトでは、C# に契約プログラミング機能を足したプログラミング言語(Spec#)もありました。

まあでも、C# が言語機能レベルでサポートしないと流行らないよね、きっと。という状態。

問題

.NET 4で契約がらみのクラス(Contract)が追加されてからも、実際のところ、このContractクラスを使ってくれたライブラリは少ないです。

どういう問題があるかというと例えば、

  • Contract. を付けないと行けなくていちいちめんどくさい
  • 契約の宣言場所がメソッド本体の中(本来、外に見せない内部実装にあたる場所)にある
    • 契約は外から見えるべき
  • ポスト プロセスが必要
    • Contract.Requires/Ensures を書いている場所では実際には何も起こらず、コンパイル結果のILをさらに書き替えてチェックを行う仕組みになってる
    • ドキュメントへの反映(「このメソッドはこういう契約を持っている」みたいなのを doc コメントに反映)もポスト プロセス
  • ソースコードの静的解析が結構重い
  • Visual Studio の上位エディション(確か、当初、Ultimate 限定?Premium でも行けたかも)が必要
    • ポスト プロセス、静的解析ともに

提案: C# 言語機能での契約

文法的には Spec# の頃とあんまり変わらないものになりそう。

requires (事前条件: 引数が満たすべき条件) や ensures (事後条件: 戻り値が見たすべき条件)の違反は fail-fast (エラー ログを残して即座にプログラム終了)にするようです。契約は、理想を言えばコンパイラーによって違反は全部コンパイル エラーにしてしまう方がいいものです(単に、技術的困難から実行時に残るだけ)。テストによって契約違反は取りきるべき(ちゃんと、呼び出し元がすべて責任をもって引数チェックしてからメソッドを呼ぶ)もの。なので、規定動作は例外すら出さず、強制終了。

一応、requires/ensures の後ろに else throw を付けて、この挙動を変える(例外を投げるだけにする)機能は検討されているようです。

あと、呼び出し元側で責任をもってチェックする必要があるということは、requires/ensures 句内では、メソッドよりもアクセス制限の強いメンバーの参照は禁止されるべきです。internal なメソッドの契約で private フィールドを参照したりはできません。

広告

Written by ufcpp

2015年2月11日 at 19:30

カテゴリー: C#

Tagged with

C# Design Notes 2/4版

with one comment

新しいC#デザイン ミーティング議事録。

今回の議題は3つ。大きいのは3つ目の Classes with values です。タプルと関連してのレコード再検討。

あっ、あと、おまけで。Roslyn の RC1 を NuGet 公開したそうです。

Internal implementation only

前にC# 7シリーズその(6)で触れた、 ImternalImplementationOnly 属性に関して、もう少し詳細な検討。

問題点に関しても前の issue ページより少し詳細に。そして、今でもできること、コンパイラーに機能追加すればできること、.NET ランタイムにも手を入れるならみたいな話と、それでどのくらい「穴」が改善するかという話がかかれています。

問題はインターフェイスに後からメソッドを足しにくい(一般にもやっては行けないこととされているし、BCL チームのポリシー的には完全にNG)ことなので、こういう属性を足す以外にも、mixins/traits って呼ばれるような言語機能の追加でも問題は回避できるかもしれません。

実際に C# 6 や 7 で、どこまでやるかや、開発ポリシーをどうしていくかなども含めて、BCL チームとの対話も必要そう。

Tuples, resords, deconstruction

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

特に、タプルとレコードについて、これらの機能は一元化する方がいいかもしれないし、少なくとも統合的にデザインしていく必要があるでしょう。タプルは「レコードの特別な場合」であるべきか、もしかしたらレコードは単なる「型名のあるタプル」であるべきかなどということも考えられます。

レコードの導入動機は、値セマンティクスや immutability を持つクラスを楽に作れるようにするものなので、この辺りを次節で検討します。

Classes with values

今回出たアイディアは以下のようなもの。

要点は

  • 値セマンティクスを持つクラスは、文字通り Value 1つだけを持つ
  • Value 用の構造体を作って、それを受け取るコンストラクターを作ることで、いわゆる「ビルダー パターン」になる
  • Value はタプル型を使って定義するとよさそう
  • オブジェクト初期化子の拡張で、「wither」も簡単に書けそう

これなら確かにタプルとレコードの親和性がよく、使う側のわかりやすさ的にもコンパイラーの実装負担的にも都合がよさそう。

まあ、これはこれで、元々のレコード型の提案にあった、部分的なメンバーの置き替え(以下のようなもの)の実装が難しくなりそう。

レコードもタプルも、まだ提案の初期段階でこれから変わっていくでしょうし今後どうなるかはわかりませんが、これはこれで面白い検討内容でした。

Written by ufcpp

2015年2月11日 at 15:35

カテゴリー: C#

Tagged with

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日 at 03:37

カテゴリー: C#

Tagged with