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

http://ufcpp.net/

immutable

with 8 comments

変数の immutability に関する議論、今に始まった話でもないんでしょっちゅう見かけるには見かけるんですけど、まとめ的な話はあんまりきれいにまとまってるところ見かけないなぁとか思ったり。

というので、まだそんなにきれいに整理できてるわけでもないけど、ちょっと書いてみる。

値の不変性

値の不変性にもいくつか種類があって、

  1. constant: コンパイル時定数に名前を付けておきたい
    • 扱いが完全にリテラルと一緒になるやつ
      • C# の const
      • C++ の場合、#define の代替としての const
    • ようは、パフォーマンス的な話で、コンパイル時に解決できる値は全部コンパイル時にやっちゃいたいって話
    • ぶっちゃけ、リテラルが定義できるようなものだけ const 付けれればいいのかなぁと
      • C# の場合、整数型と string と enum だけが const になれる
      • C++ は逆に、constexpr & ユーザー定義リテラル認める方向で進化してるっぽいですけど
  2. immutable: 初期化時以外は値の変更不可
    • 不用意なタイミングで値が変わって困るくらいなら、そもそも初期化時以外で値の書き換えできないようにしたい
      • 特に、並列処理やってると、よそのスレッドで値書き換えられたりすると意味わからない結果を招く
    • ローカル変数の場合はほとんどの変数が immutable で事足りる
      • 実際、mutable であって欲しいのはループで集計用とかカウンターに使う変数だけ。それ以外で変数を書き換えることってめったにない
  3. one-phase construction: データは一気に初期化したい
    • 「x と y は同時には 0 になっちゃいけない」みたいな、複数のメンバーに渡ったデータ検証を考えた時、x と y を別個に set されると一時的とはいえ不整合が起きる
    • コンストラクターでしか値の初期化しないならそういう心配しなくていいよねということ(その意味では 2. の immutable の派生)
    • まあ、コンストラクターに限定する必要はないけど、とにかく、プロパティ値の set は全プロパティ一気にやりたい(なので実は別に immutable と関係ない)

不変なものの作り方

1. の constant の方は古くからある概念なんで割と素直に理解できる話。ただ、C++ の場合は 2. の意味でも const キーワードを使っちゃうんで話が複雑に・・・。その点、まあ、キーワード増えて気持ち悪いのは気持ち悪いけど、const と readonly を分けた C# は正しいと思う。

C# での 2. の immutable の保証、値型の場合には readonly つければ済むことですが、参照型の場合には参照の上書きができないだけで参照先のデータまでは readonly にならないんで、少し工夫が必要というか、クラス作成側で immutable になるようにクラスを作らないとダメ。例えば、

public class Data
{
    public Data(int x, int y) { X = x; Y = y; }
    public int X { get; private set; }
    public int Y { get; private set; }
}

みたいなクラスを作れば、コンストラクター以外で値を変更できない immutable なクラスになる。

C++ みたいに「普段 mutable な型が const キーワード付けた時だけ immutable になる」っていうのは無理。最も、C# でそれが必要になる場面っていうとあんまり思いつかないですが。C++ の場合はパフォーマンスの問題から普段 mutable な型の const 参照を作る機能が必要だったけど、C# の場合にはそもそも class が全部参照なんで。

ちなみに、C# 3.0 で導入された匿名型はこういうタイプの immutable になってる。

2種類の immutable

immutable には2種類考え方があって、

  1. 内部的に immutable
    • private メンバー含め、全部がほんとに初期化時以外書き換わらない
  2. 見かけ上 immutable
    • public なメンバーが初期化時以外で書き換わっていないように見える
    • 例えば内部的に計算結果のキャッシュを持つとか、private メンバーが書き換わってる可能性は否定しない
    • いわゆる遅延評価ってのをやろうとするとこっちにならざるを得ない

前者の内部的な immutable はコンパイラーによるチェックで保証もできるけど、後者の見かけ上の immutable はプログラマーの裁量とテストに頼るしかなくて大変。

C++ の const は元来 1. の意味。ただ、メンバー変数に mutable キーワードを付けることで、無理やり 2. の意味の immutable を実現可能。その場合、結局のところほんとに immutable になってるかどうかはプログラマーの裁量に任せられる・・・のであんまり意味があるようにも思えないという欠点あり。

不変なものの使いどころ

C# とかでの class の使い方も種類があって、

  1. 普通に OOP 的な意味のクラス、操作の対象
    • GUI の UI 要素とか、ファイルシステムのオブジェクトとか、WCF の Service とか
    • 本質的に mutable なもの(immutable にしても役に立たないもの)も多くて、あんまり immutablity を考える意味なさげ
  2. データを表すもの
    • O/R マッパー的に言うところの Entity、WCF 的に言うところの DataContract
    • こっちは、データの不整合防止とか並列処理すること見こすと、極力 immutable の方がよさげ
      • LINQ で使うような型は特に

