9

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?

Bruno Reis
  • 37,201
  • 11
  • 119
  • 156
  • 3
    Is it artificial? I only see one redundant three-letter keyword. C++ does not get accused of being "against variables" because you have to write a redundant "int". Also, I am not sure how you use exceptions, but when I am programming, exceptions are not for logging. If you have a value to offer at the level you are at when an exception happens, return that value, otherwise, let the exception bubble further up, but I don't see what logging has to do with it... – Pascal Cuoq Jan 16 '10 at 19:17
  • 5
    @Pascal Cuoq: please don't make assumptions on **how** I use exceptions. If I want to log an exception for any reason, it does not automatically imply that I use exceptions for logging, as your comment implies I do. Furthermore, this is not a question about HOW to handle an exception and WHAT TO DO with it, but it is simply (as stated 2 times in the question): **WHY doesn't F# support try/with/finally**? – Bruno Reis Jan 16 '10 at 19:51

5 Answers5

13

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.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
7

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.

Brian
  • 117,631
  • 17
  • 236
  • 300
4

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.

Guy Coder
  • 24,501
  • 8
  • 71
  • 136
1

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++.

Dario
  • 48,658
  • 8
  • 97
  • 130
  • I do not quite agree with the statement that "there is no exception handing in functional programming". ML programmers have made (good) use of exceptions since the beginnings of the language, even when writing pure functions. It's true that commonly used type systems do not take them into account, but it's so small of a problem that the perfectly fine systems that have been proposed to handle them (one by Yi et al and one by Pessaux et al) have remained academic experiments. – Pascal Cuoq Jan 16 '10 at 19:46
  • Nontheless any exception throwing means interrupting the program flow *implicitly* which is therefore inherently unfunctional. – Dario Jan 16 '10 at 19:55
  • 1
    firstly, F# is not a purely functional programming language, not free of side effects. The whole purpose of `try/with/finally` would be to clearly separate *main* code from *recovery* code from *finalization* code, something very present in the non-functional world. – Bruno Reis Jan 16 '10 at 20:16
  • 1
    Of course F# is not purely functional and as you can see, there is standard .NET exception handling available to some extent (try/catch is even very flexible due to the possibility of pattern matching), but nevertheless certain techniques may be en- or discouraged in the spirit of functional programming (like mutables). – Dario Jan 16 '10 at 20:24
1

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?

Bruno Reis
  • 37,201
  • 11
  • 119
  • 156
Pascal Cuoq
  • 79,187
  • 7
  • 161
  • 281
  • Why CW? If it's your opinion, no need to hide behind it. – Bruno Reis Jan 16 '10 at 19:54
  • Anyways, F# is conceived to work within the .NET platform, and is clearly an impure functional language. That said, **no, I shouldn't be blaming the rotten interface that the system is provinding**, but I would really like to understand why does the language seems to go against it. – Bruno Reis Jan 16 '10 at 19:56
  • The whole question is pretty subjective and argumentative, and should have been CW in my opinion. – Pascal Cuoq Jan 16 '10 at 19:59
  • Not necessarily subjective and argumentative. If the question "should we add or not a try/with/finally block to F#" occurred to the design team, there must be a precise and clear reason to the "no". – Bruno Reis Jan 16 '10 at 20:01
  • Ok, ignore point 2 (although that rant kept with the tone of the question, I think). There is still no reason to assume that you want to catch exceptions and finalize around the same blocks. You open a `try...finally` when you allocate a resource, and you `try...with` around the smallest exception-raising block for which you know what to do if an exception happens. There is just no relationship between the two. – Pascal Cuoq Jan 16 '10 at 20:07
  • 2
    What if the allocation of a resource can throw an exception? That is not rare on the non-functional world (in which great part of F# lives in). In that case, I'd have to do `try/try/with/finally`. Why not simply `try/with/finally`? (and the main question remains) – Bruno Reis Jan 16 '10 at 20:12
  • 1
    ML, from which F# inherits indirectly, was designed around a small number of powerful, orthogonal features. In my opinion F# preserved the original spirit by not including a construct that would have been redundant with those they were already including. And don't complain, in OCaml we dont't even get `try...finally` (although you can encode it easily with `try ... with e -> ...; raise e` – Pascal Cuoq Jan 16 '10 at 20:14
  • 1
    I voted the remark "What if the allocation of a resource can throw an exception", but I'm not sure I see the problem. If it's **that** exception you're worried will disrupt the flow of your program, you don't have to finalize, do you? Because precisely for that exception, the resource wold not get allocated. Isn't it rather all the later exceptions that are a problem? – Pascal Cuoq Jan 16 '10 at 20:22
  • If the allocation throws an exception, you still need to worry about disposing it. Say you have a lib method that reads an internet resource. It sets up the connection, but the received file is corrupt. You now still need to dispose of the connection and the already allocated memory or temp files. Same is true with databases and many other things. A better solution is to use `use` and `use!`. Apart from that, I think that the structure is the way it is for consistency and ease of mental model. But that's just my opinion. – Abel Oct 26 '19 at 23:33