November 29, 2020
On Tuesday, 24 November 2020 at 20:30:33 UTC, Jonathan M Davis wrote:
> On Monday, November 23, 2020 3:58:29 AM MST Jacob Carlborg via Digitalmars-d wrote:
>> On Sunday, 22 November 2020 at 17:37:18 UTC,
> the ability to throw an error condition up to code that handles it higher up, allowing all of the code in between to largely ignore the error condition is huge, and a lot of code can get pretty disgusting without that.

It's mostly the question of values: writing nice looking code quickly vs writing code that handles all the edge cases thoroughly.  It's much more fun to write the first kind of code and it's perfectly justified in many cases.   Though as a user (of an app or a library) you would probably prefer the second kind of code.

Thoughtful error handling never looks good, and exceptions don't help with that at all: try/catch syntax is very heavyweight and encourages you to propagate errors instead of dealing with them where they arise.

Exceptions also make type signatures less precise: it's hard to be sure I handled all the errors I can recover from if I don't know which errors can actually occur.  There is no way to tell without inspecting the full transitive closure of the code I call into.

So it would be nice to have an alternative to exceptions even if it's not going to be the default mechanism.

Herb Sutter's proposal looks OK but it doesn't solve most of the problems I have with exceptions:
 1. The syntax for propagating errors is OK, but the syntax for *handling* them is still heavyweight.
 2. All the error types are mashed into std::error, so I still don't know which errors are possible at each call.
November 29, 2020
On Sunday, 29 November 2020 at 18:17:02 UTC, Roman Kashitsyn wrote:
> Thoughtful error handling never looks good, and exceptions don't help with that at all: try/catch syntax is very heavyweight and encourages you to propagate errors instead of dealing with them where they arise.

I have no proble with the syntax, but single inheritance is not so great.

One should be able to throw and catch a set of properties in some kind of pattern matching way. Maybe also inject handlers that are triggered by certain patterns at certain boundaries.

> Exceptions also make type signatures less precise: it's hard to be sure I handled all the errors I can recover from if I don't know which errors can actually occur.  There is no way to tell without inspecting the full transitive closure of the code I call into.

A good IDE should show this, so a tooling issue, not a language issue.

November 29, 2020
On 2020-11-29 19:17, Roman Kashitsyn wrote:

> It's mostly the question of values: writing nice looking code quickly vs writing code that handles all the edge cases thoroughly.  It's much more fun to write the first kind of code and it's perfectly justified in many cases.   Though as a user (of an app or a library) you would probably prefer the second kind of code.
> 
> Thoughtful error handling never looks good, and exceptions don't help with that at all: try/catch syntax is very heavyweight and encourages you to propagate errors instead of dealing with them where they arise.

In many cases you don't know what to do with an error so you need to propagate it up the stack. For example, opening a file. If that fails, the function has no idea if that's a critical error or not. It might be a config file that is missing and the application cannot start without it. Or it might the that a user selected a file which it doesn't have permission to open.

> Exceptions also make type signatures less precise: it's hard to be sure I handled all the errors I can recover from if I don't know which errors can actually occur.  There is no way to tell without inspecting the full transitive closure of the code I call into.
> 
> So it would be nice to have an alternative to exceptions even if it's not going to be the default mechanism.
> 
> Herb Sutter's proposal looks OK but it doesn't solve most of the problems I have with exceptions:
>   1. The syntax for propagating errors is OK, but the syntax for *handling* them is still heavyweight.
>   2. All the error types are mashed into std::error, so I still don't know which errors are possible at each call.

I have some ideas that I think solve most of these problems. With a syntax that (hopefully) won't alienate users. Perhaps I should write those down.

-- 
/Jacob Carlborg
November 29, 2020
On Sunday, 29 November 2020 at 19:52:27 UTC, Jacob Carlborg wrote:
> In many cases you don't know what to do with an error so you need to propagate it up the stack. For example, opening a file. If that fails, the function has no idea if that's a critical error or not. It might be a config file that is missing and the application cannot start without it. Or it might the that a user selected a file which it doesn't have permission to open.

Sure, it's not always possible to handle errors at the level they arise, those need to be propagated.
On the other hand, most of the time the caller has even less clue on how to deal with the error: the further you go from the point where error happened, the less likely it's going to be handled in a meaningful way.

Sometimes a crash with a core dump might be preferable to stack unwinding as the core dump might contain more useful information than a stack trace.

My main point is that it's really nice to see what can go wrong just from the function type signature, this transforms the way people (well, at least some of them) write software.
Checked exceptions in Java was a failed attempt to achieve this, they brought nothing but pain and boilerplate.
I believe sum types + pattern matching + error propagation sugar is a much nicer solution for errors that people might care to handle. Of course, Errors don't fall into this category.
November 29, 2020
On Sunday, 29 November 2020 at 23:20:13 UTC, Roman Kashitsyn wrote:
> Sometimes a crash with a core dump might be preferable to stack unwinding as the core dump might contain more useful information than a stack trace.

That's exactly what you get with an uncaught exception (remember the druntime catches exceptions by default, turn that off to get this behavior). One of my favorite parts of them and i'm not sure how a sumtype thing would compare. I guess you could set a conditional breakpoint.

