开发者

F# exception handling constructs

开发者 https://www.devze.com 2022-12-16 09:56 出处:网络
Why doesn\'t F# naturally support a try/with/finally block? Doesn\'t it make sense to try something, deal with whatever exception it throws, at least to log the exception, and then be sure that some

Why doesn't F# naturally support a try/with/finally block?

Doesn't it make sense to try something, deal with whatever exception it throws, at least to log the exception, and then be sure that some code executes after all that?

Sure, we can do

try
    try
        ...
    with ex -> ...
finally
    ...
开发者_运维百科

But that seems too artificial, it clearly demonstrates that "F# is against try/with/finally". Why is that?


As somebody already mentioned, you would usually use try-with-finally to make sure that you properly release all resources in case of an exception. I think in most of the cases you can do this more easily using the use keyword:

let input = 
  try
    use stream = new FileStream("C:\temp\test.txt");
    use rdr = new StreamReader(stream);
    Some(rdr.ReadToEnd())
  with :? IOException as e -> 
    logError(e)
    None

I think this is mostly the reason why you don't need try-with-finally as often as you would in other languages. But of course, there are some situations where you may need it (but you could of course avoid that by creating instance of IDisposable using object expressions (which is syntactically very easy). But I think this is so rare that the F# team doesn't really need to worry about this.


Orthogonality? You can simply nest a try-with inside a try-finally, as you show. (This is what happens at the IL level anyway, I think.)

That said, try-with-finally is something that we may consider in a future version of the language.

Personally I have only run into wanting it a couple times, but when you do need it, it is a little bothersome to have to do the extra nesting/indent. In general I find that I rarely write exception handling code, and it's usually just one or the other (e.g. a finally to restore an invariant or other transactional semantics, or a 'catch' near the top of an app to log an exception or show the user diagnostics).

But I don't think there's a lot to 'read in to' regarding the language design here.


Without going into great detail because the great details are in

[Expert .NET 2.0 IL Assembler] 01 by Serge Lidin

See: Ch. 14, Managed Exception Handling

"The finally and fault handlers cannot peacefully coexist with other handlers, so if a guarded block has a finally or fault handler, it cannot have anything else. To combine a finally or fault handler with other handlers, you need to nest the guarded and handler bocks within other guarded blocks, ..., so that each finally or fault handler has its own personal guarded block."

pg. 300

A try/catch uses a fault handler and a try/finally uses a finally handler.

See: ILGenerator.BeginFaultBlock Method

If you emit a fault handler in an exception block that also contains a catch handler or a finally handler, the resulting code is unverifiable.

So, all of the .net languages could be consider to be using syntactic sugar and since F# is so new, they just haven't implemented it yet. No harm no foul.


But that seems too artificial, it clearly demonstrates that "F# is against try/with/finally". Why is that?

I guess F# might be "against" exception-handling at all. For the sake of .NET interoperability, it has to support them, but basically, there is no exception-handling* in functional programming.

Throwing/Catching exceptions means performing "jumps to nowhere" that aren't even noticed by the type system which is both fundamentally against the functional philosophy.

You can use purely functional (monadic) code to wrap exceptions. All errors are handled through values, in terms of the underlying type system and free of jumps/side effects.

Instead of writing a function

let readNumber() : int = ...

that may throw arbitrary exceptions, you'll simply state

let readNumber() : int option = ...

which makes this point automatically clear by its type signature.

*This doesn't mean we don't handle exceptional situations, it's just about the kind of exception-handling in .NET/C++.


I will clarify my comment in this answer.

  1. I maintain there is no reason to assume that you want to catch exceptions and finalize some resources at the same level. Perhaps you got used to do it that way in a language in which it was convenient to handle both at the same time, but it's a coincidence when it happens. The finalization is convenient when you do not catch all exceptions from the inner block. try...with is for catching exceptions so that the computation can continue normally. There simply is no relationship between the two (if anything, they go in opposite directions: are you catching the exception, or letting it go through?).

  2. Why do you have to finalize anything at all? Shouldn't the GC be managing unreferenced resources for you? Ah... but the language is trying to give you access to system primitives which work with side-effects, with explicit allocations and de-allocations. You have to de-allocate what you have allocated (in all cases)... Shouldn't you be blaming the rotten interface that the system is providing instead of F#, which is only the messenger in this case?

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号