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

http://ufcpp.net/

Archive for the ‘C#’ Category

Roslyn コンパイラー仕様

with one comment

まだ pull-req 通ってないんですが、Roslyn リポジトリ上にこんなものが。

https://github.com/dotnet/roslyn/pull/536

コンパイラー仕様のドキュメント化、始めました。

“コンパイラー仕様”

コンパイラーを作っていると、標準化されている言語仕様では不十分な仕様ってものがどうしても出てきます。C# コンパイラーにもいくつかそういうものがあるんですが、「オープン化したんだからそういう隠れ仕様もドキュメント化しないとダメだよね」という感じで、その手始めに、コンパイラーチームの内部 OneNote で書かれていた仕様を .md 化してリポジトリに追加しようとしているみたい。pull-req が通った暁には、/docs/compilers フォルダー以下にこういう “コンパイラー仕様” が並びます。

具体的にどういうものをドキュメント化しようとしているかというと、

  • コマンドライン スイッチとその意味
  • 以前のバージョンのコンパイラーからの破壊的変更
  • 標準仕様に反するコンパイラーの内部挙動
  • 言語仕様に記述されていないコンパイラー機能
    • COM 関連や、マイクロソフト特化の動作
    • コンパイラーの挙動に影響を与える “well-known” な属性(Obsolete とか Conditional のこと)
    • “ruleset” (FxCop 的なものの、どの警告を出してどの警告は抑止するみたいなルール)のファイル構文(syntax)と意味論(semantics)
  • C#とVB間の相互運用のための機能
    • 名前付きインデクサー(インデックス付きプロパティ)のC#からの利用
  • 言語仕様で明確でなく、発散の余地のあるコンパイラー挙動
  • 制限次項(例えば識別子長など)
  • バージョンごとの変更履歴

など。

そんなのよく気づいたな

今回公開されたコンパイラー仕様のうちの1つ、「Definite Assignment」がネタとして面白かったんで紹介。要するに、「変数には確実に値を与えておけ」仕様に関する話。

C#では、未初期化のローカル変数の参照を認めていません。これ自体は言語仕様にも書かれていることです。問題は、「何をもって未初期化・初期化済みを判定するか」という部分。判定ロジックに関する詳細が言語仕様に足りていないので、「明確でなく、発散の余地のある挙動」になっているみたいです。

例えば、このページで紹介されている例は以下のようなもの。

C#コンパイラーは、到達不能な場所では「未初期化変数を使った」判定をしないみたいです。つまり、 if (false) { ここ } とか、if (true) {} else { ここ } とかでは、別に未初期化変数を使ってもコンパイルエラーにならない。

で、コンパイラー挙動がぶれているのは、さらに、ショートサーキット演算(&& とか || とか、左辺の時点で結果が確定したら右辺を実装しないもの)と組み合わさった場合。false && (ここ) とか true || (ここ) とかは絶対に通らないわけで、前述の if の例に習うなら、別に「未初期化変数を使った」判定をさぼってもいいはず。

ところが…

DefiniteAssignmentUnreachableCode

C# 3.0~5.0 まではエラーになるんですって、これ。C# 2.0の頃はエラーになってなかった。そして、Roslynでもエラーにならなくした。というか、明確なルールを加えた。とのこと。

言われるまで気づかなかった… まあ、こんなコード書かないし、誰も気づかないから言語仕様に漏れるんですが。

なんなんですかね、これ。パターン マッチングとか宣言式とかを実装する過程でこれに類するコードが生成されちゃって困ったとかがあったんですかねぇ。それとも、誰かC# 2.0の頃にこの類のコードを書いてる人がいて、C# 3.0の時に「破壊的変更」を不具合報告入れたとか?

Written by ufcpp

2015年2月17日 at 23:39

カテゴリー: C#

Tagged with

Roslyn issues 2015/2/16

leave a comment »

前回でひと段落したC# 7提案関連の話、一応リンクまとめておきますか。

そして今後は週1程度で個人的に興味持ったものをピックアップしていこうかなぁという感じなわけですが。

とりあえず、第1回。

Non-nullable references: a few difficulties and possible solutions

https://github.com/dotnet/roslyn/issues/227#issuecomment-73928184

