F#の例外でメンバ定義

http://d.hatena.ne.jp/einblicker/20110815/1313416546
こういった豆知識みたいなのは面白いですね。知らないものがあったので質問させてもらいました。

ちょっと便乗して、もしかしたら他の人は意外と知らないかもしれないネタを書いてみます。


F#では、例外を次のように定義できます。

exception MyException of string

で、こう使えます。

raise <| MyException "hoge"

ちゃんとtry...with...する場合は困らないのですが、だれもキャッチせずスレッドが終わってしまうと困ることがあります。それは、MyExceptionをインスタンス化したときに渡した文字列(この場合"hoge")がスタックトレース上に表示されないのです。

じゃあ、どうするかというと、こうします。


exception MyException2 of string with
override
this.Message =
match this :> exn with
| MyException2(text) ->
text
| _ ->
Unchecked.defaultof<_>
なぜか、MSDNhttp://msdn.microsoft.com/ja-jp/library/dd233190.aspx)には書いていないのですが、実はwithを使ってexception型にメンバを持たせられるのです。そして、exception型はSystem.Exceptionを継承するのでオーバーライドもできます。なので、Messageプロパティをオーバーライドしてインスタンス化したときに渡した文字列を含めて返せばスタックトレース上に表示されるというわけです。


が、しかし、単にそれだけならば、F#のexception型を使うのではなく、.NETのSystem.Exceptionを使うのが楽だったりします。System.Exceptionのコンストラクタに渡した文字列がMessageプロパティの値になるからです。

open System

type MyException3(text) =
inherit Exception(text)


どう使い分けるかですが、

  • 例外に値を「いくつか」詰めてスローし、F#のコードでキャッチされることを想定するならF#のexception
  • ArgumentNullExceptionみたいに事前条件が守られていないことを示すような例外ならSystem.Exceptionを継承

という感じがいいと思います。

F#のexceptionはtry...with...したときにパターンマッチングで例外に詰めた値を取り出しやすいことが特長なので、そういった使い方をしないのであればSystem.Exceptionを継承でいいと思います。異常終了したときにメッセージをスタックトレースに表示しやすいですし。