なので結局、C# に immutable を保証する機能を入れるとするなら、

  • Entity 相当のものの immutable を保証する機能を足す
    • 無難なのは、「不変なものの作り方」のところで書いた getter だけが public なプロパティを持ったクラスを生成するような構文糖衣かなぁ
      • メソッドも getter アクセスしかしない
      • C++ の const みたいに普段 mutable な型を immutable に変える機能までは不要なんじゃないかと
      • C# の言語上での保証はできても、IL 上での保証をどうやるか難しそう
    • 見かけ上の immutable は考えない方がよさげ
      • そもそも並列化を見こすということなら、見かけ上の immutable はない方がいいし
      • それにそこを考え出すと、コンパイラーによる静的なチェックだけでは限界があって CodeContract みたいな仕組みとの連携も必要で複雑になるし
  • スレッドをまたいで渡していいのはその immutable なオブジェクトだけに限るような仕組みを何か足す
    • Parallel LINQ は 4.0 ですでに入っちゃったんで、これから言語的な保証を足すのはもう難しいかもしれないけども
      • プラクティスとして「Parallel LINQ で使う場合には IEnumerable<T> の T は immutable にしましょうね」というくらいしか
    • Axum みたいなアクター型のタスク並列機能を追加するなら、アクター間の通信には immutable な型しか認めないとか

ってところなのかなぁとか思ったりしています。

Written by ufcpp

2010年5月5日 @ 17:16

カテゴリー: 未分類

8件のフィードバック

Subscribe to comments with RSS.

  1. >前者の内部的な immutable はコンパイラーによるチェックで保証もできるけど、後者の見かけ上の immutable はプログラマーの裁量とテストに頼るしかなくて大変。>C++ の const は元来 1. の意味。複合型にユーザ定義のコンストラクタを許したときから「C++ は 1. は諦めている」ように思います.またコンパイラ検証も放棄されています.加えて,C++ の const 参照は不変性をなんら示さないものに成り下がっています.個人的には,「C++ の const」は所詮「C++ の const」に過ぎず,immutable の名に値しない別の何か,と考えるようにしています.

    NyaRuRu

    2010年5月6日 at 12:29

  2. あら、そんな当初からあきらめ入ってるんですね・・・const 参照の問題はどの辺りのせいでしょう?自分で思い当たるのは、元が const でないなら別スレッドで書き換わる、mutable のせい、const_cast のせいって辺りですが。まあ、C++ の const の一番の目的は、「コピーしたくないから参照で受けるけど、関数内では書き換えない(つもり)」という意図の表明ですかね。

    信之

    2010年5月7日 at 01:54

  3. > const 参照の問題はどの辺りのせいでしょう?> 自分で思い当たるのは、元が const でないなら別スレッドで書き換わる、mutable のせい、const_cast のせいって辺りですが。まさにその元が const でない問題です.別にスレッドを持ち出さなくてもこの問題は簡単に起きえます.int x = 0;const int &ref_x = x;std::cout << "ref_x is " << ref_x << std::end;x = 1;std::cout << "ref_x is " << ref_x << std::end;これはコードリーディングの時に大きな問題になります.例えば,以下の関数 foo で,関数 bar の「作用」の結果 arg が変化するような bar は簡単に書けるのですが,arg が関数内で immutable だと仮定したようなコードを書く人が世の中には居るわけです.そういうバグは,unit test やコードレビューでちゃんと取り除かれねばなりません.void foo(const T &arg) { std::cout << "arg was " << arg << std::end; bar(); std::cout << "arg is " << arg << std::end;}

    NyaRuRu

    2010年5月7日 at 11:08

  4. なるほど。元がmutableなものの参照にいくらconstをつけてimmutableなつもりになるというのは思った以上に危険そうですねぇ・・・

    信之

    2010年5月7日 at 15:34

  5. >元がmutableなものの参照にいくらconstをつけてimmutableなつもりになるというのは思った以上に危険そうですねぇ・・・そしてさらに,ユーザ定義コンストラクタ中では this は常に mutable です.C++ では,この参照がコンストラクタから外部に流出すると,const X x; // このコンストラクタ中で non-const な this が流出していたとしたら? この x は immutable と仮定できるか?foo(x);という問題すら起きえます.immutable なオブジェクトを構築するときの中間状態が,外部からどうやっても観測できないよう隠蔽されていることが保証されている場合,その御利益は大きいと思っています.C# の Anonymous Type がまさにそれで,コンストラクタがコンパイラによって生成されるおかげで,外部から参照できるようになったときには必ず初期化が完了していると仮定できます.

    NyaRuRu

    2010年5月8日 at 03:37

  6. 「普段 mutable な型が const キーワード付けた時だけ immutable になる」はList<T>.AsReadOnlyが一応近い存在だと思います。

    egtra

    2010年5月8日 at 03:59

  7. >「普段 mutable な型が const キーワード付けた時だけ immutable になる」はList<T>.AsReadOnlyが一応近い存在だと思います。多分そういう話じゃなくて,例えば Version という型があると自動的に const Version という修飾された型も使えるようになる,という話かと.まあ個人的な印象としてそれは「C++ が主張するところの const」であって,immutable という語から期待される動作とは別の何か,だと思ってますが.あと,List<T>.AsReadOnly() もそれだけでは immutable というほどのものではないように思います.var xs = new List<int> { 1, 2, 3 };var rxs = xs.AsReadOnly();var original = rxs[0];xs[0] = 2;Debug.Assert(original == rxs[0]);という感じで,値は簡単に変わりえます.不変性を期待したアルゴリズム (並列計算等) に使うときに,ReadOnlyCollection<T> だからといって安心してテストやAssertを省略できるかというと微妙です.

    NyaRuRu

    2010年5月8日 at 05:38

  8. > という感じで,値は簡単に変わりえますはい、そういう意味でC++ const参照と同じようだと言いたかったのです。すみません、上手く書いていなくて。

    egtra

    2010年5月11日 at 15:58


コメントを残す