非null許容な参照型に関する issue ページに、Mads (C# チームの偉い人)が結構まとまった内容のコメントを付けてた。

要点は

  • フィールドだと他の場所で書き替えられる可能性があるから、非null保証しにくい
    • 参照型 T を、非null参照型 T! の変数で受けてから使う、みたいな処理が必要
    • パターンマッチングを使えば、x is T! y みたいな書き方はできる
  • 規定値どうしよう
    • 参照型の規定値は null なんで、既定でない値の代入が必須
    • 特に配列とかout 引数の場合に心配
  • ライブラリの互換性
    • 非null参照型が入ったら、既存のライブラリをぜひともこれに対応させたいって思う人がかなり多いはずだけども、既存の参照型 T を非null参照型 T! に書き替えると破壊的変更になる
    • コードを壊さないようにするためには T から T! への暗黙的変換が必要。でも、暗黙的にしてしまうと、せっかくの非null型の魅力半減

という感じ。

Avoid unnecessary boxing with String.Concat

https://github.com/dotnet/roslyn/pull/415

C# で、”int ” + 1 みたいな書き方(文字列 + その他の何か)をすると、string.Concat 呼び出しにコンパイルされます。この例で言うと、string.Concat(“int”, 1)。

で、string.Concat の引数は params object[] です。整数とか、値型に対して使うとボックス化が発生。

で、この無駄なボックス化を避けるために、値型だった場合、先に ToString メソッドを呼んでしまおうという提案がされています。もちろん挙動変更なので慎重に検討。

とりあえず、この挙動変更で問題が出るようなプログラムはなさそうには思います。また、C# の仕様書を読み合わせてみても、問題のある(仕様違反になる)変更ではないはず。

What language proposals would benefit from CLR changes?

https://github.com/dotnet/roslyn/issues/420

C# 6までは、とりあえずコンパイラーの修正だけで実現できる機能ばっかりでした。

C# 7の提案に入ってからは、まずは要望だけ洗いだしている状態で、コンパイラーだけでやるべきか、.NETランタイムに手を入れるべきかも含めて検討中。

で、今週、「.NETランタイムにも手を入れるならこういうこともできるよ。他に何か要望ある?」ページが建ちました。

Improve memory managment in async methods

非同期メソッドやイテレーター中でローカル変数を定義すると、コンパイル結果的にはクラス生成されて、ローカル変数だったものが実はフィールドに格上げされます。これは、awaitやyield returnを超えて同じ変数を使うためには必要な処置です。

問題は、awaitを超えて変数を使わない場合。awaitを超えないということは、別にフィールドにしなくても挙動は変わりません。ところが、現状のC#コンパイラーは、awaitを超えない場合でも変数をフィールドの格上げします。理由は、デバッグ時、非同期メソッド内に break point を仕掛けて止めた場合、awaitより前の変数の状態を見たいときがあるから。つまり、デバッグのためだけに、無駄な処理を行っています(ローカル変数のフィールド化は、メモリ管理上、結構な無駄)。

この挙動変える?break point で止めた時に await より前の変数覗きたい?デバッグ ビルドの時だけフィールドに格上げすべき?みたいな内容の issue ページ。

Written by ufcpp

2015年2月16日 at 02:20

カテゴリー: C#

Tagged with

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

C# 7に向けて(6): Pattern matching

with one comment

先日の続き、C# 7の目玉になるであろう機能、pattern matching と record types

の片割れ、pattern matching の話。

昨日の record types (もう、自分が書くクラスの7割くらいは record types で書くことになりそうな勢い)と比べると出番は少ないでしょうが、なかなかに便利そうな機能です。

マッチング構文

以下のような感じで、オブジェクトの型や値のマッチングを、再帰的に行う構文が追加されます。

  • 定数マッチング
    • x is 1
    • 定数との比較
    • 下記の
  • 型マッチング
    • x is T t
    • x が特定の型 T の時にマッチ
    • その時、x を T にキャストしたものが t に入る
  • プロパティ マッチング
    • x is T { X is int , Y is Int }
    • x の型についてマッチした後、そのプロパティに大してもマッチング
  • 再帰マッチング
    • x is T(var a, 1)
    • is 演算子というものに展開される
      • record types の場合はこの is 演算子もコンパイラーが生成
      • ユーザー定義の is 演算子も書ける
    • var を書いたところは、何とでもマッチした上で、その場所に入っている値を変数 a に代入する
    • var x = new T(1, 2) みたいなので構成(composition)するのの逆で、x is T(var a, var b) みたいなので分解(decomposition)できる
  • 何とでもマッチング(ワイルドカード)
    • x is T(var a, *)
    • 何でもいいし、var 不要(値をとりだす必要がない)場合には、そこに * を書く

最後の再帰マッチングについては、composition と decomposition がきっちり対応する書き方になるのが結構きれい。

is 演算子を書く以外に、switch ステートメントでもパターン マッチングできるように拡張したいようです。

パラダイムが変わるのか

僕がこういう感じのセクション タイトルを付けるときは、だいたい「えっ… 別に」という答え付きだったりするわけですが。

こういう機能が出てくると、ついつい「オブジェクト指向 VS 関数型」、「関数型の方がいい!」「オブジェクト指向から関数型へのパラダイムシフト!」的な流れになりがちですが、まあ、どっちも使うでしょう、普通に。

自分の最近書いたコードで言うと、ビュー側での表示の切り替えのコード。例えば、要所を抜き出しすと、以下のような

RPG ゲーム的なものを想像してもらって、コマンド選択で「たたかう」だの「まほう」だののコマンド選択式で何らかのアクションを起こすようなものの一部分を抜き出したものです(実際、これを大規模化した感じのコードを前に書きまして)。

いわゆるモデル側では、やっぱり、仮想メソッドを使って処理の切り替えをやる方がシンプルでいい実装だと思うんですよね。この例で言うと、Model.cs 内、ApplyTo を呼んでいる部分。

一方で、これをやるためには、オブジェクト自身がやりたい処理を全部把握している必要があります。ApplyTo (選択したアクションを、ターゲットに適用)の場合は、モデル自身がやるべきことを知っているので仮想メソッドにできる。ビュー側はそうはいきません。ビューのことはビューしか知らず、モデル側(BattleAction)内部のメソッドとしては処理を書けません。

で、結局、往々にしてビュー側コードはどうなるかというと… 型を見ての分岐。

as してから != null チェックだらけに(AsPatternMatching.cs みたいな)。

これに対して、C# 7で導入予定の pattern matching を使えば、型による分岐が楽になります(Cs7PatternMatching.cs)。

争点

結構大きな機能追加なので、まだまだこれから仕様を固めないと行けないところとか、もめそうなところとか結構多め。

  • 匿名型どうしよう?
    • record types と匿名型でアプローチが違いすぎるんだけども
  • 配列、List、Dictionary のマッチング
  • switch でいいの?
    • switch っていう単語は今まで通りな legacy な switch であってほしい。マッチングには match キーワード導入しない?
    • switch みたいなステートメントじゃなくて、式にしてほしい
  • パターン マッチング式中での dynamic 型の扱い
  • switch での完備性のチェック
    • その3で説明した「completeness」
    • パターン マッチングがあり得るパターンを漏れなく網羅しているかどうかをチェックしたいけども、できる?
  • 再帰マッチングとプロパティ マッチングは混在させたい?
  • ワイルドカードは * なの?
    • F# とかでは _ だけども…
    • _ の方がなじみはあるものの、C# では _ は普通に識別子に使える名前だし…

Written by ufcpp

2015年2月7日 at 02:56

カテゴリー: C#

Tagged with

C# 7に向けて(5): Record types

with 3 comments

現時点ではC# 7の目玉になるであろう機能として、pattern matching と record types があります。

C# 6の機能提案の段階で出てきてはいたものの、期間的な問題で「7送り」になりました。当時と比べて、record types の書き方が少し変わっています。

長くなりそうなので、record types と pattern matching の説明は2回に分けようかと思います。今日は record types の話。

最小限の例

値の書き替えってどのくらいの頻度でやりますか?

僕の場合だと、自分が書くクラスの半分以上は書き換え不能(先日書いた内容で言うところの immutable)に作っています(参考:C# 7に向けて(1): 変数やフィールドの書き換え)。「めんどくさいから後で」とか言って mutable に作ったまま放置してるものとかも含めれば、7割くらいは immutable にしたいクラスではないかと。

まあ、めんどくさい… 例えば以下の通り。

上から、

  • Point1: readonly のおかげできっちり immutable を保証しているもの
  • Point2: 実装している自分が気を付けている限りには immutable になっているもの
  • Point3: 完全にさぼり。immutable じゃない。でも、使ってる側では別に値の書き換えしないことの方が多い

という感じ。自分は Point2 くらいの書き方をすることが一番多いです。面倒でなければ Point1 の書き方をしたいものの。あと、C# 6で導入される get-only 自動プロパティを使えば、Point2 程度の書き方で、Point1 程度のきっちりした immtable 保証ができます。

そして、Point4 が、C# 7 での導入が提案されている新機能、record types です。たったこれだけで、immutable なクラスが作られる。

マジでほしい。今すぐ欲しい。C# 6も正式版はまだだけども… きっと、C# 7 のプレビュー版が出た瞬間に入れて使うと思う(仕様変更でプレビュー版更新のたびに大変だと思うけども、たぶんやっちゃう)。

カスタマイズ

詳しくは提案ページのコードでも見てもらうとして。

一番シンプルな書き方をした時に、一番望む形になっているのは本当にありがたいです。ですが、常に immutable に作りたいわけじゃないですし、カスタマイズはしたいところ。record types では、ちゃんと、コードを足してカスタマイズしていくことができます。

  • class Point(int x: X, int y: Y)
    • パラメータ名と、生成されるプロパティ名を変えたい場合
  • class Point(int X, int Y) { public X { get; set; } }
    • X を mutable にしたい場合、明示的にプロパティを書く
  • class Point(int X, int Y) { public Length => Sqrt(X * X + Y * Y); }
    • パラメーターと無関係な追加のメンバーも足せる
    • ちゃんと、自動生成されたプロパティを参照可能
  • class Point(int X, int Y) { public Point(int x) : this(x, x) { } }
    • コンストラクターを足せる。
    • ただし、必ず primary コンストラクター(クラス定義の最初の行の引数リストのこと)を呼ばないとダメ
  • class Point(int X, int Y) : Shape;
    • 継承も利用可能
  • struct Point(int X, int Y);
    • 構造体も利用可能

ここではプロパティの自動生成の話ばかり話していいますが、これに加えて、いくつかのメンバーも自動生成されます。

  • ToString … “Point(X: 1, Y: 2)” みたいなのを出力
  • Equals … メンバーごとの比較で等値判定
  • GetHashCode … メンバーごとのハッシュ値計算結果から、自身のハッシュ値を計算
  • is 演算子 … pattern matching 用(次回説明予定)

ちなみに、もう1つ、便利記法として、record types の場合はインスタンスを作るときに new を省略できるようにしようっていう話もあります。new Point(1, 2) の代わりに、Point(1, 2) だけでコンストラクター呼び出し。このため、record types は、デリゲートとかメソッドと同列の「invokable」という扱いにしようとのこと。

争点

C# チーム自身が issue として挙げている点や、コメントでついている意見などとして以下のようなものがあります。

  • 属性の付け方どうしよう?
    • primary コンストラクターの引数1個から、フィールド、プロパティが生成されて、さらにプロパティは get メソッドが作られる
    • 合わせると、引数、フィールド、プロパティ、メソッドの4つあるわけで、それぞれに属性付けたい?指定なしだとどれについてほしい?
  • record キーワードとか付けないの?
    • CodePlex 時代にはいったん class キーワードの前に record 修飾を付ける文法で提案されてたはずだけども
    • 既存の構文とずいぶん違うし、結構な量のコードがコンパイラーによって生成されるし、わけなくていい?
    • プロパティの自動生成とかはしてほしくなくて、primary コンストラクターだけ使いたい場合もありそうでは?
  • (C# 3で導入された)匿名型が浮かない?
    • 匿名型は new { X = 1, Y = 2 } みたいな書き方で immutable なクラスが生成される
    • 今回導入される構文は new Pont(1, 2)
  • Equals, GetHashCode, ToString
    • 現状のコード生成結果は(仮)
    • 仕様化が必要
    • IEquatable は実装した方がいい?
    • == はないの?(参照型に対して == はあんまり推奨されないけども… struct のときとかは)

Written by ufcpp

2015年2月5日 at 23:56

カテゴリー: C#

Tagged with