Reed は、.NET の例外が OCaml の例外と異なる動作をする理由について既に説明しました。一般に、.NET の例外は例外的な状況にのみ適しており、その目的のために設計されています。OCaml はより軽量なモデルを持っているため、一部の制御フロー パターンの実装にも使用されます。
具体的な例を挙げると、OCaml では例外を使用してループの中断を実装できます。たとえばtest
、数値が必要な数値であるかどうかをテストする関数があるとします。以下は、1 から 100 までの数値を繰り返し処理し、最初に一致した数値を返します。
// Simple exception used to return the result
exception Returned of int
try
// Iterate over numbers and throw if we find matching number
for n in 0 .. 100 do
printfn "Testing: %d" n
if test n then raise (Returned n)
-1 // Return -1 if not found
with Returned r -> r // Return the result here
これを例外なく実装するには、2 つのオプションがあります。同じ動作を持つ再帰関数を作成できます-呼び出す場合(そして、C# でループ内をfind 0
使用するのと本質的に同じ IL コードにコンパイルされます):return n
for
let rec find n =
printfn "Testing: %d" n
if n > 100 then -1 // Return -1 if not found
elif test n then n // Return the first found result
else find (n + 1) // Continue iterating
再帰関数を使用したエンコードは少し長くなる可能性がありますが、F# ライブラリが提供する標準関数を使用することもできます。これは、多くの場合、制御フローに OCaml 例外を使用するコードを書き直すための最良の方法です。この場合、次のように記述できます。
// Find the first value matching the 'test' predicate
let res = seq { 0 .. 100 } |> Seq.tryFind test
// This returns an option type which is 'None' if the value
// was not found and 'Some(x)' if the value was found.
// You can use pattern matching to return '-1' in the default case:
match res with
| None -> -1
| Some n -> n
オプションの種類に慣れていない場合は、入門資料を参照してください。F# wikibook には優れたチュートリアルがあり、MSDN ドキュメントにも役立つ例があります。
モジュールから適切な関数を使用するとSeq
、多くの場合、コードが大幅に短くなるため、望ましいです。再帰を直接使用するよりもわずかに効率が悪いかもしれませんが、ほとんどの場合、それについて心配する必要はありません。
編集:実際のパフォーマンスに興味がありました。入力がリストではなく遅延生成されたシーケンスである場合、使用するバージョンSeq.tryFind
はより効率的です(リスト割り当てのコストのため)。これらの変更と25 番目の要素を返す関数を使用すると、私のマシンでコードを 100000 回実行するのに必要な時間は次のようになります。seq { 1 .. 100 }
[ 1 .. 100 ]
test
exceptions 2.400sec
recursion 0.013sec
Seq.tryFind 0.240sec
Seq
これは非常に些細なサンプルなので、再帰を使用して記述された同等のコードよりも、一般に 10 倍遅く実行されることはないと思います。速度低下はおそらく、追加のデータ構造 (シーケンスを表すオブジェクト、クロージャーなど) の割り当てと、追加の間接化 (単純な数値演算とジャンプではなく、多数の仮想メソッド呼び出しが必要になるコード) が原因であると考えられます。ただし、例外はさらにコストがかかり、コードを短くしたり、読みやすくしたりしません...