Reading Beginning F# – Robert Pickering I focused on the following paragraph:
Programmers coming from an OCaml background should be careful when
using exceptions in F#. Because of the architecture of the CLR,
throwing an exception is pretty expensive—quite a bit more expensive
than in OCaml. If you throw a lot of exceptions, profile your code
carefully to decide whether the performance costs are worth it. If the
costs are too high, revise the code appropriately.
Why, because of CLR, throwing an exception is more expensive if F# than in OCaml? And what is the best way to revise the code appropriately in this case?
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
testthat tests whether a number is a number we want. The following iterates over numbers from 1 to 100 and returns first matching number: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 usingreturn ninsideforloop in C#):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:
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
Seqmodule 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.tryFindis more efficient if the input is lazily generated sequenceseq { 1 .. 100 }instead of a list[ 1 .. 100 ](because of the costs of list allocation). With these changes, andtestfunction that returns 25th element, the time needed to run the code 100000 times on my machine is:This is extremely trivial sample, so I think the solution using
Seqwill 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…