Reed already explained why .NET exceptions behave differently than OCaml exceptions. In general, .NET exceptions are suitable only for exceptional situations and are designed for that purpose. OCaml has more lightweight model and so they are used to also implement some control flow patterns.
To give a concrete example, in OCaml you could use exception to implement breaking of a loop. For example, say you have a function test
that tests whether a number is a number we want. The following iterates over numbers from 1 to 100 and returns first matching number:
// 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
To implement this without exceptions, you have two options. You could write a recursive function that has the same behaviour - if you call find 0
(and it is compiled to essentially the same IL code as using return n
inside for
loop in C#):
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
The encoding using recursive functions can be a bit lengthly, but you can also use standard functions provided by the F# library. This is often the best way to rewrite code that would use OCaml exceptions for control flow. In this case, you can write:
// 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
If you're not familiar with option types, then take a look at some introductory material. F# wikibook has a good tutorial and MSDN documentation has useful examples too.
Using an appropriate function from the Seq
module often makes the code a lot shorter, so it is preferrable. It may be slightly less efficient than using recursion directly, but in most of the cases, you don't need to worry about that.
EDIT: I was curious about the actual performance. The version using Seq.tryFind
is more efficient if the input is lazily generated sequence seq { 1 .. 100 }
instead of a list [ 1 .. 100 ]
(because of the costs of list allocation). With these changes, and test
function that returns 25th element, the time needed to run the code 100000 times on my machine is:
exceptions 2.400sec
recursion 0.013sec
Seq.tryFind 0.240sec
This is extremely trivial sample, so I think the solution using Seq
will not generally run 10 times slower than equivalent code written using recursion. The slowdown is probably due to the allocation of additional data structures (object representing the sequence, closures, ...) and also due to additional indirection (the code needs numerous virtual method calls, instead of just plain numeric operations and jumps). However, exceptions are even more costly and don't make the code shorter or more readable in any way...