I'm pro-exception but I also do think D should go ahead and move forward with making the return value type thing work better. If we do it right, the supporting features can be useful in other contexts too.
November 30, 2020
On Sunday, 29 November 2020 at 23:20:13 UTC, Roman Kashitsyn wrote:
>
> I believe sum types + pattern matching + error propagation sugar is a much nicer solution for errors that people might care to handle. Of course, Errors don't fall into this category.

I think it can be too limiting, especially for large software projects. Exceptions or whatever is working underneath is usually better. Problem is that all sort can go wrong in a function. Often there is a memory allocation, which can go wrong. However, catching a memory allocation error almost never happens as 99% of the time people just assume it will work. This is where exceptions come handy as uncaught exceptions will abort the program and the programmers can start to debug the problem. One thing I sometimes see among badly run projects is that they catch all exceptions, like std::exception or (...) in C++ but the action will be wrong as they catch anything that they don't expect. So in the case of the memory allocation error, it will be handled the same as a bunch of other errors which is likely to be the wrong action.

Also for script like languages which D really can be. Exceptions are great as you don't care about error handling at all when you just mock up some script like program. If you get an exception the program aborts then you find the problem and fix it, then move on.

Uncaught exceptions often help find error that we don't expect and that the program aborts is also correct since we didn't know the error would happen and therefore didn't handle it.
November 30, 2020
On Sunday, 22 November 2020 at 18:21:44 UTC, Paul Backus wrote:
> nodiscard
> I  have a DIP in the works to address this:

A value which must be used at least once sounds very similar, at least conceptually, to a value which must be used exactly once; which is to say linear types, as implemented in @live.

It probably makes sense for the two should be integrated.
November 30, 2020
On Sunday, 29 November 2020 at 23:20:13 UTC, Roman Kashitsyn wrote:
> On Sunday, 29 November 2020 at 19:52:27 UTC, Jacob Carlborg wrote:
>> [..]
>
> Sure, it's not always possible to handle errors at the level they arise, those need to be propagated.
> On the other hand, most of the time the caller has even less clue on how to deal with the error: the further you go from the point where error happened, the less likely it's going to be handled in a meaningful way.
>

What kind of error conditions are you talking about that you consider handleable locally? Do you have concrete examples? I am asking because this is way outside the experiences I have made regarding error handling and I would like to understand your perspective.

Every serious application that I have ever worked on had to deal with errors in the context of larger operations. There were essentially no locally handleable errors. So every error has to go up a few layers until there is even enough context available for recovery. This happens for example when an change operation triggers a sanity check deep inside a complex data model. The failing check has no notion of whether the larger operation needs to be rolled back or whether this is expected by the controller and there is a fallback strategy.

November 30, 2020
On 11/30/20 1:58 AM, Gregor Mückl wrote:
> On Sunday, 29 November 2020 at 23:20:13 UTC, Roman Kashitsyn wrote:
>> On Sunday, 29 November 2020 at 19:52:27 UTC, Jacob Carlborg wrote:
>>> [..]
>>
>> Sure, it's not always possible to handle errors at the level they arise, those need to be propagated.
>> On the other hand, most of the time the caller has even less clue on how to deal with the error: the further you go from the point where error happened, the less likely it's going to be handled in a meaningful way.
>>
> 
> What kind of error conditions are you talking about that you consider handleable locally? Do you have concrete examples? I am asking because this is way outside the experiences I have made regarding error handling and I would like to understand your perspective.
> 
> Every serious application that I have ever worked on had to deal with errors in the context of larger operations. There were essentially no locally handleable errors. So every error has to go up a few layers until there is even enough context available for recovery. This happens for example when an change operation triggers a sanity check deep inside a complex data model. The failing check has no notion of whether the larger operation needs to be rolled back or whether this is expected by the controller and there is a fallback strategy.
> 

That is exactly my experience as well.

My programs catch Exception type in main() to report a user friendly message. (Error is not caught.) There are a couple of exception to this:

- I catch and ignore errors locally in non-essential operations like printing verbose output or collecting timing statistics. (Nobody cares if they failed and everybody is mad if they failed.)

- I catch formatting errors to augment the error because a higher level may not understand a low level error of "cannot convert 'x' to int".

That's all... Exceptions are the simplest error management. For me, their only problem is the performance impact, which I haven't even measured. :)

The code is the cleanest with exceptions: enforce() and assert() checks guarantee that everything is sane. If not, the operation is aborted.

Ali

November 30, 2020
On Monday, 30 November 2020 at 11:02:29 UTC, Ali Çehreli wrote:
> That's all... Exceptions are the simplest error management. For me, their only problem is the performance impact, which I haven't even measured. :)

They are quite simple. The performance impact is simply because of a clumsy generic solution with expensive lookup.

But they are also a bit too simple. Like, if accessing external resources you often want to do retries. Annoying to throw all the way out.

Consider for instance if you try to fetch a file from an url, then it fails. It would have been nice to inject a recovery handler that can analyze the failure and provide a new url, sleep then retry etc.

e.g. something along the lines of this sketch:

fetch_url(url) {
  retry with (url) {
   …download attempt…
   …throw http_fail, server_busy…
  } catch (…){
   …ok cleanup, nobody wanted a retry…
  }
}


main(){
  on http_fail(url){
    url = replace_with_backup_server(url)
    return true; // retry
  }
  data = fetch_url(url)
  …
}