Archive for the ‘C#’ Category
Roslyn コンパイラー仕様
まだ 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#では、未初期化のローカル変数の参照を認めていません。これ自体は言語仕様にも書かれていることです。問題は、「何をもって未初期化・初期化済みを判定するか」という部分。判定ロジックに関する詳細が言語仕様に足りていないので、「明確でなく、発散の余地のある挙動」になっているみたいです。
例えば、このページで紹介されている例は以下のようなもの。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
int x; | |
if (false && x == 3) // 恒偽定数式の後ろの、ショートサーキット演算に続く式は絶対に到達できない | |
{ | |
x = x + 1; | |
} | |
} | |
} |
C#コンパイラーは、到達不能な場所では「未初期化変数を使った」判定をしないみたいです。つまり、 if (false) { ここ } とか、if (true) {} else { ここ } とかでは、別に未初期化変数を使ってもコンパイルエラーにならない。
で、コンパイラー挙動がぶれているのは、さらに、ショートサーキット演算(&& とか || とか、左辺の時点で結果が確定したら右辺を実装しないもの)と組み合わさった場合。false && (ここ) とか true || (ここ) とかは絶対に通らないわけで、前述の if の例に習うなら、別に「未初期化変数を使った」判定をさぼってもいいはず。
ところが…
C# 3.0~5.0 まではエラーになるんですって、これ。C# 2.0の頃はエラーになってなかった。そして、Roslynでもエラーにならなくした。というか、明確なルールを加えた。とのこと。
言われるまで気づかなかった… まあ、こんなコード書かないし、誰も気づかないから言語仕様に漏れるんですが。
なんなんですかね、これ。パターン マッチングとか宣言式とかを実装する過程でこれに類するコードが生成されちゃって困ったとかがあったんですかねぇ。それとも、誰かC# 2.0の頃にこの類のコードを書いてる人がいて、C# 3.0の時に「破壊的変更」を不具合報告入れたとか?
Roslyn issues 2015/2/16
前回でひと段落したC# 7提案関連の話、一応リンクまとめておきますか。
- C# Design Notes / デザイン プロセスについて
- C# Design Notes / テーマ
- C# Design Notes 1/28版
- C# 7に向けて(1): 変数やフィールドの書き換え
- C# 7に向けて(2): 性能と信頼性
- C# 7に向けて(3): その他
- C# 7に向けて(4): C# 6に漏れた分
- C# 7に向けて(5): Record types
- C# 7に向けて(6): Pattern matching
- C# 7に向けて(7): まだ具体案の見えていないもの
- C# 7に向けて(8): Tuples
- C# Design Notes 2/4版
- C# 7に向けて(9): Method Contracts
そして今後は週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 ページ。
C# 7に向けて(9): Method Contracts
今 issue ページができてて大きめのものはこれが最後のはず。Method Contracts。メソッドに対する「契約」。
結構長かった… 結局、全9回に。今後は、「週刊ブログ」程度でよくなるはず。きっと。
契約プログラミング
そもそも「contracts」とは何か的な話は、昔書いたスライド貼って済まそう。
(補足: このスライドでは非null制約を例に挙げて説明していますが、非null制約は契約でやるよりも、「非null参照型」を作る方がいいと思います。C# 7向けの提案の中にはこの「非null参照型」も含まれています。)
一応、.NET 4の頃から、ライブラリとツールでのサポートはありました。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public int Insert(T item, int index) | |
{ | |
Contract.Requires(index >= 0 && index <= Count); | |
Contract.Ensures(Contract.Result<int>() >= 0 && Contract.Result<int>() < Count); | |
return InsertCore(item, index); | |
} |
また、研究的な位置づけのプロジェクトでは、C# に契約プログラミング機能を足したプログラミング言語(Spec#)もありました。
まあでも、C# が言語機能レベルでサポートしないと流行らないよね、きっと。という状態。
問題
.NET 4で契約がらみのクラス(Contract)が追加されてからも、実際のところ、このContractクラスを使ってくれたライブラリは少ないです。
どういう問題があるかというと例えば、
- Contract. を付けないと行けなくていちいちめんどくさい
- 契約の宣言場所がメソッド本体の中(本来、外に見せない内部実装にあたる場所)にある
- 契約は外から見えるべき
- ポスト プロセスが必要
- Contract.Requires/Ensures を書いている場所では実際には何も起こらず、コンパイル結果のILをさらに書き替えてチェックを行う仕組みになってる
- ドキュメントへの反映(「このメソッドはこういう契約を持っている」みたいなのを doc コメントに反映)もポスト プロセス
- ソースコードの静的解析が結構重い
- Visual Studio の上位エディション(確か、当初、Ultimate 限定?Premium でも行けたかも)が必要
- ポスト プロセス、静的解析ともに
提案: C# 言語機能での契約
文法的には Spec# の頃とあんまり変わらないものになりそう。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public int Insert(T item, int index) | |
requires index >= 0 && index <= Count | |
ensures return >= 0 && return < Count | |
{ | |
return InsertCore(item, index); | |
} |
requires (事前条件: 引数が満たすべき条件) や ensures (事後条件: 戻り値が見たすべき条件)の違反は fail-fast (エラー ログを残して即座にプログラム終了)にするようです。契約は、理想を言えばコンパイラーによって違反は全部コンパイル エラーにしてしまう方がいいものです(単に、技術的困難から実行時に残るだけ)。テストによって契約違反は取りきるべき(ちゃんと、呼び出し元がすべて責任をもって引数チェックしてからメソッドを呼ぶ)もの。なので、規定動作は例外すら出さず、強制終了。
一応、requires/ensures の後ろに else throw を付けて、この挙動を変える(例外を投げるだけにする)機能は検討されているようです。
あと、呼び出し元側で責任をもってチェックする必要があるということは、requires/ensures 句内では、メソッドよりもアクセス制限の強いメンバーの参照は禁止されるべきです。internal なメソッドの契約で private フィールドを参照したりはできません。
C# Design Notes 2/4版
新しい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
今回出たアイディアは以下のようなもの。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Point の実際の値を持つ構造体。 | |
// 現状の C# だと自分で実際にこういう型定義を書く必要があるけども、タプルにできるのでは。 | |
struct PointValue | |
{ | |
public int X; | |
public int Y; | |
} | |
// 値セマンティクスを持つ immutable クラス | |
// レコード型の実装はこうあるべきだろうという内容。 | |
// ここでいう値セマンティクス(value semantics)は、全メンバー比較で Equals、全メンバーのハッシュ値から GetHashCode 計算できるもののこと。 | |
class Point | |
{ | |
// 唯一の構造体メンバーに readonly を付けているので immutable。 | |
// タプルが入るのであれば、public readonly (int X, int Y) Value; でいいはず。 | |
// Point p に対して (var x, var y) = p; で、(var x, var y) = p.Value; の意味に解釈するのもよさそう。 | |
public readonly PointValue Value; | |
// 外から mutable な型で値をもらう。いわゆる「ビルダー パターン」。 | |
// PointValue をタプルにするのなら、スプラッティングが効いて、new Point(x, y) とも書けるはず。 | |
// 簡易構文として、new Point { X = 1, Y = 2 } で、new Point(new PointValue { X = 1, Y = 2 }) の意味に解釈するのもよさそう。 | |
public Point(PointValue value) { Value = value; } | |
// Value に処理を丸投げ | |
public int X => Value.X; | |
public int Y => Value.Y; | |
public override int GetHashCode() => Value.GetHashCode(); | |
public override bool Equals(object obj) => (obj as Point)?.Value.Equals(Value) ?? false; | |
// 値書き換え(immutable なので別のインスタンスを作る)用の "wither" (GetX, SetX みたいなのを getter/setter というように、WithX をこう言う) | |
// p.Value { X = 1 } みたいに、new するとき以外でもオブジェクト初期化子を使えると、wither 書くのが楽になるはず。 | |
// Point WithX(int x) => new Point(Value { X = x }); | |
// あと、var q = p { X = 1 }; みたいなので new Point(Value { X = 1 }) の意味に解釈するのもよさそう。 | |
// これなら、自前の wither 定義要らない。 | |
public Point WithX(int x) { var v = Value; v.X = x; return new Point(v); } | |
public Point WithY(int y) { var v = Value; v.Y = y; return new Point(v); } | |
} |
要点は
- 値セマンティクスを持つクラスは、文字通り Value 1つだけを持つ
- Value 用の構造体を作って、それを受け取るコンストラクターを作ることで、いわゆる「ビルダー パターン」になる
- Value はタプル型を使って定義するとよさそう
- オブジェクト初期化子の拡張で、「wither」も簡単に書けそう
これなら確かにタプルとレコードの親和性がよく、使う側のわかりやすさ的にもコンパイラーの実装負担的にも都合がよさそう。
まあ、これはこれで、元々のレコード型の提案にあった、部分的なメンバーの置き替え(以下のようなもの)の実装が難しくなりそう。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Person(int Id, string Name) | |
{ | |
public string Name { get; set; } // Name だけ mutable | |
} |
レコードもタプルも、まだ提案の初期段階でこれから変わっていくでしょうし今後どうなるかはわかりませんが、これはこれで面白い検討内容でした。
C# 7に向けて(8): Tuples
C# 7 Proposals な内容、あと Method Contracts だけになったと一息ついたところで、新しい Issue ページが立つわけですが。
タプル、つまり、型名を持たない構造化データ/データの一時的なグループ化の話。
これは結構気合の入った提案文章になっているので、全訳気味に紹介。
System.Tuple のおかげでタプルという言葉だけではあまり期待感が持てないかもしれませんが、C# 開発者的にインパクトのある言葉で表現すると「アセンブリをまたげる匿名型」「メソッド引数・戻り値やフィールドに使える匿名型」です。
背景
「複数の値の一時的にグループ化」が必要な最もよくある例は、メソッドの引数リストでしょう。C# をはじめ、多くのプログラミング言語がこれをサポートしています。要するに、複数の引数を持つメソッドには、F(1, “abc”) というように複数の値を渡すわけですが、ここで、(1, “abc”) というような値のグループ化が行われていると考えられます。
この次に値のグループ化を必要とするのは、引数の逆、つまり戻り値です。この需要、「多値戻り値」を楽に書けるプログラミング言語はとたんに少なくなります。最近では多値戻り値が使えるプログラミング言語が増えてきていますが、C# もC# 7でその流れを追従することになりそうです。
まず、C# の現状がどうかというと…
out 引数
戻り値を複数返したい場合の1つ目の選択肢は out 引数です。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public void Tally(IEnumerable<int> values, out int sum, out int count) { … } | |
int s, c; | |
Tally(myValues, out s, out c); | |
Console.WriteLine($"Sum: {s}, count: {c}"); |
利点
- 複数の戻り値それぞれがちゃんと名前を持つ
- しっかりした命名は下手なコメントや仕様書よりもよっぽど重要
- どの値が何を意味するかわかりやすくなる
欠点
- 非同期メソッドの戻り値に使えない
- 使う側で、事前に変数の準備が必要
- 無駄に行数が増える(複数のステートメントが必要)
- var が使えない
System.Tuple
「無名の一時的な値のグループ化」を意味する型として、 .NET 4 で Tuple クラスが追加されました。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public Tuple<int, int> Tally(IEnumerable<int> values) { … } | |
var t = Tally(myValues); | |
Console.WriteLine($"Sum: {t.Item1}, count: {t.Item2}"); |
利点
- 非同期メソッドの戻り値にもできる(Task<Tuple<T1, T2, …>>)
欠点
- 名前が消える(Item1, Item2, … という意味のない名前になる)
- ヒープ確保が必要(Tuple は参照型なので)
名前が消えるという結構嫌な欠点の割に、Tuple クラスは使いやすいかというと微妙… Tuple.Create(1, “abc”) とかが煩雑。
データ移送(transport)用の型を作る
現状、out 引数や System.Tuple の欠点を回避しようとすると、普通に明示的な型定義が必要になります。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public struct TallyResult { public int Sum; public int Count; } | |
public TallyResult Tally(IEnumerable<int> values) { … } | |
var t = Tally(myValues); | |
Console.WriteLine($"Sum: {t.Sum}, count: {t.Count}"); |
各値の名前も消えず、非同期メソッドでも使えて、struct にしたのでヒープ確保も起きません。
型名にちゃんとした意味があれば、疑うべくもなく型を作るのがよい方法だと思います。「一時的」じゃなく、その後もずっと使うようなものであれば、もっと型を作る意義が出てくるでしょう。
ですが、この例の TallyResult (Tally メソッドの結果なんて、見りゃわかるだろ)なんて名前に特に意味もないわけで、あまりよい方法とは言えません。もちろん、SumAndCount (メンバーが Sum と Count なんだから見りゃわかる)なんて名前も論外。値も、だいたいすぐに sum と count に分解して使うようなものです。特定用途下に置いていい名前が付くことがあるかもしれませんが(我々のプログラム中では、A という概念が sum と count を必要とするので、これを A と命名しよう。とか)、それはそれで、別の用途に使えなくなります。
タプル構文
そこで、(System.Tuple ではなく、C# の言語機能として)タプル型を導入しようという提案が出ています。
書き方は、以下のような理由から、引数リストと似た構文になるようです。
- 引数リストは、背景での説明通り、概念的にはタプルに近い
- タプルを導入したい一番の理由が多値戻り値
- 引数と戻り値は、入ってくるものと出ていくもの、表裏一体
- 「新構文の追加」というより、「引数と戻り値の対称性の改善」と考えればよくて、現状の構文になじみやすい
タプル型
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public (int sum, int count) Tally(IEnumerable<int> values) { … } | |
var t = Tally(myValues); | |
Console.WriteLine($"Sum: {t.sum}, count: {t.count}"); |
C# では、いわゆる「名前付きタプル」だけ導入するつもりみたいです。一般にタプルというと、(int, string) みたいな、メンバー名のない、ほんと単なる値のグループ化を指したりするものですが、こういう「メンバー名のないタプル」はなし。
この例の (int sum, int count) で、 struct Anonymous { public int sum; public int count; } みたいな構造体が(型名なしで)作られる感じ。
タプルは、非同期メソッドの戻り値にも使えます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public async Task<(int sum, int count)> TallyAsync(IEnumerable<int> values) { … } | |
var t = await TallyAsync(myValues); | |
Console.WriteLine($"Sum: {t.sum}, count: {t.count}"); |
タプル リテラル
リテラル用の構文を追加しなくても、一応、var t = new (int sum, int count) { sum = 0, count = 0 } とかいう書き方はできるでしょうが、あまりにも煩雑なので、ちゃんとリテラル構文も。
タプル型自体が「引数リストの定義」(仮引数リスト)みたいな書き方なんだから、タプル リテラルは「引数リストへの値の渡し方」(実引数リスト)みたいであるべきです。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(int sum, int count) result = (1, 2); // 左辺の型を見て | |
var result = (sum: 1, count: 2); // 名前付き引数の要領で、右辺から型推論 |
タプルの分解
値を(無名のまま)構造化(construction)するのがタプルなら、その逆、脱構造化(deconstruction)操作も欲しくなります。タプルはほんとに一時的なグループ化に使うことが多く、値を受け取る側は即座に、値を別々の変数に入れて使いたかったりします。var t = (sum: 1, count: 2) でタプル化できるんなら、(var sum, var count) = t;でタプルの分解をしたい。ちゃんとそのための構文も用意する予定です。ただし、分解は、変数宣言時のみ認めるのか、既存の変数に対する代入でも認めるのかはまだ未定だそうです。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var t = (sum: 1, count: 2); // タプル構築 | |
(var sum, var count) = t; // タプル分解 | |
(sum, 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)が考えられます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Base {} | |
class Derived : Base {} | |
(Base x, Base y) a = (x: new Derived(), y: new Derived()); // covariance | |
(double sum, long count) b = (sum: 1, count: 2); // implicit conversion from int | |
(int s, int c) c = (sum: 1, count: 2); // renaming |
(issue ページでは「やる」とは名言されておらず、「できない理由はないよね」「やれるけど」「たぶんできるとよさそう」的書き方)
アセンブリをまたいでの単一化
こんな便利そうなタプルですが、これまで入らなかった主な理由というか、最大の課題は、アセンブリをどうやってまたぐか。
.NET では、異なるアセンブリの中に、完全に同名・同名前空間・同機能のクラスを定義できますが、これはアセンブリごとにそれぞれ別の型と認識されます。赤の他人が同名の別クラスを定義してしまったことによって、クラス利用者のコードが壊れるようなことがあってはいけないので当然といえば当然の処置。しかしこれが、アセンブリをまたいだ匿名クラス・匿名構造体の利用を難しくしています。タプルは実装上、匿名構造体になるわけで、この問題をクリアしないと、アセンブリを超えた利用が面倒です。
あり得る解決策は例えば、
- .NET ランタイムに手を入れて、別アセンブリ同名の型をほんとに同一視できるような機能を入れるか
- 例えば、ASP.NET 5 では、Assembly Neutral なインターフェイスというのを導入するようです
- コンパイラーが頑張って、相互変換コードを生成するか
とかになります。
ここはほんとに最大のネックなので、更なる検討が必要なところ。
匿名型との関係
C# 3.0で匿名型という構文が導入済みなわけですが、コンパイラーによる型生成という点でタプルはよく似ています。導入の動機(LINQ 用 ⇔ 多値戻り値用)や実装方法(クラス ⇔ 構造体)が違うので完全に統一するのは無理でしょうが、親和性を高めることはできます。そのために、匿名型の方にも手を入れることが考えられます。
- { string Name, int Age } みたいな書き方で型を明示的に書けるようにする(今は var (型推論)で受けるしかない)
- 戻り値の型やフィールドにも使えるようにする
- タプル同様、アセンブリをまたいだ利用についての検討が必要
- 分解(deconstruction) できるようにする
付加的な機能充実
タプル型を多値戻り値のために使うことを考えると、いくつか利便性のための機能が考えられます。
タプルのメンバーをメソッド本体内で使う
多値戻り値を、return (x, y); みたいに返すんじゃなくて、out 引数であるかのような書き方で返す構文(書き方が近いだけで実際には、ちゃんとタプルが作られる)が考えられます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public (int sum, int count) Tally(IEnumerable<int> values) | |
{ | |
sum = 0; count = 0; | |
foreach (var value in values) { sum += value; count++; } | |
} |
out 引数同様、return ステートメント不要。その代り、やはり同様に、メソッドを抜けるまでに必ず値の代入が必要。
引数のスプラッティング
複数の引数に対して、1つのタプルで値を渡せるようにする(コンパイラーがタプルを分解して、各引数に渡す)ことをスプラッティング(splatting)というみたい。元々は3D CG方面の用語で、複数のテクスチャを1枚に合成する手法から来ているっぽい。おそらくは元の用途が、地面や壁にはねた飛沫(splat。要するにスプラッター)の表現だったからこの名前。
C# でも、タプル型を導入するのであればスプラッティングも欲しくなるでしょう。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(int sum, int count) Tally(IEnumerable<int> values) | |
{ | |
sum = 0; count = 0; | |
foreach (var value in values) { sum += value; count++; } | |
} | |
double Average(int sum, int count) => count == 0 ? 0 : sum / count; | |
var avg = Average(Tally(new[] { 1, 2, 3, 4, 5})); // タプル (int sum, int count) を分解して、Averabe(sum, count) に渡す |
Tally の戻り値は (int sum, int count) というタプル型。一方で、Average は引数が2つのメソッド。2つのメソッドをつなぐためにはタプルの分解が必要になります。この例のように、多値戻り値をそのまま別のメソッドの多値引数にしたいことは当然あるわけで、多値戻り値のためにタプルを導入するのであれば欲しい機能です。
この逆のパターン(unsplatting)もあります。普通、タプル型の引数を1つ受け取るようなメソッドは作らないでしょうが、ジェネリック型引数にタプル型を指定した場合には起こり得る話です。この場合に、複数の引数を渡して、1つのタプルに構築してからメソッドを呼び出してもらいたいです。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var list = List<(string name, int age)>(); | |
list.Add("John Doe", 66); // "unsplatting" to a tuple |
C# 7に向けて(6): Pattern matching
先日の続き、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 がきっちり対応する書き方になるのが結構きれい。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// composition | |
var expr = Add(Mult(Const(2), X()), Const(1)); | |
// decomposition | |
switch (expr) | |
{ | |
case Add(Mult(var a, var x), var b): …; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
abstract class Expr; | |
class X() : Expr; | |
class Const(double Value) : Expr; | |
class Add(Expr Left, Expr Right) : Expr; | |
class Mult(Expr Left, Expr Right) : Expr; | |
class Neg(Expr Value) : Expr; |
is 演算子を書く以外に、switch ステートメントでもパターン マッチングできるように拡張したいようです。
パラダイムが変わるのか
僕がこういう感じのセクション タイトルを付けるときは、だいたい「えっ… 別に」という答え付きだったりするわけですが。
こういう機能が出てくると、ついつい「オブジェクト指向 VS 関数型」、「関数型の方がいい!」「オブジェクト指向から関数型へのパラダイムシフト!」的な流れになりがちですが、まあ、どっちも使うでしょう、普通に。
自分の最近書いたコードで言うと、ビュー側での表示の切り替えのコード。例えば、要所を抜き出しすと、以下のような
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// バトル中のアクション。 | |
/// </summary> | |
public abstract class BattleAction | |
{ | |
public abstract void ApplyTo(Unit u); | |
} | |
/// <summary> | |
/// 攻撃アクション。 | |
/// </summary> | |
public class Attack : BattleAction | |
{ | |
public int Power { get; set; } | |
public override void ApplyTo(Unit u) => u.Life -= Power; | |
} | |
/// <summary> | |
/// 回復アクション。 | |
/// </summary> | |
public class Heal : BattleAction | |
{ | |
public int Power { get; set; } | |
public override void ApplyTo(Unit u) => u.Life += Power; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Threading.Tasks; | |
/// <summary> | |
/// デモ用 モデルもどき。 | |
/// </summary> | |
public class SampleModel | |
{ | |
/// <summary> | |
/// コマンド実行。 | |
/// </summary> | |
public static async Task ExecuteCommandAsync() | |
{ | |
var selectedAction = await SelectCommandAsync(); | |
var target = await SelectTargetAsync(); | |
// モデル側は普通に仮想メソッドとか使って処理を掛けるのが楽 | |
selectedAction.ApplyTo(target); | |
// ほんとは ApplyTo でアクション結果を返してもらって、ビューに通知 | |
} | |
/// <summary> | |
/// コマンド選択。 | |
/// </summary> | |
private static Task<BattleAction> SelectCommandAsync() | |
{ | |
// ほんとはビューにメッセージを投げて、 | |
// コマンド入力ウィンドウでも開いて、ユーザー入力を待って、アクションを選択する | |
return Task.FromResult<BattleAction>(new Attack()); | |
} | |
/// <summary> | |
/// ターゲット選択。 | |
/// </summary> | |
private static Task<Unit> SelectTargetAsync() | |
{ | |
// ほんとはビューにメッセージを投げて、 | |
// ターゲット選択ウィンドウでも開いて、ユーザー入力を待って、ターゲットを選択する | |
return Task.FromResult(new Unit()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// ユニット。 | |
/// </summary> | |
public class Unit | |
{ | |
public int Life { get; set; } | |
// ほんとは他に攻撃力、魔力、防御力、… などなど。 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// デモ用 ビューもどき。 | |
/// </summary> | |
public class SampleView | |
{ | |
public static void ShowBattleAction(BattleAction action) | |
{ | |
// モデル側はビューの事情を知ったことではないので、処理はこっち側で分岐することに。 | |
// 型を見て分岐。 | |
var heal = action as Heal; | |
if (heal != null) ShowHeal(heal); | |
var attack = action as Attack; | |
if (attack != null) ShowAttack(attack); | |
} | |
private static void ShowHeal(Heal heal) | |
{ | |
// 回復アクションの結果表示 | |
} | |
private static void ShowAttack(Attack attack) | |
{ | |
// 攻撃アクションの結果表示 | |
} | |
} |
RPG ゲーム的なものを想像してもらって、コマンド選択で「たたかう」だの「まほう」だののコマンド選択式で何らかのアクションを起こすようなものの一部分を抜き出したものです(実際、これを大規模化した感じのコードを前に書きまして)。
いわゆるモデル側では、やっぱり、仮想メソッドを使って処理の切り替えをやる方がシンプルでいい実装だと思うんですよね。この例で言うと、Model.cs 内、ApplyTo を呼んでいる部分。
一方で、これをやるためには、オブジェクト自身がやりたい処理を全部把握している必要があります。ApplyTo (選択したアクションを、ターゲットに適用)の場合は、モデル自身がやるべきことを知っているので仮想メソッドにできる。ビュー側はそうはいきません。ビューのことはビューしか知らず、モデル側(BattleAction)内部のメソッドとしては処理を書けません。
で、結局、往々にしてビュー側コードはどうなるかというと… 型を見ての分岐。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static void ShowBattleAction(BattleAction action) | |
{ | |
var heal = action as Heal; | |
if (heal != null) ShowHeal(heal); | |
var attack = action as Attack; | |
if (attack != null) ShowAttack(attack); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static void ShowBattleAction(BattleAction action) | |
{ | |
if (action is Heal heal) ShowHeal(heal); | |
else if (action is Attack attack) ShowAttack(attack); | |
} |
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# では _ は普通に識別子に使える名前だし…
C# 7に向けて(5): Record types
現時点では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 にしたいクラスではないかと。
まあ、めんどくさい… 例えば以下の通り。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 5 以前で、きっちり作るなら | |
// プロパティ1つごとに5か所ずつ同じものを書かないと行けない | |
// しかも、プロパティ/フィールド、コンストラクターで結構場所が離れる | |
public class Point | |
{ | |
public int X { get { return _x; } } | |
private readonly int _x; | |
public int Y { get { return _y; } } | |
private readonly int _y; | |
public Point(int x, int y) | |
{ | |
_x = x; | |
_y = y; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 5 以前で、多少さぼる | |
// private set な自動実装プロパティを使用 | |
// 自動実装で作られるフィールドは readonly ではなく、private なだけでクラス内からは書き換え可能 | |
public class Point | |
{ | |
public int X { get; private set; } | |
public int Y { get; private set; } | |
public Point(int x, int y) | |
{ | |
X = x; | |
Y = y; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 5 以前で、さぼる | |
// new Point(1, 2) って書くのをあきらめて、new Point { X = 1, Y = 2 } と書く | |
// set も public になってしまっていて大変残念 | |
public class Point | |
{ | |
public int X { get; set; } | |
public int Y { get; set; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// C# 7 の秘めた可能性 | |
// これだけで readonly なフィールドを持った、get-only なプロパティが生成される | |
public class Point(int X = 0, int Y = 0); |
上から、
- 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 のときとかは)
C# 7に向けて(4): C# 6に漏れた分
元々、C# 6として(Visual Studio 2015と同じスケジュールで)入る予定だった機能のうちのいくつかは、期間的な問題でいったん仕様から外れました。そのうちいくつかは本当に単なる工数の問題で、ほぼ当時の仕様そのままに、C# 7に入りそうです。そういった類の機能に関する話も、今日、GitHub 上の issue 化されました。
- Proposal: Binary literals #215
- Proposal: Digit separators #216
- Proposal: Declaration Expressions #254
あと、InternalImplementationOnly 属性ってのが、追加で(こちらはC# 6に間に合わせて)入りそうです。
もう1個、昨日、pattern matching と record types の仕様も issue になったみたいですが、これは CodePlex 時代から少し仕様が変わっているみたい。大きな機能なので別途1記事使って何か書く予定。今日はこの話はスルー。
Binary literal
2進数リテラルを入れたいという話。現状の16進数リテラルとほぼ同じ構文で、x/X が b/B に変わるだけ。0b00010101 みたいな。
構文解析の実装コストが少々かかる程度で、特に大きな問題もないはず。知らない人が見て一瞬「b って何だ?」って思う程度で、まあ、すぐになじめそうですし。
むしろなぜこんなちょろそうな機能が「期間的な問題でいったん外す」判定を受けたのか…
Digit separators
同上。ちょろい機能その2。10進、16進、2進問わず、数字の間に _ を挟んでも OK になるという機能。33_554_432、0x1b_a0_44_fe, 0b_1001_1010_0001_0100 など、好きなところに _ を挟んでください。
2進数リテラル以上に実装コストが低い(数値解釈の文脈に入ったら _ を無視するだけ)。
議論の余地があるとしたら、科学表記リテラル(1.2e-10 みたいなやつ)で使えるの?というあたり。
追記: あと、issue についているコメントを見ていると、区切り文字を何にするかももめそう。というか「(できるかはわからないけど)スペース区切りを認めてくれ」という話が。さすがに、スペースで区切られた複数トークンで1リテラルとかキモイというか、他の構文との兼ね合いで問題出そうというか。
Declaration Expressions
変数宣言を式のど真ん中に書けるようにしようっていうやつ。用途や構文が少し被り気味の pattern matching が入ることになったので、そっちとの兼ね合いでいったん外すことになったもの(pattern matching がC# 7での実装送りになったので、それと同じスケジュールに)。
構文自体は割ともう固まっているんですが、問題は、これで作った変数のスコープをどこまでにするか。if-else の条件式のところで書いたものは else 句内で使えるべきかとか、そういう辺りが中々難しい問題で、結構難航しそう。
InternalImplementationOnly 属性
インターフェイスには、後から実装を足すと、利用者側に破壊的変更を引き起こすという欠点があります。一度 public にしたら、(少なくともユーザーが付いてしまったら)変更できません。既存機能を壊すような変更はおろか、単純な追加さえできません。
これは、第三者がインターフェイスを実装すると起きる問題なわけですが。一方で、たまに、「赤の他人に実装してほしいわけじゃなくて、自分が書いた実装クラスの詳細を隠したいから一段階インターフェイスを挟んでいるだけ。他の人には実装させたくない」というようなインターフェイスの使い方をします。この場合、
- 実装を隠す意図なので、実装が変わった時にインターフェイスの側も修正したい
- 他人に実装させる気はないので、これを禁止できれば、インターフェイスも変更し放題
という要求が生じます。
この「禁止」のために、新たに InternalImplementationOnly 属性を定義して、これが付いているインターフェイスは同一アセンブリ内でしか実装できないように、C#/VB コンパイラーに手を入れたいとのこと。
これは、C# 6/Visual Studio 2015 に間に合わせたいそうです。というのも、Roslyn 中でまさにこういうインターフェイスの使い方(ISynbol インターフェイス)をしていて、こいつの第三者による実装が生まれてしまうと困るので、Roslyn 正式リリース(= Visual Studio 2015 正式リリース)までの対応が必要。
C# 7に向けて(3): その他
1/21, 28のDesign Notes付近の、C# チーム公式の「C# 7 提案」紹介3個目。
(以前の2つにもリンク)
さて、あと大きめの話題というと、method contracts と pattern matching が残ってはいるんですが。2回連続で結構長めのブログ書いたので、今日は先に「その他」的なものを3つ。
- Proposal: [DoesNotReturn] #127
- Proposal: Static Linking #162
- Proposal: Add completeness checking to pattern matching draft specification #188
[DoesNotReturn]
1つ目は割かしほんとにその他枠。
C#の場合、戻り値を持つ(voidでない)メソッドは、ちゃんとどの分岐でも値を返しているか(もしくは、例外を投げて終わっているか)をチェックしています(返していないところがあったらコンパイル エラー)。
しかし、現状だと判定できないものが1つあって、元から例外を投げる意図で別のメソッドを呼び出す場合。100%常に例外を投げるメソッドを呼んだ場合、その後ろのステートメントは絶対に実行されないはずですが、これを判定するすべがない。
なので、こういう「戻ってこない」「絶対例外投げて終わる」メソッドを示す用の属性を追加したいという提案。
まあ、もう少し例外ハンドリングの仕組み自体に改善の余地はないの?とは思うものの、「低コストでたまに便利」的な意味ではよさそう。
Static Linking
2つ目。テーマ的には Componentization の一部。
今、.NETのライブラリは基本的には動的リンクしかなくて、大半のケースではそれで特に問題もないんですが。特定の用途に置いては明らかに静的リンクが有利な場合があります。
提案内で例に上がっているのは、小さな機能が山ほど詰まったヘルパー ライブラリです。具体例でいうと、LINQみたいなやつでしょうね、小さい静的メソッドが大量に詰まってるやつ。1つのプログラムでその全機能を使うことはほぼなくて、使わない部分をクライアントにダウンロードさせるのはもったいない。使うところだけをプログラム中に埋め込みたい。
正しく使えば性能面で有利なのはわかるんですが… ちゃんと有利なものに使われるかとか、むしろ使っちゃいけない場面で使われないか(セキュリティ ホールとかの申告な問題が見つかった時にどうするの?とか)考えて使われるかなぁ…という心配が。
Completeness
3つ目。pattern matching 関連なので、ほんとは pattern matching の話の後の方がいい気もするものの。pattern matching の提案とは別に、後から追加された提案です。
pattern matching、要するに、型を見て分岐する際に、「意図したすべての型を網羅している」というのをチェックしたいことがあります。↓こういうの。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Message { } | |
class A : Message { } | |
class B : Message { } | |
class C : Message { } | |
class PatternMatching | |
{ | |
public static void Match(Message m) | |
{ | |
if (m is A) { /* … */ return; } | |
if (m is B) { /* … */ return; } | |
if (m is C) { /* … */ return; } | |
// もう所望の全パターン網羅済み! | |
// ここに来るはずがない! | |
// というか、ここに来ない保証がほしい。 | |
} | |
} |
こういうのをチェックできる状態を、完全(complete)と呼びたいみたいです。
数学用語使って「完備」(代数学におけるcompleteの訳語)とか言った方がいいかも。(一定のルール下で)これ以上集合的に拡大できない状態。これ以上元が増えない保証。
型階層が完備であることを保証するためには、要するに、第三者が子クラスを足せないことを保証してやる必要があります。現状のC#でもこれを保証する方法があって、以下のようなパターンで実装すればOK。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
abstract class Message | |
{ | |
// コンストラクターを private に | |
private Message() { } | |
// inner クラスであれば private なコンストラクターにアクセスできる | |
// なので、以下の A, B, C はコンパイルできる | |
public sealed class A : Message { } | |
public sealed class B : Message { } | |
public sealed class C : Message { } | |
} | |
// private なコンストラクターのせいで外からは継承できない | |
public class D : Message { } // なので、ここでコンパイル エラー | |
// しかも、A, B, C ともに sealed なので、そいつらを継承することもできない |
このパターンをもって「完備である」とするか、もしくは、こういうコードを生成するようなシンタックス シュガー(今のところ abstract sealed っていう修飾子で提案)を追加したいとのこと。
C# 7に向けて(2): 性能と信頼性
C# 7の提案のうちいくつかを見た瞬間、思ったのは「C++ 11みたい」でした。
まあ、背景には、年々C#の適用範囲が広がっていること、そして、オープンソース化に伴って今後はさらに広がるであろうことがあります。
ゲーム開発の標準言語と言えばC#
その広がった先の1つはゲーム開発でしょう。「ゲーム開発の標準言語と言えばC#」なんていう煽り(わりかしほんとに「煽り」ではあると思う。「話題にならないよりは炎上する方がマシ」くらいの覚悟の上の)もあります。まあ、割かし昔から、ゲーム開発者にもC#好きな人はいて、ゲーム会社の社内ツールなんかはC#で書かれることが結構あったみたいです。しかし、「ゲーム自体を」となると、これは割かし最近の話。「XNAがあったし(震え声)」と小声で主張もしたいところですが、実際のところ、割かし皮肉なことに、「C#でゲーム開発」が注目を浴びるようになったのはUnityのおかげで、iOS/Android向けのゲーム開発ができるようになったからでしょう。今では、Mono for Unreal EngineとかParadoxとかいうものもあります。
実際のところ「C#でゲーム開発」がどうなのかというと、「ジャンルを選べば割と十分」と言ったところ。そこそこよい性能を、かなりいい開発効率で実現できると思います。
とはいえ、「そこそこよい」ではダメなジャンルももちろんあります。アクション性の高いゲームだと結構きついんじゃないかと思います。C#単体でももっと性能を求めることや、C++などの性能的に実績の高い言語との相互運用をさらに深めていくなど、改善の余地はまだまだあります。
性能を求めて
ゲーム以外でも、性能を求めたい場面は多々あるはずです。マイクロソフト自身のプロジェクトで言っても、WPFやRoslynはかなりシビアな性能を求められたはずです。そんな中で「C#でもこう書ければいいのに」「C++ならこう書けるのに」といった類の要望がいろいろ出てきたのでしょう。もちろん、C#の持ち味である安全性や生産性の高さを崩すわけにもいきませんが、その前提下においても、できそうなことはあります。
ということで、以下のような提案が出ています。
- Proposal: Lambda Capture Lists #117
- Proposal: Ref Returns and Locals #118
- Proposal: Slicing #120
- Proposal: Fixed-size buffers enhancements #126
- Proposal: move #160
- Proposal: Destructible Types #161
なんかC++っぽい。
Lambda Capture Lists
前回、readonlyローカル変数のところでちょっと話題にも出しましたが、C#のラムダ式で、ラムダ式の外の変数をキャプチャした場合、参照渡しになって(内部的にはクラスとそのフィールド生成とかいう結構大ごとなコード生成が起きます)、いろんなところから値が書き替えられる可能性があります(書き換えを防ぐという観点でいうと、前回のreadonlyローカル変数も有効な解決策ですが)。
この挙動がほしいことは確かにあります。便利です。ですが、「規定動作がこれでいいか」とか、「挙動を変えれなくていいか」とか考えると、結構嫌な感じ。
C++の場合、C++ 11でラムダ式を導入したわけですが、その文法では、ラムダ式の外の変数をどうキャプチャするかを指定できます。これにならって、C#のラムダ式でも、参照渡しなのか、値渡しなのか、指定できるようにしたいというのが議題。
「どう書くか」は悩ましいですが… C++のラムダ式の書き方はちょっと煩雑すぎて。
Ref Returns and Locals
現状のC#では、「参照渡し」ができるのは ref 引数と out 引数だけです。
しかし、.NETランタイムのレベルでは、もっといろいろな場面で参照を使えます。ローカル変数を参照変数にすることもできますし、戻り値に参照を返すこともできます。これをC#でも認めれば、性能を改善できる場面がありますという議題。
まあ、悩ましいのは、「参照型の参照渡し」とかが、なじみのない人にとってなかなかにわかりにくい概念なところですかねぇ。同じ問題は抱えます。
Slicing
配列の一部分だけを参照したいことは結構あります。string.Substring とか、LINQで .Skip(start).Take(length) とか書く方法もありますが、配列のコピーが発生するので、性能的にはいまいちです。.NETランタイムや、C#の言語レベルでのサポートがほしいという議題。
今のところ、Slice<T>型と、array[left : right]的な文法が提案されています。例えば、クイックソートなんかを書くなら以下のような感じ。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public void QuickSort(int[] map, int left, int right) | |
{ | |
do | |
{ | |
// 中略 | |
if (j – left <= right – i) | |
{ | |
if (left < j) QuickSort(map, left, j); | |
left = i; | |
} | |
else | |
{ | |
if (i < right) QuickSort(map, i, right); | |
right = j; | |
} | |
} while (left < right); | |
} | |
//↑こういう、(left, right) を渡す代わりに、↓見たいな「Slice」が使えるとうれしい | |
public void QuickSort(Slice<int> slice) | |
{ | |
do | |
{ | |
// 中略 | |
if (j – left <= right – i) | |
{ | |
if (left < j) QuickSort(slice[left: j]); | |
left = i; | |
} | |
else | |
{ | |
if (i < right) QuickSort(slice[i: right]); | |
right = j; | |
} | |
} while (left < right); | |
} |
Fixed-size buffers enhancements
今、固定長バッファーは unsafe 下でだけ認められているけども、safe な文脈でもやりようあるし、それで性能改善とか、unsafe を必要としないネイティブ相互運用ができるんじゃないかという議題。
今出ている提案では、「Ref Returnsが入れば、それを使って実現できるし実装コスト低いんじゃないか」というようなノリ。
move
「ある変数やフィールドから、別の変数に所有権を移す」というような処理があります。別の変数に代入して中身を引き渡した後、自分自身はnullを代入して所有を放棄するというようなもの。これを、「move」(要は、所有権の移動)と言って、新しい構文として導入できないかという議題。
要はこれも、C++ 11のmove。「コピー コンストラクターの呼び出しが不要になる」という目に見えたメリットがあるC++と違って、C#に導入する利点は少し弱いんですが…
とはいえこれは、所有者が単一であることを保証するのに便利です。同時に複数の変数から同じ値を参照する必要がないことは結構あるんですが、この場合、リソース管理がかなり簡単になります(C++で言うところの、unique_ptr 的なアプローチ)。具体的には、次節のDestructible Typesとの組み合わせが便利ということになります。
Destructible Types
C#は、デストラクターという名前の構文はあるもの、その実態は「ファイナライザー」で、要するに、「ガベージ コレクションの回収対象になった時に呼ばれる」ものです。が、ファイナライザーは性能への影響もでかいし、呼ばれるタイミングが非決定的なのがたびたび問題になります。結局、IDisposableを実装して、自前でDisposeを呼ぶタイミングを管理することになって、かなり煩雑な処理が必要です。
そこで、C++的なマナーのデストラクター、要するに、変数がスコープを抜けた時に呼ばれる、決定論的な破棄処理を掛けたいという議題。
destructive 修飾をクラスや構造体につけることで、「~型名」をファイナライザー的動作ではなく、C++のデストラクター的動作にするという提案が出ています。
このやり方だと、shared_ptr などを使わない場合の C++ と同じく、値の所有権を正しく管理する必要があります。所有している変数やフィールドが単一でないと、破棄のタイミングが確定しません(複数のうちの1つがスコープを抜けた時に破棄されてしまうとなると、残りは破棄済みの不正な値を参照することになる)。そこで、所有権を移さないなら readonly ref、移したいなら前節の move などが必要になります。
まとめ
「C++だわ」という感想はありますが。
まあ、C# 7を待つまでもなく、.NET Nativeや、SIMD 演算対応など、これまでC#や.NETが苦手としていた性能面へのアプローチは始まっています。C#の用途が広がってきている今、C++ 11 (14, 17)に習うべきところは多々あるでしょう。
とはいえ、この辺りはおそらく、C# 7への組み込みが最も難航しそうなところな気がします。要は、費用対効果はどうか、pros/consのバランスが合うかという辺りが結構難しそう。
実際、1/28のミーティング議事録の時点ですでに、Safe fixed-size buffersは「優先度最低で」認定を受けていたりします。