【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

値型と副作用

F#で値型の操作に失敗してハマりました。
※以下のプロンプトはid:n7shi:20090612のセミコロン省略版fsiを使用しています。

> let r=Rect()
val r : Rect
> r.Union(Rect(1.0,1.0,2.0,2.0))
val it : unit = ()
> r.ToString()
val it : string = "0,0,0,0"

Union()はインスタンスを書き換える副作用のあるメソッドですが、エラーも何もなく無視されています。
プロパティを書き換えようとしたところ、エラーが出て事情が分かりました。

> r.X<-1.0
stdin(13,1): error FS0191: A value must be local and mutable in order to mutate
the contents of a value type, e.g. 'let mutable x = ...'.

Rectは値型(構造体)のため、intなどと同じ定数扱いになって書き換えられないということです。わざわざmutableにするように教えてくれて親切なエラーです。

> let mutable r=Rect()
val mutable r : Rect
> r.Union(Rect(1.0,1.0,2.0,2.0))
val it : unit = ()
> r.ToString()
val it : string = "0,0,3,3"

無事にUnion()を適用することができました。

Nullable

F#でUnion()の動作を確認した後、早速C#で使ってみました。たまたまNullable型を使ったのですが、似たような結果となりました。

class Program
{
    public static void Main()
    {
        Rect? r = new Rect();
        r.Value.Union(new Rect(1, 1, 2, 2));
        Console.WriteLine("{0}", r);
        // 結果: 0,0,0,0
    }
}

これはr.Valueで値がコピーされてUnion()が適用されるためです。F#との比較ではr.Value.X = 1;という代入がエラーになるところも一緒です。

C#では構造体をconstにできませんが、F#ではどうなっているのかildasmで調べてみました。get_r()という関数が定義されてrへの参照が振り替えられていました。やはり値コピーでUnion()が無視されていました。

値型は副作用のあるメソッドを使用するときに注意が必要だということを再認識しました。