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

http://ufcpp.net/

Archive for 4月 23rd, 2015

ピックアップ Rolsyn 2015/4/23: Nullability

leave a comment »

null許容性の検証に関するC# デザイン ミーティングの議事録3週(issue的には2ページ)分。

C# Design Notes for Apr 1 and 8, 2015 #2119

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

低コストでnull許容性関連の実験をする手段として、Roslynアナライザーを書いてみたという話。このアナライザーの発展のさせ方や、それがC#の言語設計にどう影響していくかなどを検討。

フロー ベース

null検証は、フロー解析ベース、つまり、変数などを使う場所の手前で代入があったかとか、if での非nullチェックがあったかとかを調べる方法でやろうとしている。

ローカル変数やメソッドの引数についてはそんなに障壁はなくて、問題になりそうなのはせいぜいラムダ式でキャプチャする場合(匿名クラスのフィールドに昇格して、メソッドの外での書き換えの可能性がある)くらい。

一方で、フィールド、プロパティ、配列の中身などのnull許容性を追おうとすると難しい。ローカル変数とメソッド引数を追えるだけでも大きな価値になると思うけども、少なくとも、「dotted chain」(a.b.c() みたいなの)だけでも追えると便利だろう。

属性 vs 構文

今回作ったアナライザーは属性を使ってnull許容かどうかを指定しているけども、C#の構文レベルで対応するのと比べて、以下の様な問題がある。

  • 書くのが面倒
  • ローカル変数に属性は付けれない
  • 型引数や配列の要素に属性は付けれない

今は実験的にやっているのでしょうがないけども、この先C#の構文レベルで対応することになれば、この問題を解消するやり方はわかっている。今も、dynamicで似たようなことをやっている。

アナライザー vs 組み込みルール

null検証をアナライザーで(コンパイラー自身じゃなくて、プラグイン的に)やるのには利点も欠点もある。

利点は

  • 言語機能にしてしまうと、明確な仕様と合理的な説明が必要。アナライザーであればもっと経験則的な手段が取れる
  • 言語機能には後方互換性が必要。アナライザーなら今必要なコードに対してだけ診断をかけれる
  • アナライザーなら徐々に発展させていける
  • アナライザーなら個々人の要件に応じてルールのオンオフができる。言語機能だと万人の要求に合わないといけない

一方、欠点として

  • 言語機能のほうが高効率に実装しやすい
  • 言語機能のほうが何が良くて何が悪いか、ルールを標準化しやすい
  • アナライザーだと拾えない文脈でも、言語に組み込むなら拾える
  • 構文(syntax)的に T? とか T! とかを足すのに、意味論(semantics)的な診断だけアナライザーでやるのは気持ち悪いといえば気持ち悪い
  • 「warning wave」(#1580)を導入するなら、後方互換性を気にする必要性は下がる

単純なnull検証だけ言語機能に組み込んで、追加でアナライザーを使った経験則的な検証を行うというのももちろんあり得る。この場合、言語機能の方でwarningになっている検証結果を、アナライザーの方ではwarningを消したい場合も出てくるけども、今、そういう機能は足りていない。

ちょっと使ってみた感じ

(原文に幾つかコード例あり)

既存コードは大部分、属性をつけるだけでそのまま動きそうだし、便利そう。

結論として

よい実験だった。null許容性の検証をフロー ベースやることに強い関心が持てる。

C# Design Notes for Apr 15, 2015 #2133

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

こちらは、null検証とジェネリックの検討。この2つを合わせて使う場合、いろいろ課題あり。

制約なしのジェネリック

どんな型でも ToString メソッドは使えるわけだけども、これを default(T) に対して呼ぶと、null許容型ならエラーに、非null型なら問題なく使えたりする。ただ、T! (非null型)に対してdefault(T)を認めないようにできるかというと難しい。

例えば、List とか Dictionary は内部的に配列でデータを持っていて、一旦は中身は default(T) な状態になっている。List<string!> などでは、入力も出力も非nullにできるけども、内部ではnullがある。こういう挙動は認めたい。

注釈の上書き

ジェネリック型引数 T に対して、T が参照型だとわかっているなら、null許容性の上書きを許す実装もできる。T に与える型が string でも string! でも string? でも、T? と書いたら元のnull許容性を上書きして使いたい。

これはいくつかのシナリオで有益だけども、多くのジェネリック利用場面では、型引数が参照型かどうかわからない。

FirstOrDefault

FirstOrDefault みたいに default(T) を返すものの場合、「参照型の時だけ nullable」ということがあり得る。これを [NullableIfReference] 属性を導入することでうまく扱うことはできる。「なんとかなる」という意味ではこの方法でいいんだけども、複雑性を増やしてまでこれをやりたいかというと疑問。

TryGet

int.TryParse みたいなメソッドでは、戻り値が true の時だけout引数で受け取る値の非null保証がほしい場合もある。これも、[NullableIfFalse] みたいな属性の導入でなんとかできるけど、価値が得られるかは疑問。

Written by ufcpp

2015年4月23日 at 22:18

カテゴリー: 未分類