Re: The Right Approach to Exceptions
On 24/02/12 13:47, Regan Heath wrote: On Thu, 23 Feb 2012 15:13:17 -, James Miller wrote: On 23 February 2012 05:09, Regan Heath wrote: On Tue, 21 Feb 2012 14:19:17 -, Andrei Alexandrescu wrote: On 2/21/12 5:55 AM, Regan Heath wrote: On Sun, 19 Feb 2012 23:04:59 -, Andrei Alexandrescu wrote: On 2/19/12 4:00 PM, Nick Sabalausky wrote: Seriously, how is this not *already* crystal-clear? I feel as if every few weeks you're just coming up with deliberately random shit to argue so the rest of us have to waste our time spelling out the obvious in insanely pedantic detail. It sometimes happened to me to be reach the hypothesis that my interlocutor must be some idiot. Most often I was missing something. I get the impression that you find "Devil's advocate" a useful tool for generating debate and out of the box thinking.. there is something to be said for that, but it's probably less annoying to some if you're clear about that from the beginning. :p Where did it seem I was playing devil's advocate? Thanks. "Devil's Advocate" is perhaps not the right term, as you don't seem to ever argue the opposite to what you believe. But, it occasionally seems to me that you imply ignorance on your part, in order to draw more information from other posters on exactly what they think or are proposing. So, some get frustrated as they feel they have to explain "everything" to you (and not just you, there have been times where - for whatever reason - it seems that anything less than a description of every single minute detail results in a miss understanding - no doubt partly due to the medium in which we are communicating). Regan -- Using Opera's revolutionary email client: http://www.opera.com/mail/ I think that is technically called being facetious. Doesn't seem quite right to me: http://dictionary.reference.com/browse/facetious R Socratic irony?
Re: The Right Approach to Exceptions
On Saturday, 18 February 2012 at 22:35:58 UTC, Jonathan M Davis wrote: I'd hate to see us have a crippled exception hierarchy just because some people mishandle exceptions and simply print out messages on failure in all cases. Obviously sometimes that's what you have to do, but often you can handle them much better, and that's part of the reason that we have _Exception_ and not just Error. And a proper exception hierarchy can be very benificial to programmers who actually use it correctly. +1 Inheritance in general can (and is) misused too. Does that mean that should also be banned? Regards, Ben
Re: The Right Approach to Exceptions
On 02/26/2012 01:12 PM, deadalnix wrote: Le 25/02/2012 14:10, Timon Gehr a écrit : On 02/21/2012 07:57 PM, deadalnix wrote: opDispatch is nice, but rather incomplete. It doesn't handle template methods for example. It surely does. struct S{ template opDispatch(string op){ auto opDispatch(T...)(T args){ writeln(op, args); } } } void main() { S s; s.foo(1,2.0,"bar"); } Explicit template argument, I should have precised. Please validate your non-trivial claims before you make them. Otherwise, time gets wasted discussing non-existent problems. A good way to ensure what you claim is true is to always provide a minimal code example that demonstrates the claim, after having verified that the code indeed exposes the problematic behavior when compiled with the latest version of the reference compiler.
Re: The Right Approach to Exceptions
Le 25/02/2012 14:10, Timon Gehr a écrit : On 02/21/2012 07:57 PM, deadalnix wrote: opDispatch is nice, but rather incomplete. It doesn't handle template methods for example. It surely does. struct S{ template opDispatch(string op){ auto opDispatch(T...)(T args){ writeln(op, args); } } } void main() { S s; s.foo(1,2.0,"bar"); } Explicit template argument, I should have precised.
Re: The Right Approach to Exceptions
On 02/25/2012 09:18 PM, Andrei Alexandrescu wrote: On 2/25/12 7:19 AM, Timon Gehr wrote: On 02/24/2012 08:14 PM, Andrei Alexandrescu wrote: On 2/24/12 1:13 PM, H. S. Teoh wrote: In my mind, contract code belongs in the function signature, because they document how the function expects to be called, and what it guarantees in return. It doesn't seem to make sense to me that contracts would be hidden from the user of the library. Sorta defeats the purpose, since how is the user supposed to know what the function expects? Rely on documentation, perhaps, but docs aren't as reliable as actual contract code. Yah, and that's why we managed, with great implementation effort, to allow contract checks in interfaces. The concept has still to take off though. Andrei 'In' contracts are hardly usable at all at the moment, because they are not inherited by default. I thought that was fixed. Is there a bug report on it? Thanks! Andrei This is the bug report: http://d.puremagic.com/issues/show_bug.cgi?id=6856 ( Furthermore, there is an issue with the design: http://d.puremagic.com/issues/show_bug.cgi?id=7584 )
Re: The Right Approach to Exceptions
On 2/25/12 7:19 AM, Timon Gehr wrote: On 02/24/2012 08:14 PM, Andrei Alexandrescu wrote: On 2/24/12 1:13 PM, H. S. Teoh wrote: In my mind, contract code belongs in the function signature, because they document how the function expects to be called, and what it guarantees in return. It doesn't seem to make sense to me that contracts would be hidden from the user of the library. Sorta defeats the purpose, since how is the user supposed to know what the function expects? Rely on documentation, perhaps, but docs aren't as reliable as actual contract code. Yah, and that's why we managed, with great implementation effort, to allow contract checks in interfaces. The concept has still to take off though. Andrei 'In' contracts are hardly usable at all at the moment, because they are not inherited by default. I thought that was fixed. Is there a bug report on it? Thanks! Andrei
Re: The Right Approach to Exceptions
On 02/24/2012 08:14 PM, Andrei Alexandrescu wrote: On 2/24/12 1:13 PM, H. S. Teoh wrote: In my mind, contract code belongs in the function signature, because they document how the function expects to be called, and what it guarantees in return. It doesn't seem to make sense to me that contracts would be hidden from the user of the library. Sorta defeats the purpose, since how is the user supposed to know what the function expects? Rely on documentation, perhaps, but docs aren't as reliable as actual contract code. Yah, and that's why we managed, with great implementation effort, to allow contract checks in interfaces. The concept has still to take off though. Andrei 'In' contracts are hardly usable at all at the moment, because they are not inherited by default.
Re: The Right Approach to Exceptions
On 02/21/2012 07:57 PM, deadalnix wrote: opDispatch is nice, but rather incomplete. It doesn't handle template methods for example. It surely does. struct S{ template opDispatch(string op){ auto opDispatch(T...)(T args){ writeln(op, args); } } } void main() { S s; s.foo(1,2.0,"bar"); }
Re: The Right Approach to Exceptions
On Fri, Feb 24, 2012 at 07:37:03PM -0500, Jonathan M Davis wrote: > On Friday, February 24, 2012 16:18:59 H. S. Teoh wrote: > > On Fri, Feb 24, 2012 at 06:48:47PM -0500, Jonathan M Davis wrote: [...] > > > which, as you point out, would have to be done at runtime, or by > > > doing something similar to what Java 7 is doing and make it so > > > that multiple exceptions can be caught with the same block > > > > > > catch(Ex1, Ex2, Ex3 e) > > > > > > and e ends up being the most derived type that is common to them. > > > > This can also be done by catch conditions by using is-expressions or > > derived class casts, though it would be more verbose. We may not > > need to implement both. > > You do run into the who rethrowing issue with that though, if you > catch the wrong type. It also can force you to combine disparate catch > blocks, since you caught the common type, and you're forced to use > if-else statements to separate out the various cases instead of > letting catch take care of it. All in all, it would just be _way_ > cleaner to have a way to give a list of exception types that you want > that catch to catch. I think you misunderstood me, what I'm referring to is this: // MyException is common base of DerivedEx1 and DerivedEx2 catch(MyException e : cast(DerivedEx1)e !is null || cast(DerivedEx2)e !is null) { // catch code } There's no rethrowing necessary, the catch block doesn't execute unless the exception is either DerivedEx1 or DerivedEx2. You also get to specify the common base type to assign to e. But yes, this is more verbose. I was just pointing out that it may not be necessary to extend the language to catch multiple exception types if we're already going to implement catch conditions. > Also, I think that having a syntax for simply giving a list of > exceptions that an exception block catches is plenty without needing > the whole condition thing. The condition thing just seems like > overkill. But maybe someone has a valid use case where giving a > specific list of exceptions doesn't cut it. The condition thing may be a another justification for the whole Variant[string] thing, though, if you could do something like this: catch(MyException e : ("myattr" in e.info) !is null) { // catch code } This then lets intermediate code on the call stack add annotations to a passing exception, and a catch block further up the call stack to only catch annotated exceptions. I can't think of any actual real-world use case for this, but maybe Jose(?), or whoever it was that wanted to add extra info to exceptions in transit, can. > > I don't think D's job is to stop people from doing stupid things. > > Otherwise we might as well go back to Pascal or something. :) > > Indeed. What we need is a setup which enables (and hopefully encourages) > people to do the right thing. We can't stop people from being stupid. Having a proper exception hierarchy in Phobos will, in my mind, set a good precedent to proper use of exceptions. To be quite honest, I was shocked when I discovered the current exception-by-module situation in Phobos---after reading TDPL I was under the impression that we had a properly-designed exception hierarchy. :) > For instance, much as Java generally tries to protect you from your > own stupidity, the fact that it has very good exception hierarchy > doesn't stop people (and in some cases the developers of its standard > library) from doing stupid things with exceptions. [...] Trying to prevent stupidity often cripples the language/system. As the old Unix adage goes: Unix was not designed to stop people from doing stupid things, because that would also stop them from doing clever things. -- Doug Gwyn This is one area where D trumps Java by a long shot IMO. I find Java really straitjacketed sometimes in the way it tries to "protect" me from myself. I have to jump through hoops to convince it that, yes, I really *really* want to do this. T -- Always remember that you are unique. Just like everybody else. -- despair.com
Re: The Right Approach to Exceptions
On Friday, February 24, 2012 16:18:59 H. S. Teoh wrote: > On Fri, Feb 24, 2012 at 06:48:47PM -0500, Jonathan M Davis wrote: > > 1. Being able to annotate catch blocks in some manner to enable them > > to catch multiple, specific exceptions - either by using some kind of > > condition > > > > catch(Ex1 e) if(condition) > > Even though I was (one of) the one(s) who proposed this syntax, I think > a better syntax would be something like: > > catch(Ex1 e: condition) > > but that's just bikeshedding. > > > which, as you point out, would have to be done at runtime, or by doing > > something similar to what Java 7 is doing and make it so that multiple > > exceptions can be caught with the same block > > > > catch(Ex1, Ex2, Ex3 e) > > > > and e ends up being the most derived type that is common to them. > > This can also be done by catch conditions by using is-expressions or > derived class casts, though it would be more verbose. We may not need to > implement both. You do run into the who rethrowing issue with that though, if you catch the wrong type. It also can force you to combine disparate catch blocks, since you caught the common type, and you're forced to use if-else statements to separate out the various cases instead of letting catch take care of it. All in all, it would just be _way_ cleaner to have a way to give a list of exception types that you want that catch to catch. Also, I think that having a syntax for simply giving a list of exceptions that an exception block catches is plenty without needing the whole condition thing. The condition thing just seems like overkill. But maybe someone has a valid use case where giving a specific list of exceptions doesn't cut it. > I don't think D's job is to stop people from doing stupid things. > Otherwise we might as well go back to Pascal or something. :) Indeed. What we need is a setup which enables (and hopefully encourages) people to do the right thing. We can't stop people from being stupid. For instance, much as Java generally tries to protect you from your own stupidity, the fact that it has very good exception hierarchy doesn't stop people (and in some cases the developers of its standard library) from doing stupid things with exceptions. And D, which tries to protect you without preventing you from being stupid (unlike Java, which generally tries to prevent you from doing something stupid), certainly isn't going to stop you from being stupid. - Jonathan M Davis
Re: The Right Approach to Exceptions
On Fri, Feb 24, 2012 at 06:48:47PM -0500, Jonathan M Davis wrote: [...] > However, Java does have a great exception hierarchy, much of which is > standard. And that's a lot better than having only a handful of > unrelated exceptions, let alone having all exceptions be specific to a > module like we do now. > > So, we really have 2 problems here: > > 1. Phobos doesn't have a proper exception hierarchy. Its exceptions > need to be better organized according to the problem that they > indicate rather than by module. +1. > 2. There are something things that can be done to Exception and how > exceptions work in D to help resolve issues related to wanting to > catch and handle exceptions without having to catch all exceptions > that match their common base type or otherwise have to duplicate code > among catch blocks. > > The two proposals that seem best for this are > > 1. Being able to annotate catch blocks in some manner to enable them > to catch multiple, specific exceptions - either by using some kind of > condition > > catch(Ex1 e) if(condition) Even though I was (one of) the one(s) who proposed this syntax, I think a better syntax would be something like: catch(Ex1 e: condition) but that's just bikeshedding. > which, as you point out, would have to be done at runtime, or by doing > something similar to what Java 7 is doing and make it so that multiple > exceptions can be caught with the same block > > catch(Ex1, Ex2, Ex3 e) > > and e ends up being the most derived type that is common to them. This can also be done by catch conditions by using is-expressions or derived class casts, though it would be more verbose. We may not need to implement both. [...] > So, if we better organize Phobos' exceptions, implement a multi-catch > feature, and implement Andrei's Variant[string], then Phobos' > exceptions will work far better (and by far more reusable), and some > of the greater shortcomings of D's exceptions themselves should be > mitigated, if not eliminated. +1. > Naturally, that still won't stop people from doing stupid things with > exceptions in their own code (like throwing on EOF), but we'll have a > good setup for those who use exceptions properly, and we should be > able to avoid putting stupid uses of exceptions in Phobos, simply > because the code is open and peer-reviewed, and I think that most of > the people around here agree that using exceptions for stuff like EOF > is stupid. [...] I don't think D's job is to stop people from doing stupid things. Otherwise we might as well go back to Pascal or something. :) T -- For every argument for something, there is always an equal and opposite argument against it. Debates don't give answers, only wounded or inflated egos.
Re: The Right Approach to Exceptions
On Friday, February 24, 2012 16:38:31 Steven Schveighoffer wrote: > On to my second point. One of the issues I have with Java is that > exceptions are *overused*. For example, EOF should not be an exception, > most files have ends, it's not a very exceptional situation. If there is > an intuitive way to use an existing return value to convey an error rather > than an exception, I'd prefer the return value. There is a runtime cost > just for setting up a try/catch block, even if no exceptions are thrown. It's definitely true that Java uses exceptions when they shouldn't - EOF being a good example of that. Exceptions should almost always only be used when something actually goes wrong. EOF is not something going wrong. However, Java does have a great exception hierarchy, much of which is standard. And that's a lot better than having only a handful of unrelated exceptions, let alone having all exceptions be specific to a module like we do now. So, we really have 2 problems here: 1. Phobos doesn't have a proper exception hierarchy. Its exceptions need to be better organized according to the problem that they indicate rather than by module. 2. There are something things that can be done to Exception and how exceptions work in D to help resolve issues related to wanting to catch and handle exceptions without having to catch all exceptions that match their common base type or otherwise have to duplicate code among catch blocks. The two proposals that seem best for this are 1. Being able to annotate catch blocks in some manner to enable them to catch multiple, specific exceptions - either by using some kind of condition catch(Ex1 e) if(condition) which, as you point out, would have to be done at runtime, or by doing something similar to what Java 7 is doing and make it so that multiple exceptions can be caught with the same block catch(Ex1, Ex2, Ex3 e) and e ends up being the most derived type that is common to them. 2. Andrei's Variant[string] proposal, which would enable a generic formatting scheme with string templates which a free function could use to generate messages from exceptions without caring about the actual exception type. It would also allow you to tack on extra information to the exception which wouldn't normally be part of that type of exception but your particular program needs for some reason. So, if we better organize Phobos' exceptions, implement a multi-catch feature, and implement Andrei's Variant[string], then Phobos' exceptions will work far better (and by far more reusable), and some of the greater shortcomings of D's exceptions themselves should be mitigated, if not eliminated. Naturally, that still won't stop people from doing stupid things with exceptions in their own code (like throwing on EOF), but we'll have a good setup for those who use exceptions properly, and we should be able to avoid putting stupid uses of exceptions in Phobos, simply because the code is open and peer-reviewed, and I think that most of the people around here agree that using exceptions for stuff like EOF is stupid. - Jonathan M Davis
Re: The Right Approach to Exceptions
On Fri, Feb 24, 2012 at 04:38:31PM -0500, Steven Schveighoffer wrote: [...] > On to my second point. One of the issues I have with Java is that > exceptions are *overused*. For example, EOF should not be an > exception, most files have ends, it's not a very exceptional > situation. If there is an intuitive way to use an existing return > value to convey an error rather than an exception, I'd prefer the > return value. There is a runtime cost just for setting up a try/catch > block, even if no exceptions are thrown. [...] Yeah, EOF shouldn't be an exception. It should simply be a terminal state that file-reading code gets into, whereupon it will simply return some special value (e.g., dchar.init, or empty array) upon further reading. Preferably also have a .eof or .empty method or something equivalent that indicates EOF, so you can write "while (!obj.eof) { ... }". Using an exception for EOF is abuse, IMO. Another beef I have about EOFs is that I've seen code where EOF is returned only once, and then you're not supposed to call that code ever again after that because it will crash. I think that's really stupid. If you hit EOF, attempting to read further should simply return EOF again without changing the state of the system any further. Multiple attempts to read past EOF should still return EOF. (One would think this was obvious, but obviously it's not very obvious...) T -- To err is human; to forgive is not our policy. -- Samuel Adler
Re: The Right Approach to Exceptions
On Sat, 18 Feb 2012 13:52:05 -0500, Andrei Alexandrescu wrote: There's a discussion that started in a pull request: https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca Let's come up with a good doctrine for exception defining and handling in Phobos. From experience I humbly submit that catching by type is most of the time useless. OK, so after reading about 100 or so of these messages, I stopped. Sorry if this has been said before, but here is my take: many many times when dealing with exceptions I hate to do this: class ExceptionTypeA : Exception {...} class ExceptionTypeB : Exception {...} try { } catch(ExceptionTypeA ex) { // code } catch(ExceptionTypeB ex) { // same f'ing code } I know, I could do this: catch(Exception e) { if(cast(ExceptionTypeA)e || cast(ExceptionTypeB)e) { // code } else throw e; } But that sucks, and as others have pointed out, it kills the stack trace. So I love the idea that others have specified to have a 'template constraint' type piece for catch. IMO, it should not be a template, but a runtime check. i.e., I don't think we need to templatize the catch, we just need to add extra code to the 'should we catch' check that currently consists of 'does the type match'. This would be a huge improvement over existing exception catching techniques. So the above would become: catch(Exception e) if(e is ExceptionTypeA or ExceptionTypeB) // not sure of the exact syntax, maybe the if statement I used above? I agree the constraints should be pure and nothrow. On to my second point. One of the issues I have with Java is that exceptions are *overused*. For example, EOF should not be an exception, most files have ends, it's not a very exceptional situation. If there is an intuitive way to use an existing return value to convey an error rather than an exception, I'd prefer the return value. There is a runtime cost just for setting up a try/catch block, even if no exceptions are thrown. -Steve
Re: The Right Approach to Exceptions
On 2/24/12 1:13 PM, H. S. Teoh wrote: In my mind, contract code belongs in the function signature, because they document how the function expects to be called, and what it guarantees in return. It doesn't seem to make sense to me that contracts would be hidden from the user of the library. Sorta defeats the purpose, since how is the user supposed to know what the function expects? Rely on documentation, perhaps, but docs aren't as reliable as actual contract code. Yah, and that's why we managed, with great implementation effort, to allow contract checks in interfaces. The concept has still to take off though. Andrei
Re: The Right Approach to Exceptions
On Fri, Feb 24, 2012 at 01:46:56PM -0500, Jonathan M Davis wrote: > On Friday, February 24, 2012 07:57:13 H. S. Teoh wrote: > > Actually, I wonder if it makes sense for the compiler to insert > > in-contract code in the *caller* instead of the callee. Conceptually > > speaking, an in-contract means "you have to fulfill these conditions > > before calling this function". So why not put the check in the > > caller? > > > > Similarly, an out-contract means "this function's return value will > > satisfy these conditions" - so let the caller verify that this is > > true. > > > > Semantically it amounts to the same thing, but this gives us more > > flexibility: the library doesn't have to be compiled with contracts > > on/off, the contracts are in the library API, and the user tells the > > compiler whether or not to wrap the contract code around each call > > to the library. > > > > (Yes this bloats the code everywhere a DbC function is called, but > > this is supposed to be done in non-release builds only anyway, so I > > don't think that matters so much.) > > It wouldn't work unless the source were available, because otherwise > you just have the function signature. [...] In my mind, contract code belongs in the function signature, because they document how the function expects to be called, and what it guarantees in return. It doesn't seem to make sense to me that contracts would be hidden from the user of the library. Sorta defeats the purpose, since how is the user supposed to know what the function expects? Rely on documentation, perhaps, but docs aren't as reliable as actual contract code. T -- Без труда не выловишь и рыбку из пруда.
Re: The Right Approach to Exceptions
On Friday, February 24, 2012 08:27:44 H. S. Teoh wrote: > On Fri, Feb 24, 2012 at 07:57:13AM -0800, H. S. Teoh wrote: > > > On Thursday, February 23, 2012 15:18:27 H. S. Teoh wrote: > > > > In my book, a linked library shares equal status with the "main > > > > program", therefore the definition of "user input" still sits at > > > > the internal-to-program and external boundary. > > [...] > > > I wasn't trying to say that library code should always use DbC and > > application code should always use defensive programming. I'm saying > > that if it makes sense for a function to use DbC (or vice versa) then > > it should use DbC regardless of whether it's in a library or not. > > [...] > > Argh, I just realized that my first post was so poorly worded it made no > sense at all. My second post was what I meant to say. :) > > What I was trying to express in the first post was that "user input" > comes from a source external to the program, whether from a user typing > at the keyboard, or from a file or network resource, and this data > traverses program code paths until eventually they are converted into > the internal form the program uses for further processing. Input > sanitization should be done along this code path until the input is > processed into program-internal form, at which point, DbC begins to take > effect, the assumption being that after preprocessing by the input > sanitization code, all data should be valid, and if not, it's a failure > of the input processing code and represents a logic flaw in the program, > therefore an assertion should be thrown. Yes. In general, that's the core difference between assertions and exceptions. If an assertion fails, it's a bug in the code, whereas if an exception is thrown, then it may or may not be caused by a program bug (and is frequently caused by interacting with I/O - be it directly or indirectly). But that does require a judgement call sometimes as to which approach is better in a particular situation, and if you're being utterly paranoid (which some programs probably need to be but most don't), then you could end up using exceptions where you'd normally use assertions simply because you want to _guarantee_ that the check is always done. But hopefully, that sort of thing would be kept to a minimum. Regarldess, at the core, assertions are for verifying program correctness, and exceptions are for reporting error conditions caused by bad stuff happening during the normal operation of the program. - Jonathan M Davis
Re: The Right Approach to Exceptions
On Friday, February 24, 2012 07:57:13 H. S. Teoh wrote: > Actually, I wonder if it makes sense for the compiler to insert > in-contract code in the *caller* instead of the callee. Conceptually > speaking, an in-contract means "you have to fulfill these conditions > before calling this function". So why not put the check in the caller? > > Similarly, an out-contract means "this function's return value will > satisfy these conditions" - so let the caller verify that this is true. > > Semantically it amounts to the same thing, but this gives us more > flexibility: the library doesn't have to be compiled with contracts > on/off, the contracts are in the library API, and the user tells the > compiler whether or not to wrap the contract code around each call to > the library. > > (Yes this bloats the code everywhere a DbC function is called, but this > is supposed to be done in non-release builds only anyway, so I don't > think that matters so much.) It wouldn't work unless the source were available, because otherwise you just have the function signature. So, the result would be inconsistent, and in the case of non-templated functions built against a non-release version of the callee, you'd end up having the checks twice, because the callee would have to have them regardless (since it's compiled separately, and not every function calling it would necessarily have its source). For templated functions, it already depends on the caller whether the assertions are enabled, because it's instantiated when the module with the caller in it is built. It's an interesting idea though. - Jonathan M Davis
Re: The Right Approach to Exceptions
On Fri, Feb 24, 2012 at 07:57:13AM -0800, H. S. Teoh wrote: > > On Thursday, February 23, 2012 15:18:27 H. S. Teoh wrote: > > > In my book, a linked library shares equal status with the "main > > > program", therefore the definition of "user input" still sits at > > > the internal-to-program and external boundary. [...] > I wasn't trying to say that library code should always use DbC and > application code should always use defensive programming. I'm saying > that if it makes sense for a function to use DbC (or vice versa) then > it should use DbC regardless of whether it's in a library or not. [...] Argh, I just realized that my first post was so poorly worded it made no sense at all. My second post was what I meant to say. :) What I was trying to express in the first post was that "user input" comes from a source external to the program, whether from a user typing at the keyboard, or from a file or network resource, and this data traverses program code paths until eventually they are converted into the internal form the program uses for further processing. Input sanitization should be done along this code path until the input is processed into program-internal form, at which point, DbC begins to take effect, the assumption being that after preprocessing by the input sanitization code, all data should be valid, and if not, it's a failure of the input processing code and represents a logic flaw in the program, therefore an assertion should be thrown. T -- Those who don't understand Unix are condemned to reinvent it, poorly.
Re: The Right Approach to Exceptions
On Thu, Feb 23, 2012 at 07:06:02PM -0500, Jonathan M Davis wrote: > On Thursday, February 23, 2012 15:18:27 H. S. Teoh wrote: > > On Thu, Feb 23, 2012 at 12:07:40PM -0800, Jonathan M Davis wrote: > > In my book, a linked library shares equal status with the "main > > program", therefore the definition of "user input" still sits at the > > internal-to-program and external boundary. > > Yes, "in your book." Some people will agree with you and some won't. > It really depends on what the code is doing though IMHO. In some > cases, one is better and in some cases, the other is better. But it > _is_ important to remember that there's a big difference between > linking against a library over which you have control and a 3rd party > library. > > And there are times when it just plain makes more sense to have a > function which throws an exception on bad input regardless of whether > it's an "internal" function or not. For instance, if you want to > convert a string to something else (e.g. with SysTime's > fromISOExtString or even just with std.conv.to), you need to actually > verify that the string has a value which can be correctly converted. > It's actually cheaper to have the function doing the conversion do the > checking rather than have another function do a check first, and then > have the converting function not check (save perhaps for an assertion > outside of release mode), because then you'll be processing the string > _twice_. I guess this is a judgment call. Personally, I would consider arguments to string conversion functions to be "user input" even though, technically speaking, you could just be passing a byte array literal to it, in which case it's just a case of bad input parameters. > This is _not_ a cut-and-dried issue. Sometimes DbC makes more sense, > and sometimes defensive programming does. You pick the one that works > best for a given situation. > > The whole thing is a gray area, and you're not going to get a > consensus that a library should always use DbC on its functions or > that it should always use defensive programming. I wasn't trying to say that library code should always use DbC and application code should always use defensive programming. I'm saying that if it makes sense for a function to use DbC (or vice versa) then it should use DbC regardless of whether it's in a library or not. If I were to write a string conversion function, for example, I wouldn't use contracts to enforce the right encoding, regardless of whether it's in a library or in application code. I would use exceptions, simply because that's what makes sense in this case. Just because something is in the library shouldn't change whether DbC or defensive programming is used. It's the semantics that matter, not whether it's in a library. [...] > > No need to templatize anything, just ship two versions of the > > library, one with DbC compiled in, one without. Let the user decide > > which one to link in. > > There _is_ a need to do that if the caller wants to control whether an > assertion or an exception is used. There's also a need if you want to > enable it in some places and not in others. However, the reality of > the matter is that using a debug version of a library is as close as > you're likely to get. And I'm certainly not arguing that templatizing > functions in this manner would be a good idea. I'm just pointing aut > that there are issues with how DbC is currently implemented. [...] Actually, I wonder if it makes sense for the compiler to insert in-contract code in the *caller* instead of the callee. Conceptually speaking, an in-contract means "you have to fulfill these conditions before calling this function". So why not put the check in the caller? Similarly, an out-contract means "this function's return value will satisfy these conditions" - so let the caller verify that this is true. Semantically it amounts to the same thing, but this gives us more flexibility: the library doesn't have to be compiled with contracts on/off, the contracts are in the library API, and the user tells the compiler whether or not to wrap the contract code around each call to the library. (Yes this bloats the code everywhere a DbC function is called, but this is supposed to be done in non-release builds only anyway, so I don't think that matters so much.) T -- Тише едешь, дальше будешь.
Re: The Right Approach to Exceptions
On Thu, 23 Feb 2012 15:13:17 -, James Miller wrote: On 23 February 2012 05:09, Regan Heath wrote: On Tue, 21 Feb 2012 14:19:17 -, Andrei Alexandrescu wrote: On 2/21/12 5:55 AM, Regan Heath wrote: On Sun, 19 Feb 2012 23:04:59 -, Andrei Alexandrescu wrote: On 2/19/12 4:00 PM, Nick Sabalausky wrote: Seriously, how is this not *already* crystal-clear? I feel as if every few weeks you're just coming up with deliberately random shit to argue so the rest of us have to waste our time spelling out the obvious in insanely pedantic detail. It sometimes happened to me to be reach the hypothesis that my interlocutor must be some idiot. Most often I was missing something. I get the impression that you find "Devil's advocate" a useful tool for generating debate and out of the box thinking.. there is something to be said for that, but it's probably less annoying to some if you're clear about that from the beginning. :p Where did it seem I was playing devil's advocate? Thanks. "Devil's Advocate" is perhaps not the right term, as you don't seem to ever argue the opposite to what you believe. But, it occasionally seems to me that you imply ignorance on your part, in order to draw more information from other posters on exactly what they think or are proposing. So, some get frustrated as they feel they have to explain "everything" to you (and not just you, there have been times where - for whatever reason - it seems that anything less than a description of every single minute detail results in a miss understanding - no doubt partly due to the medium in which we are communicating). Regan -- Using Opera's revolutionary email client: http://www.opera.com/mail/ I think that is technically called being facetious. Doesn't seem quite right to me: http://dictionary.reference.com/browse/facetious R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Re: The Right Approach to Exceptions
On Thursday, February 23, 2012 15:18:27 H. S. Teoh wrote: > On Thu, Feb 23, 2012 at 12:07:40PM -0800, Jonathan M Davis wrote: > In my book, a linked library shares equal status with the "main > program", therefore the definition of "user input" still sits at the > internal-to-program and external boundary. Yes, "in your book." Some people will agree with you and some won't. It really depends on what the code is doing though IMHO. In some cases, one is better and in some cases, the other is better. But it _is_ important to remember that there's a big difference between linking against a library over which you have control and a 3rd party library. And there are times when it just plain makes more sense to have a function which throws an exception on bad input regardless of whether it's an "internal" function or not. For instance, if you want to convert a string to something else (e.g. with SysTime's fromISOExtString or even just with std.conv.to), you need to actually verify that the string has a value which can be correctly converted. It's actually cheaper to have the function doing the conversion do the checking rather than have another function do a check first, and then have the converting function not check (save perhaps for an assertion outside of release mode), because then you'll be processing the string _twice_. This is _not_ a cut-and-dried issue. Sometimes DbC makes more sense, and sometimes defensive programming does. You pick the one that works best for a given situation. The whole thing is a gray area, and you're not going to get a consensus that a library should always use DbC on its functions or that it should always use defensive programming. > > Arguably, the best thing would be if there was a way for the caller to > > indicate whether it wanted the callee to have DbC enable and possibly > > even indicate whether it wanted the callee to use DbC or defensive > > programming. But there's no way to do that in D, and I'm not sure that > > it could even be done with the C linking model - at least, there's no > > way to it without templatizing everything and giving an argument to > > the template indicating what you want, which obviously isn't a good > > solution (and won't work at all in the case of virtual functions, > > since they can't be templatized). > No need to templatize anything, just ship two versions of the library, > one with DbC compiled in, one without. Let the user decide which one to > link in. There _is_ a need to do that if the caller wants to control whether an assertion or an exception is used. There's also a need if you want to enable it in some places and not in others. However, the reality of the matter is that using a debug version of a library is as close as you're likely to get. And I'm certainly not arguing that templatizing functions in this manner would be a good idea. I'm just pointing aut that there are issues with how DbC is currently implemented. And the primary problem with how DbC is implemented is the fact that its assertions test the caller, not the callee, but the assertions end up in the callee. So, the assertions are separated from the code that they're actually testing. It's the best that we can do at this point, but it does result in a weird situation where you end up using assertions to test _other_ people's code rather than your own. - Jonathan M Davis
Re: The Right Approach to Exceptions
On Thu, Feb 23, 2012 at 12:07:40PM -0800, Jonathan M Davis wrote: > On Thursday, February 23, 2012 07:47:55 H. S. Teoh wrote: [...] > > The way I understand it, DbC is used for ensuring *program* > > correctness (ensure that program logic does not get itself into a > > bad state); defensive programming is for sanitizing *user input* > > (ensure that no matter what the user does, the program doesn't get > > into a bad state). > > > > That's why DbC is compiled out in release mode -- the assumption is > > that you have thoroughly tested your program logic and verified > > there are no logic problems. Input sanitizing is never compiled out, > > because you never know what users will do, so you always have to > > check. > > > > The two do somewhat overlap, of course. For example, failing to > > sanitize user input may eventually lead to passing invalid arguments > > to an internal function. > > Exactly. But where things tend to blur is the concept of "user input." > For instance, if you're using a 3rd party library, should it be > asserting on the arguments that you pass it? In my book, a linked library shares equal status with the "main program", therefore the definition of "user input" still sits at the internal-to-program and external boundary. > Unless you compile it in non-release mode, it obviously won't, which > could be an argument for using exceptions, but regardless of that, > from the library's perspective, you're a user. I believe the traditional way is to ship a debug or devel version of the library which is linked when you compile in non-release mode (e.g., libc-dbg), and then in release mode the release mode library is linked (libc proper). That way DbC will be enforced in non-release mode by the library, and suppressed in the release mode binary. If libraries only ship in release mode, then that sorta defeats the point of DbC, which is to ensure program correctness before release and not get in the way after. Now the library has to be paranoid and always sanitize all inputs. > If it used DbC, it would be putting assertions it in its own code to > test _your_ code. And since you're a user, it arguably should use > exceptions to make sure that the arguments that it gets are correct. No, the library should ship a development version with all contracts compiled-in, so that contract violations will be enforced during development & testing. Sadly, this isn't often done in practice, which leads to the sad situation where the program/library boundary has a lot of overhead, because the library must be paranoid and always sanitize all inputs no matter what. [...] > Arguably, the best thing would be if there was a way for the caller to > indicate whether it wanted the callee to have DbC enable and possibly > even indicate whether it wanted the callee to use DbC or defensive > programming. But there's no way to do that in D, and I'm not sure that > it could even be done with the C linking model - at least, there's no > way to it without templatizing everything and giving an argument to > the template indicating what you want, which obviously isn't a good > solution (and won't work at all in the case of virtual functions, > since they can't be templatized). [...] No need to templatize anything, just ship two versions of the library, one with DbC compiled in, one without. Let the user decide which one to link in. T -- Never trust an operating system you don't have source for! -- Martin Schulze
Re: The Right Approach to Exceptions
On Thursday, February 23, 2012 07:47:55 H. S. Teoh wrote: > On Thu, Feb 23, 2012 at 02:57:43AM -0800, Jonathan M Davis wrote: > [...] > > > DbC tends to work better with internal stuff where you control both > > the caller and the callee, whereas defensive programming works better > > with public APIs. But regardless, which is best to use depends on the > > situtation and what you're goals are. > > [...] > > The way I understand it, DbC is used for ensuring *program* correctness > (ensure that program logic does not get itself into a bad state); > defensive programming is for sanitizing *user input* (ensure that no > matter what the user does, the program doesn't get into a bad state). > > That's why DbC is compiled out in release mode -- the assumption is that > you have thoroughly tested your program logic and verified there are no > logic problems. Input sanitizing is never compiled out, because you > never know what users will do, so you always have to check. > > The two do somewhat overlap, of course. For example, failing to sanitize > user input may eventually lead to passing invalid arguments to an > internal function. Exactly. But where things tend to blur is the concept of "user input." For instance, if you're using a 3rd party library, should it be asserting on the arguments that you pass it? Unless you compile it in non-release mode, it obviously won't, which could be an argument for using exceptions, but regardless of that, from the library's perspective, you're a user. If it used DbC, it would be putting assertions it in its own code to test _your_ code. And since you're a user, it arguably should use exceptions to make sure that the arguments that it gets are correct. So, it all tends to get blurry, and the best decision varies from situation to situation. It's also part of what makes the concept of assertion vs exception harder for some folks (and as nice as enforce may be it, it blurs the line even further for many people IMHO, since it then makes exceptions the same as assertions syntactically). Arguably, the best thing would be if there was a way for the caller to indicate whether it wanted the callee to have DbC enable and possibly even indicate whether it wanted the callee to use DbC or defensive programming. But there's no way to do that in D, and I'm not sure that it could even be done with the C linking model - at least, there's no way to it without templatizing everything and giving an argument to the template indicating what you want, which obviously isn't a good solution (and won't work at all in the case of virtual functions, since they can't be templatized). - Jonathan M Davis
Re: The Right Approach to Exceptions
On 23 February 2012 05:09, Regan Heath wrote: > On Tue, 21 Feb 2012 14:19:17 -, Andrei Alexandrescu > wrote: > >> On 2/21/12 5:55 AM, Regan Heath wrote: >>> >>> On Sun, 19 Feb 2012 23:04:59 -, Andrei Alexandrescu >>> wrote: >>> On 2/19/12 4:00 PM, Nick Sabalausky wrote: >>> >>> > Seriously, how is this not *already* crystal-clear? I feel as if > every few > weeks you're just coming up with deliberately random shit to argue so > the > rest of us have to waste our time spelling out the obvious in insanely > pedantic detail. It sometimes happened to me to be reach the hypothesis that my interlocutor must be some idiot. Most often I was missing something. >>> >>> >>> I get the impression that you find "Devil's advocate" a useful tool for >>> generating debate and out of the box thinking.. there is something to be >>> said for that, but it's probably less annoying to some if you're clear >>> about that from the beginning. :p >> >> >> Where did it seem I was playing devil's advocate? Thanks. > > > "Devil's Advocate" is perhaps not the right term, as you don't seem to ever > argue the opposite to what you believe. But, it occasionally seems to me > that you imply ignorance on your part, in order to draw more information > from other posters on exactly what they think or are proposing. So, some > get frustrated as they feel they have to explain "everything" to you (and > not just you, there have been times where - for whatever reason - it seems > that anything less than a description of every single minute detail results > in a miss understanding - no doubt partly due to the medium in which we are > communicating). > > > Regan > > -- > Using Opera's revolutionary email client: http://www.opera.com/mail/ I think that is technically called being facetious. -- James Miller
Re: The Right Approach to Exceptions
On Thu, Feb 23, 2012 at 02:57:43AM -0800, Jonathan M Davis wrote: [...] > DbC tends to work better with internal stuff where you control both > the caller and the callee, whereas defensive programming works better > with public APIs. But regardless, which is best to use depends on the > situtation and what you're goals are. [...] The way I understand it, DbC is used for ensuring *program* correctness (ensure that program logic does not get itself into a bad state); defensive programming is for sanitizing *user input* (ensure that no matter what the user does, the program doesn't get into a bad state). That's why DbC is compiled out in release mode -- the assumption is that you have thoroughly tested your program logic and verified there are no logic problems. Input sanitizing is never compiled out, because you never know what users will do, so you always have to check. The two do somewhat overlap, of course. For example, failing to sanitize user input may eventually lead to passing invalid arguments to an internal function. T -- Only boring people get bored. -- JM
Re: The Right Approach to Exceptions
On Wednesday, February 22, 2012 22:33:47 Jim Hewes wrote: > On 2/21/2012 2:29 PM, Ali Çehreli wrote: > > On 02/18/2012 09:09 PM, Jim Hewes wrote: > > > I think of exception handling as tied to contract programming. > > > > I think your use of the word 'contract' is colliding with the contract > > programming feature. What you describe later does not match with the > > contract programming and I guess is the reason why Andrei is pointing > > out two chapters from TDPL. > > > > I will reread those chapters later today but I think Andrei is referring > > to the distinction between assert() and std.exception.enforce(). > > Thanks. I assume the objection is about the bad parameters. In design by > contract, a function should not be checking the input, correct? It > assumes it's correct. But I was mostly thinking of the case when the > functions are more of a public API and you can't trust the input. I did > mention using assert for internal functions. But I guess if you are > strict, you should never check input. I just shouldn't mention design by > contract at all then. :) In DbC, you use assertions to verify arguments, because it's up to the caller to ensure that the arguments are valid - otherwise it's breaking the contract. So, in release mode, there are no checks, and in non-release mode, your program gets killed if the contract is violated. It's considered a bug in the code if bad arguments are passed to the function. In defensive programming, however, you use exceptions, which is much safer, because the function will never actually execute with bad arguments, but it's slower, because it's always checking, whereas assertions are compiled out in release mode. Also, it's not necessarily a bug when you pass an invalid argument to a function using defensive programming, because throwing an failure is part of its normal behavior (and won't be compiled out in release mode) and is recoverable, unlike an AssertError. Rather, the function is merely informing the caller that it was given bad arguments and lets the caller sort it out. DbC tends to work better with internal stuff where you control both the caller and the callee, whereas defensive programming works better with public APIs. But regardless, which is best to use depends on the situtation and what you're goals are. - Jonathan M Davis
Re: The Right Approach to Exceptions
On 2/21/2012 2:29 PM, Ali Çehreli wrote: On 02/18/2012 09:09 PM, Jim Hewes wrote: > I think of exception handling as tied to contract programming. I think your use of the word 'contract' is colliding with the contract programming feature. What you describe later does not match with the contract programming and I guess is the reason why Andrei is pointing out two chapters from TDPL. I will reread those chapters later today but I think Andrei is referring to the distinction between assert() and std.exception.enforce(). Thanks. I assume the objection is about the bad parameters. In design by contract, a function should not be checking the input, correct? It assumes it's correct. But I was mostly thinking of the case when the functions are more of a public API and you can't trust the input. I did mention using assert for internal functions. But I guess if you are strict, you should never check input. I just shouldn't mention design by contract at all then. :)
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Thu, Feb 23, 2012 at 04:00:04AM +0100, Juan Manuel Cabo wrote: [...] > I'm sorry. I went over the top. I apollogize. I apologize too, for some of the inflammatory things I said in the heat of the moment in some of my replies to this thread. > ..I won't post for a while. > This thread is almost poping a vein in my neck.. Heh, several times I had to stop myself from hitting Send, take a deep breath, clear my head, and go back to edit out my inflammatory remarks before posting it. > Passion can do that! > I love D. Love all your good work guys!!! [...] Me too. D is the closest to what I've always wanted in an ideal programming language. It's not perfect, and its implementation still has a ways to go, but it already far surpasses all other languages that I've looked at in terms of what I consider important in an ideal language. I'm loving it so much I just can't convince myself to go back to C++ anymore in my personal projects, except for maintenance. (At work I just grit my teeth and comfort myself that they're paying me for it, so I'll tolerate C/C++. :P) T -- Без труда не выловишь и рыбку из пруда.
Re: Towards a better conceptual model of exceptions (Was: Re: The Right Approach to Exceptions)
On Thu, Feb 23, 2012 at 04:37:37AM +0100, Kapps wrote: [...] > The Condition approach you propose is fairly similar to what I was > thinking of, as an approach to fixing Problems that occur (not > necessarily exceptions). One of the fundamental problems I have with > exceptions is that, despite intended to be recoverable as opposed to > Errors, they are not recoverable. The operation still fails when an > exception is thrown, and you have to restart all over again (even if > it's something easily recoverable from). Exactly!! As soon as the stack unwinds, you've just left behind the very context that would have been the best position to fix problems from. In a properly-written program, the caller of the function treats the throwing function as a black box, so once the stack unwinds back to the caller, there's no more possibility of repairing the problem. The best you can do is to repeat the operation from scratch and hope it works this time. That's what I like so much about the Lisp approach. The high-level code registers handlers that are qualified to make high-level decisions, and the low-level code implements the low-level details of how to go about fixing the problem. > The other issue, is that exceptions are just poorly designed for > multithreaded and event based applications. If you queue some work to > be done, you likely won't even know if an exception is thrown. If you > have a callback / event based approach, you have to do the equivalent > of returning error codes (something exceptions were designed to > avoid), except with passing in error codes instead. An example is .NET > forcing all asynchronous operations to have a Begin and an End > with the End rethrowing whatever problems occurred during the > operation (even if they were easily solveable if the caller simply had > immediate access to handle them). I've thought about this too, though I don't currently know what's the right way to implement it. For an event-based program, Conditions aren't good enough, because high-level handlers are deregistered once the registering function's scope exits. This is to prevent unrelated functions from polluting each other's handler stack, with handlers from one function catching stuff it shouldn't from other code. But for an event-based program, you *need* persistent problem handlers. The question is how to prevent persistent handlers from getting problem notifications that they weren't designed to handle. One idea, which I haven't really thought through yet, is to introduce the concept of Domains. A Domain represents a particular operation carried out by some sequence of events and their respective callbacks. For instance, in a network app, a "Login" operation involves calling a Login function in the communications layer, with a callback to notify when the login process is completed. The Login function in turn calls the Connection layer to connect to the remote server, with a callback to inform the Login function when the server is connected. This callback in turn calls the SendMsg function to initiate the authentication process, with its respective callback, etc.. The sum total of all these components: the Login function, its callback, the Connection layer, its callback, etc.., comprise the Login domain. Domains may be nested; for example, the Connection layer may call the Socket layer and the Packet layer, all of which have their own set of callbacks. This "initiate connection" process constitutes a subdomain of the Login domain, and the "send packet" process constitutes a subdomain of the "initiate connection" domain. Each problem handler is then associated with a particular domain (and its subdomains). Whenever a problem occurs, the problem handling system identifies in which domain the problem happened, and searches up the domain hierarchy (starting from the lowest-level domain, like "send packet", up to "initiate connection" up to "Login") for problem handlers. The problem handler then can take the necessary actions to correct the problem. The Condition system can (and probably should) be adapted to this model too. For example if the "initiate connection" domain gets a timeout error from the packet layer, the best place to attempt retry is right there in the Connection layer. However, that's not the best place to decide whether or not to retry; it may be the case that a higher-level handler (i.e. handler higher up the domain hierarchy) is in a better position to make this decision. So somehow they need to be able to cooperate with each other. The details of how this can work still need to be worked out. > [...] > (Unfortunately, unless you have a global problem handler for the > entire transfer operation, this still suffers from the second issue > about how those exceptions do not get carried up to the caller that > starte the operation.) I think with the concept of Domains, we can solve this. Problems that are found in lower level domains bubble up the domain hierarchy un
Re: Towards a better conceptual model of exceptions (Was: Re: The Right Approach to Exceptions)
On Wednesday, 22 February 2012 at 07:30:54 UTC, H. S. Teoh wrote: I have an idea. What if handlers took *two* arguments, a Condition, and a (possibly derived) Exception object? The raise system would then match conditions to handlers by both the Condition type and derived Exception type. *snip* The Condition approach you propose is fairly similar to what I was thinking of, as an approach to fixing Problems that occur (not necessarily exceptions). One of the fundamental problems I have with exceptions is that, despite intended to be recoverable as opposed to Errors, they are not recoverable. The operation still fails when an exception is thrown, and you have to restart all over again (even if it's something easily recoverable from). The other issue, is that exceptions are just poorly designed for multithreaded and event based applications. If you queue some work to be done, you likely won't even know if an exception is thrown. If you have a callback / event based approach, you have to do the equivalent of returning error codes (something exceptions were designed to avoid), except with passing in error codes instead. An example is .NET forcing all asynchronous operations to have a Begin and an End with the End rethrowing whatever problems occurred during the operation (even if they were easily solveable if the caller simply had immediate access to handle them). The first one, is solveable with something similar to Conditions. A Problem would generally be a state issue. For example, you're writing a simple file transfer tool. The client authenticates with the server and gets a session ID. The server then starts transferring to the client, when suddenly the client loses connection for a moment (maybe they lost Wi-Fi signal, maybe someone unplugged their router momentarily, maybe they just had a bad route). With the exception based approach, the only way to notify this is to stop the entire operation, reconnect, reauthenticate, create a new session, negotiate what was being transfered, negotiate how far in the transfer completed, and other annoying things. Instead, we could register a handler to solve network problems. At the lowest level, the socket could have a handler that attempts to reconnect if any socket operations fail due to a connection error. Then, the transfer protocol could have a handler that, if the dependent socket reconnect handler is successful, notifies the server of the session key (and maybe the last packet it received) and carries on seamlessly without having to do a bunch of work. If the socket reconnect fails, the network problem handler reports a failure, and the transfer protocol handler does not get executed. If there is no transfer protocol handler, or there is one that basically says a solution is not possible, the same happens. Instead, that Problem is executed into an Exception, as the operation could not continue. (Unfortunately, unless you have a global problem handler for the entire transfer operation, this still suffers from the second issue about how those exceptions do not get carried up to the caller that starte the operation.) The transfer approach might not be the greatest example, but it demonstrates something that (IMO) Exceptions are misused for, which affects the design of Exceptions themselves as it attempts to hammer a solution they're not great for in with them.
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Thursday, 23 February 2012 at 02:12:20 UTC, Juan Manuel Cabo wrote: If we are going to get ideallistic [..] I'm sorry. I went over the top. I apollogize. ..I won't post for a while. This thread is almost poping a vein in my neck.. Passion can do that! I love D. Love all your good work guys!!! --jm
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Wednesday, February 22, 2012 20:07:37 Robert Jacques wrote: > StringBuilder in .Net is implemented using lists and doesn't expose > iteration nor indexing; why are we worrying about the indexing and > container performance of D's appender?l Because we're losing something that we currently have and which is potentially useful. Now, improving the performance of appending may outweigh that, but if we don't need to lose it, then we shouldn't. >From the sounds of it though, we don't have much choice if we want to really improve Appender's appending performance. - Jonathan M Davis
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Thursday, 23 February 2012 at 01:57:49 UTC, Jonathan M Davis wrote: The D equivalent would really be Array, not Appender. Array!T in D is ref counted and more geared towards T being a struct. And I had big trouble sorting it with sort!()() in D2.056, so I made my own sort just to be able to use Array!(T). I know that that situation will not remain forever (and I didn't check if it was already fixed in D2.058). I'm not sure that it's a great idea to use Appender as a container - particularly when there are types specifically intended to be used as containers. Appender is geared specifically towards array building (like StringBuilder in Java, except generalized for all arrays). If it's a container that you're looking for, then I really think that you should use a container. - Jonathan M Davis If Appender supports the range interface, then it is a container. Someone will use it that way, because in the real world people take the things for what they are, not for what they are named. Appender can work with T classes well. Contains items. It would be GC managed (as opposed to Array!T). So it is a container. If it is not O(1) to access, it should be said big in the ddoc though. It is a recurrent trend in your posts, that you post just because you have some ideallistic concern or opinion. If we are going to get ideallistic, this is an extract of a poem by Jorge Luis Borges (argentinian writer) that illustrates my point: If (as the Greek states in the Cratilo) the name is the archetype of the thing, in the letters of rose it is the rose and all the Nile is in the word Nile Si como escribió el griego en el Crátilo, el nombre es arquetipo de la cosa, en el nombre de rosa está la rosa y todo el Nilo en la palabra Nilo. --Jorge Luis Borges --jm
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Wed, 22 Feb 2012 19:57:37 -0600, Jonathan M Davis wrote: On Thursday, February 23, 2012 02:36:31 Juan Manuel Cabo wrote: Yeah, but I don't care about the underlying array. I care about multiple places referencing the same Appender. If I from any place that references it, it appends to the same appender. The Appender "array" has identity. Ranges do not: int[] bla = [1,2,3]; int[] ble = bla; ble ~= 4; assert(bla.length == 3); This is very easy to solve with appender. This is what happens in Java: ArrayList bla = new ArrayList(); bla.add(1); ArrayList ble = bla; ble.add(2); //prints 2 System.out.println(Integer.toString(bla.size())); //prints 2 System.out.println(Integer.toString(ble.size())); (yikes, aint that verbose!) The ArrayList has identity. It is a class, so that it many variables reference the _same_ object. (this can be accomplished with structs too though, but not with ranges). The D equivalent would really be Array, not Appender. I'm not sure that it's a great idea to use Appender as a container - particularly when there are types specifically intended to be used as containers. Appender is geared specifically towards array building (like StringBuilder in Java, except generalized for all arrays). If it's a container that you're looking for, then I really think that you should use a container. - Jonathan M Davis StringBuilder in .Net is implemented using lists and doesn't expose iteration nor indexing; why are we worrying about the indexing and container performance of D's appender?
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Thursday, 23 February 2012 at 01:36:32 UTC, Juan Manuel Cabo wrote: On Thursday, 23 February 2012 at 00:51:38 UTC, Jonathan M Davis wrote: [...] If appender ends up with multiple arrays in it, then random access is no longer O(1) and is therefore unacceptable. As such, most sort algorithms wouldn't work with it. If all I want is binary search on a big appender, then it is O(k * n * log(n)), and that k right there doesn't bother me. Also, binary search is absolutely not cpu cache friendly to begin with. Also, your bit about using appender to pass an array around wouldn't work either, because it wouldn't simply be wrapper around an array anymore. - Jonathan M Davis Yeah, but I don't care about the underlying array. I care about multiple places referencing the same Appender. If I from any place that references it, it appends to the same appender. The Appender "array" has identity. Ranges do not: int[] bla = [1,2,3]; int[] ble = bla; ble ~= 4; assert(bla.length == 3); This is very easy to solve with appender. This is what happens in Java: ArrayList bla = new ArrayList(); bla.add(1); ArrayList ble = bla; ble.add(2); //prints 2 System.out.println(Integer.toString(bla.size())); //prints 2 System.out.println(Integer.toString(ble.size())); (yikes, aint that verbose!) The ArrayList has identity. It is a class, so that it many variables reference the _same_ object. (this can be accomplished with structs too though, but not with ranges). I meant ref counted structs. P.S. Please don't top post. Replies should go _after_ the preceding message. Sorry, got it. --jm
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Thursday, February 23, 2012 02:36:31 Juan Manuel Cabo wrote: > Yeah, but I don't care about the underlying array. I care > about multiple places referencing the same Appender. If I > from any place that references it, it appends to the same > appender. The Appender "array" has identity. Ranges do not: > > int[] bla = [1,2,3]; > int[] ble = bla; > ble ~= 4; > assert(bla.length == 3); > > This is very easy to solve with appender. > This is what happens in Java: > ArrayList bla = new ArrayList(); > bla.add(1); > ArrayList ble = bla; > ble.add(2); > //prints 2 > System.out.println(Integer.toString(bla.size())); > //prints 2 > System.out.println(Integer.toString(ble.size())); > > (yikes, aint that verbose!) > The ArrayList has identity. It is a class, so that it > many variables reference the _same_ object. > (this can be accomplished with structs too though, but > not with ranges). The D equivalent would really be Array, not Appender. I'm not sure that it's a great idea to use Appender as a container - particularly when there are types specifically intended to be used as containers. Appender is geared specifically towards array building (like StringBuilder in Java, except generalized for all arrays). If it's a container that you're looking for, then I really think that you should use a container. - Jonathan M Davis
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Thursday, 23 February 2012 at 00:51:38 UTC, Jonathan M Davis wrote: P.S. Please don't top post. Replies should go _after_ the preceding message. P.S: You are right though, that it wouldn't be O(1) anymore and it should be said big in the documentation that it is amortized. --jm
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Thursday, 23 February 2012 at 01:36:32 UTC, Juan Manuel Cabo wrote: If all I want is binary search on a big appender, then it is O(k * n * log(n)), and that k right there doesn't bother me. (Where binary search is of course O(log(n)) and accessing individual elements with the proposed Appender is O(N / (4080/T.sizeof)), so k == 4080/T.sizeof) --jm
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Thursday, 23 February 2012 at 00:51:38 UTC, Jonathan M Davis wrote: [...] If appender ends up with multiple arrays in it, then random access is no longer O(1) and is therefore unacceptable. As such, most sort algorithms wouldn't work with it. If all I want is binary search on a big appender, then it is O(k * n * log(n)), and that k right there doesn't bother me. Also, binary search is absolutely not cpu cache friendly to begin with. Also, your bit about using appender to pass an array around wouldn't work either, because it wouldn't simply be wrapper around an array anymore. - Jonathan M Davis Yeah, but I don't care about the underlying array. I care about multiple places referencing the same Appender. If I from any place that references it, it appends to the same appender. The Appender "array" has identity. Ranges do not: int[] bla = [1,2,3]; int[] ble = bla; ble ~= 4; assert(bla.length == 3); This is very easy to solve with appender. This is what happens in Java: ArrayList bla = new ArrayList(); bla.add(1); ArrayList ble = bla; ble.add(2); //prints 2 System.out.println(Integer.toString(bla.size())); //prints 2 System.out.println(Integer.toString(ble.size())); (yikes, aint that verbose!) The ArrayList has identity. It is a class, so that it many variables reference the _same_ object. (this can be accomplished with structs too though, but not with ranges). P.S. Please don't top post. Replies should go _after_ the preceding message. Sorry, got it. --jm
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Wed, 22 Feb 2012 18:51:27 -0600, Jonathan M Davis wrote: On Thursday, February 23, 2012 01:38:05 Juan Manuel Cabo wrote: (And not talking about some cheesy insertion sort!!) If you build an array once and for all, and all you want is to do binary search on it later, it doesn't make sense to allocate that big contiguous .data. I'd rather leave it as an appender. --jm On Wednesday, 22 February 2012 at 23:22:35 UTC, Juan Manuel Cabo wrote: >> No, because the array doesn't actually exist until appender >> makes copy. > > Will one be able to use the sort!()() algorithm directly on > your appender, > that is, without accessing/creating the underlying array? If appender ends up with multiple arrays in it, then random access is no longer O(1) and is therefore unacceptable. As such, most sort algorithms wouldn't work with it. Also, your bit about using appender to pass an array around wouldn't work either, because it wouldn't simply be wrapper around an array anymore. - Jonathan M Davis P.S. Please don't top post. Replies should go _after_ the preceding message. Well, a VList (which is a list of arrays) is has O(1) amortized indexing. Actual performance of my implementation would be O(N / (4080/T.sizeof)), which isn't so bad. And anything under a page of ram would be O(1).
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Wed, Feb 22, 2012 at 07:51:27PM -0500, Jonathan M Davis wrote: [...] > P.S. Please don't top post. Replies should go _after_ the preceding message. Answer: Because it breaks the normal flow of conversation. Question: Why is it bad to top-post? T -- Why waste time learning, when ignorance is instantaneous? -- Hobbes, from Calvin & Hobbes
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Thursday, February 23, 2012 01:38:05 Juan Manuel Cabo wrote: > (And not talking about some cheesy insertion sort!!) > > If you build an array once and for all, and all you want > is to do binary search on it later, it doesn't make sense to > allocate that big contiguous .data. I'd rather leave it > as an appender. > > --jm > > > On Wednesday, 22 February 2012 at 23:22:35 UTC, Juan Manuel Cabo > > wrote: > >> No, because the array doesn't actually exist until appender > >> makes copy. > > > > Will one be able to use the sort!()() algorithm directly on > > your appender, > > that is, without accessing/creating the underlying array? If appender ends up with multiple arrays in it, then random access is no longer O(1) and is therefore unacceptable. As such, most sort algorithms wouldn't work with it. Also, your bit about using appender to pass an array around wouldn't work either, because it wouldn't simply be wrapper around an array anymore. - Jonathan M Davis P.S. Please don't top post. Replies should go _after_ the preceding message.
Re: new std.variant (was Re: The Right Approach to Exceptions)
(And not talking about some cheesy insertion sort!!) If you build an array once and for all, and all you want is to do binary search on it later, it doesn't make sense to allocate that big contiguous .data. I'd rather leave it as an appender. --jm On Wednesday, 22 February 2012 at 23:22:35 UTC, Juan Manuel Cabo wrote: No, because the array doesn't actually exist until appender makes copy. Will one be able to use the sort!()() algorithm directly on your appender, that is, without accessing/creating the underlying array? --jm
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Wednesday, 22 February 2012 at 20:59:15 UTC, Jonathan M Davis wrote: speed [...] is really its whole point of existance. I don't know why else you'd ever use appender. [...] - Jonathan M Davis A use case is to give identity to a built-in array. Consider this: class MyClass { private MyData[] theData; public @property MyData[] data() { return theData; } ... } MyClass m = new MyClass(); m.data ~= new MyData(); //Nothing got appended: assert(m.data.length == 0); For the 95% of the use cases, that is the desired behaviour. You don't want anyone appending to your private array. If you wanted to, you would have defined MyClass.append(myData). But there are a few cases where you want to give identity to the array, and let anyone who has a "handle" to it, to be able to append it. (another case is while porting code from languages that don't represent arrays as ranges, and return them as getters). --jm
Re: new std.variant (was Re: The Right Approach to Exceptions)
No, because the array doesn't actually exist until appender makes copy. Will one be able to use the sort!()() algorithm directly on your appender, that is, without accessing/creating the underlying array? --jm
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Wed, 22 Feb 2012 14:17:09 -0600, Jonathan M Davis wrote: On Wednesday, February 22, 2012 14:12:07 Jonathan M Davis wrote: On Wednesday, February 22, 2012 12:16:43 Robert Jacques wrote: > There's a big difference between sealed and not accessible. .data's API > requires exposing an array, and there's no way to do this without leaking > memory like a sieve in one way or another. However, if all you need is to > iterate the contents, that's easy to do. I'm currently adding backwards > iteration. Even indexing is fairly efficient (not yet implemented), > particularly relative indexing (i.e. n from back or front). > > I haven't seen too many use cases yet where accessing the underlying array > is important, nor has it come up on bugzilla. I've found one case in > Phobos where appender was used as a stack. What's your example? What > features does it have to support and how efficient does it have to be? It's can be useful to just get at the underlying array and pass it to functions which are going to use it but not alter it (or at least not append to it). Iterating over it doesn't give you an array. And since appender's entire purpose is simply to make appending to an array more efficient, making it impossible to treat it as one until you're done appending is overly restrictive IMHO. Yes, if you leak references to the underlying data, you're asking for trouble, but that doesn't mean that it can't be used without leaking memory. Unfortunately, I don't have any code snippets with me at the moment, so I can't give any concrete examples of usage, but any situation where you want to be able to operate on the array while building it needs the ability to get at the underlying array. Yes, in most cases, you're probably simply appending to the array, but at least once in a while, you need to operate on an array while building it. Also, wouldn't it be less efficient if you _had_ to copy the array once you were done with the appender? That would seem to go against what appender is trying to do. - Jonathan M Davis No, because the array doesn't actually exist until appender makes copy. Internally, appender is using a list of arrays to store the data (similar to a VList and string builders from other languages). So it's little o(2N) for both memory and speed; the current appender is much worse than that. In terms of actual performance, on a clean machine I'm substantially faster for N < 4k (thanks to a free list), about the same for things of a few k in size, and then as things get bigger the current appender tanks.
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Wednesday, February 22, 2012 14:24:49 Robert Jacques wrote: > I view appender's purpose as array building, which is slightly different > from simply speeding up array appending. Simply put, an array is a > terrible data structure for building arrays. But, I can appreciate the > need for mutation and if a particular array building algorithm can't be > performed on appender, then appender has failed. Would exposing a > bidirectional array be sufficient for your usages? A random access range? Well, as long as you don't have access to the actual array, you're going to lose something. There are functions that you just won't be able to use because they take an array. However, a range with essentially the same properties as a range (bidirectional, random access, etc.) would cover a large number of the functions that you might want to call. And if you need to hide the array for speed for efficiency for some reason - especially if it results in a large increase in speed - then that could at least partially outweigh the cost of losing the array (especially if Appender has an API to at least use it as a range). But I'd definitely be against hiding it simply for safety, since speed is really its whole point of existance. I don't know why else you'd ever use appender. So, I don't really like the idea of losing access to the underlying array, but if you can provide at least make it so that you could use it with range-based functions, and the changes provides a sigificant speed improvement, then the need for speed arguably outweighs the loss of being able to use the internal array directly. - Jonathan M Davis
Re: The Right Approach to Exceptions
Le 22/02/2012 18:50, H. S. Teoh a écrit : On Wed, Feb 22, 2012 at 11:53:39AM +0100, deadalnix wrote: [...] Additionnaly, I would mention that the transient isn't a caracteristic of the Exception, but of the recovery strategy. Technically correct. Though I'm playing with the idea of making recovery strategies a property of an exception - since a recovery strategy is meaningless without an associated exception (or problem). I need to think this through a bit more, though, as to how to correctly implement this. I did though about this. This isn't the right way. Recovery strategy doesn't have any meaning at the catch point, so we shouldn't make it a property of Exception. And sometime you don't care about the Exception. If you try to connect something that is know to fail for exemple, you really don't want to know what went wrong. You just want to try again with some backoff. I do think you made a point with the handler getting an Exception and a recovery stratgy as parameter, but it is still unclear where all this goes to me.
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Wed, 22 Feb 2012 13:12:07 -0600, Jonathan M Davis wrote: On Wednesday, February 22, 2012 12:16:43 Robert Jacques wrote: There's a big difference between sealed and not accessible. .data's API requires exposing an array, and there's no way to do this without leaking memory like a sieve in one way or another. However, if all you need is to iterate the contents, that's easy to do. I'm currently adding backwards iteration. Even indexing is fairly efficient (not yet implemented), particularly relative indexing (i.e. n from back or front). I haven't seen too many use cases yet where accessing the underlying array is important, nor has it come up on bugzilla. I've found one case in Phobos where appender was used as a stack. What's your example? What features does it have to support and how efficient does it have to be? It's can be useful to just get at the underlying array and pass it to functions which are going to use it but not alter it (or at least not append to it). Iterating over it doesn't give you an array. And since appender's entire purpose is simply to make appending to an array more efficient, making it impossible to treat it as one until you're done appending is overly restrictive IMHO. Yes, if you leak references to the underlying data, you're asking for trouble, but that doesn't mean that it can't be used without leaking memory. Unfortunately, I don't have any code snippets with me at the moment, so I can't give any concrete examples of usage, but any situation where you want to be able to operate on the array while building it needs the ability to get at the underlying array. Yes, in most cases, you're probably simply appending to the array, but at least once in a while, you need to operate on an array while building it. - Jonathan M Davis I view appender's purpose as array building, which is slightly different from simply speeding up array appending. Simply put, an array is a terrible data structure for building arrays. But, I can appreciate the need for mutation and if a particular array building algorithm can't be performed on appender, then appender has failed. Would exposing a bidirectional array be sufficient for your usages? A random access range?
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Wednesday, February 22, 2012 14:12:07 Jonathan M Davis wrote: > On Wednesday, February 22, 2012 12:16:43 Robert Jacques wrote: > > There's a big difference between sealed and not accessible. .data's API > > requires exposing an array, and there's no way to do this without leaking > > memory like a sieve in one way or another. However, if all you need is to > > iterate the contents, that's easy to do. I'm currently adding backwards > > iteration. Even indexing is fairly efficient (not yet implemented), > > particularly relative indexing (i.e. n from back or front). > > > > I haven't seen too many use cases yet where accessing the underlying array > > is important, nor has it come up on bugzilla. I've found one case in > > Phobos where appender was used as a stack. What's your example? What > > features does it have to support and how efficient does it have to be? > > It's can be useful to just get at the underlying array and pass it to > functions which are going to use it but not alter it (or at least not append > to it). Iterating over it doesn't give you an array. And since appender's > entire purpose is simply to make appending to an array more efficient, > making it impossible to treat it as one until you're done appending is > overly restrictive IMHO. Yes, if you leak references to the underlying > data, you're asking for trouble, but that doesn't mean that it can't be > used without leaking memory. > > Unfortunately, I don't have any code snippets with me at the moment, so I > can't give any concrete examples of usage, but any situation where you want > to be able to operate on the array while building it needs the ability to > get at the underlying array. Yes, in most cases, you're probably simply > appending to the array, but at least once in a while, you need to operate > on an array while building it. Also, wouldn't it be less efficient if you _had_ to copy the array once you were done with the appender? That would seem to go against what appender is trying to do. - Jonathan M Davis
Re: The Right Approach to Exceptions
On 2012-02-22 15:01, Andrei Alexandrescu wrote: On 2/22/12 1:22 AM, Jacob Carlborg wrote: Now I'm completely lost. According to what I've read this is thread this is exactly what you want to do, put the formatting inside the exceptions. No, just have exceptions inform an external formatter. Andrei Ok, I see. -- /Jacob Carlborg
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Wednesday, February 22, 2012 12:16:43 Robert Jacques wrote: > There's a big difference between sealed and not accessible. .data's API > requires exposing an array, and there's no way to do this without leaking > memory like a sieve in one way or another. However, if all you need is to > iterate the contents, that's easy to do. I'm currently adding backwards > iteration. Even indexing is fairly efficient (not yet implemented), > particularly relative indexing (i.e. n from back or front). > > I haven't seen too many use cases yet where accessing the underlying array > is important, nor has it come up on bugzilla. I've found one case in > Phobos where appender was used as a stack. What's your example? What > features does it have to support and how efficient does it have to be? It's can be useful to just get at the underlying array and pass it to functions which are going to use it but not alter it (or at least not append to it). Iterating over it doesn't give you an array. And since appender's entire purpose is simply to make appending to an array more efficient, making it impossible to treat it as one until you're done appending is overly restrictive IMHO. Yes, if you leak references to the underlying data, you're asking for trouble, but that doesn't mean that it can't be used without leaking memory. Unfortunately, I don't have any code snippets with me at the moment, so I can't give any concrete examples of usage, but any situation where you want to be able to operate on the array while building it needs the ability to get at the underlying array. Yes, in most cases, you're probably simply appending to the array, but at least once in a while, you need to operate on an array while building it. - Jonathan M Davis
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Wed, 22 Feb 2012 11:33:57 -0600, Jonathan M Davis wrote: On Wednesday, February 22, 2012 08:19:38 Robert Jacques wrote: To Variant? Yes, definitely. To Appender? I don't think so. There is an slight change in API behavior necessitated by performance considerations, but I don't think it warrants a review by the community at large. Specifically, appender's internal data structure is now sealed, which means that .data must always make a copy. My preference would be to deprecate .data in favor of a .dup/.idup pair. It'll break a bunch of code (which I don't like), but it will make sure no one is calling .data twice in a row, resulting in a silent performance problem. I've definitely written code that needed to get at data while appending. I would consider it to be a huge downside to not be able to efficiently get at the current state of the array within the appender. Why on earth would you seal it? - Jonathan M Davis There's a big difference between sealed and not accessible. .data's API requires exposing an array, and there's no way to do this without leaking memory like a sieve in one way or another. However, if all you need is to iterate the contents, that's easy to do. I'm currently adding backwards iteration. Even indexing is fairly efficient (not yet implemented), particularly relative indexing (i.e. n from back or front). I haven't seen too many use cases yet where accessing the underlying array is important, nor has it come up on bugzilla. I've found one case in Phobos where appender was used as a stack. What's your example? What features does it have to support and how efficient does it have to be?
Re: Towards a better conceptual model of exceptions (Was: Re: The Right Approach to Exceptions)
On Wed, Feb 22, 2012 at 11:14:11AM +0100, deadalnix wrote: > Le 22/02/2012 08:32, H. S. Teoh a écrit : [...] > >I have an idea. What if handlers took *two* arguments, a Condition, > >and a (possibly derived) Exception object? The raise system would > >then match conditions to handlers by both the Condition type and > >derived Exception type. The handler then has access to the particular > >derived object that it wants to handle without needing to downcast. > >So it sorta simulates a catch(DerivedException). > > > Well I have no idea how to implement that. Let's put some thinking > into this, it certainly an option. However, this require to create > the Exception, even if in many cases, it will not be usefull. I was thinking about using TypeInfo to match stuff up at runtime. But I need to work through the details first to see if it's actually implementable. This seems to be an area where language support would help a lot. Though it's better if we can do it without language support, so that we can get this thing up and running and test it with real-life usage before making language changes that may not benefit in the long run. :) > About the name, it matters, but isn't the big issue here. When we have > something working well, we could think about names. As the concept may > be changed again, naming isn't that important. Names are just identifiers, sure, but it does help to have a useful name to help us think about the problem from a useful angle. For example if we renamed Exception to Bug, the mechanics of the it (try, throw, catch) would still work the same, but we would be likely to use it in the wrong ways because "Bug" doesn't help us think about it in a useful way. T -- Why ask rhetorical questions? -- JC
Re: The Right Approach to Exceptions
On Wed, Feb 22, 2012 at 11:53:39AM +0100, deadalnix wrote: [...] > Additionnaly, I would mention that the transient isn't a caracteristic > of the Exception, but of the recovery strategy. Technically correct. Though I'm playing with the idea of making recovery strategies a property of an exception - since a recovery strategy is meaningless without an associated exception (or problem). I need to think this through a bit more, though, as to how to correctly implement this. T -- The volume of a pizza of thickness a and radius z can be described by the following formula: pi zz a. -- Wouter Verhelst
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Wednesday, February 22, 2012 08:19:38 Robert Jacques wrote: > To Variant? Yes, definitely. To Appender? I don't think so. There is an > slight change in API behavior necessitated by performance considerations, > but I don't think it warrants a review by the community at large. > Specifically, appender's internal data structure is now sealed, which means > that .data must always make a copy. My preference would be to deprecate > .data in favor of a .dup/.idup pair. It'll break a bunch of code (which I > don't like), but it will make sure no one is calling .data twice in a row, > resulting in a silent performance problem. I've definitely written code that needed to get at data while appending. I would consider it to be a huge downside to not be able to efficiently get at the current state of the array within the appender. Why on earth would you seal it? - Jonathan M Davis
Re: The Right Approach to Exceptions
On Tue, 21 Feb 2012 14:19:17 -, Andrei Alexandrescu wrote: On 2/21/12 5:55 AM, Regan Heath wrote: On Sun, 19 Feb 2012 23:04:59 -, Andrei Alexandrescu wrote: On 2/19/12 4:00 PM, Nick Sabalausky wrote: Seriously, how is this not *already* crystal-clear? I feel as if every few weeks you're just coming up with deliberately random shit to argue so the rest of us have to waste our time spelling out the obvious in insanely pedantic detail. It sometimes happened to me to be reach the hypothesis that my interlocutor must be some idiot. Most often I was missing something. I get the impression that you find "Devil's advocate" a useful tool for generating debate and out of the box thinking.. there is something to be said for that, but it's probably less annoying to some if you're clear about that from the beginning. :p Where did it seem I was playing devil's advocate? Thanks. "Devil's Advocate" is perhaps not the right term, as you don't seem to ever argue the opposite to what you believe. But, it occasionally seems to me that you imply ignorance on your part, in order to draw more information from other posters on exactly what they think or are proposing. So, some get frustrated as they feel they have to explain "everything" to you (and not just you, there have been times where - for whatever reason - it seems that anything less than a description of every single minute detail results in a miss understanding - no doubt partly due to the medium in which we are communicating). Regan -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Re: The Right Approach to Exceptions
Le 22/02/2012 16:33, H. S. Teoh a écrit : On Wed, Feb 22, 2012 at 11:18:13AM +0100, deadalnix wrote: Le 21/02/2012 20:01, H. S. Teoh a écrit : [...] Does RTTI handle template methods? I'm not aware of any language that have both template and RTTI. So I'm clueless about this. Doesn't C++ have RTTI? You could argue that, but IMO, it look more like a stub than a real functionnality of the language.
Re: The Right Approach to Exceptions
On Wed, Feb 22, 2012 at 11:18:13AM +0100, deadalnix wrote: > Le 21/02/2012 20:01, H. S. Teoh a écrit : [...] > >Does RTTI handle template methods? > > > > I'm not aware of any language that have both template and RTTI. So I'm > clueless about this. Doesn't C++ have RTTI? T -- "The number you have dialed is imaginary. Please rotate your phone 90 degrees and try again."
Re: The Right Approach to Exceptions
On Tue, Feb 21, 2012 at 6:32 PM, deadalnix wrote: > Le 21/02/2012 00:23, Andrei Alexandrescu a écrit : > >> On 2/20/12 4:44 PM, Juan Manuel Cabo wrote: >>> >>> HAhaha, it sometimes feel as though people are afraid that the >>> Variant[string] >>> idea is to never use plain old variables and never use exception >>> subclasses. :-) >>> >>> On the contrary, the idea is so that plain old variables and exception >>> subclasses >>> can be created for the right reasons, and to remove cases where they need >>> to be created for the wrong reasons. >> >> >> Yah, I think there's a lot of confusion and consequent apprehension >> regarding this. Thanks for attempting to clarify things. >> >> Andrei >> > > So it doesn't help. Dulb subclasses of Exceptions are done mostly to be able > to catch them. To avoid useless subclasses, we need a more precise way to > catch Exception than the type only. > Agreed. I would love it if catch worked using a structural/pattern matching mechanism. Something like template's if clause or like Scala pattern matching... -Jose > This Variant[string] doesn't help.
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Tue, 21 Feb 2012 21:51:34 -0600, Andrei Alexandrescu wrote: On 2/21/12 6:11 PM, Robert Jacques wrote: On Tue, 21 Feb 2012 09:12:57 -0600, Adam D. Ruppe wrote: On Tuesday, 21 February 2012 at 02:33:15 UTC, Robert Jacques wrote: Nope. See (https://jshare.johnshopkins.edu/rjacque2/public_html/ ) Any luck in getting the required patches into phobos? I'd love to see this full thing in there for the next release. It rox. I'm in the process of cleaning up the required patches and submitting them. The first one's already in (https://github.com/sandford/phobos/commit/8b845d2a50bc20993afed7306f3d84921e4ac54b). I've almost got appender ready. Are the changes substantial enough to put them through the review process? Andrei To Variant? Yes, definitely. To Appender? I don't think so. There is an slight change in API behavior necessitated by performance considerations, but I don't think it warrants a review by the community at large. Specifically, appender's internal data structure is now sealed, which means that .data must always make a copy. My preference would be to deprecate .data in favor of a .dup/.idup pair. It'll break a bunch of code (which I don't like), but it will make sure no one is calling .data twice in a row, resulting in a silent performance problem.
Re: The Right Approach to Exceptions
On 2/22/12 1:22 AM, Jacob Carlborg wrote: Now I'm completely lost. According to what I've read this is thread this is exactly what you want to do, put the formatting inside the exceptions. No, just have exceptions inform an external formatter. Andrei
Re: The Right Approach to Exceptions
Le 22/02/2012 06:47, H. S. Teoh a écrit : On Tue, Feb 21, 2012 at 07:43:32PM -0500, Jonathan M Davis wrote: On Tuesday, February 21, 2012 14:15:03 Andrei Alexandrescu wrote: I thought I was pushing the generics angle, and OO people explained it to me that that was wrong. I've changed my mind. Now I'm trying to see if the generics angle has some possibilities. Maybe, maybe not, but we'll never know without experimenting with it. I think your is_transient idea can be expanded upon. The way exceptions are currently implemented, they only carry information, not behaviour, as Jonathan said rightly. The try/catch mechanism essentially reduces to "I've hit a problem I don't know how to solve, here's a description of it". There's no behaviour in there. The throwing code has already given up. It's up to the catcher to interpret the description of the problem and figure out how to recover. To recover well, the catcher must know the intimate details of the problem well. So you have the situation of a specific catcher catching a specific Exception subclass. This is not an ideal situation, because now high-level code needs to know the specifics of low-level errors. With your is_transient idea, though, this begins to change. Now we're no longer just describing the problem. When is_transient=1, it means the thrower is suggesting that perhaps retrying would help. Of course, it's up to the catcher whether or not to follow through with this suggestion, but it's one step up from "here's a description of the problem, figure out the solution yourself". But now the catcher doesn't necessarily have to know the specifics of the low-level problem. It knows at least one strategy that might fix the problem, regardless of what the problem is: retry the operation. This is good, because the low-level code, which knows the problem best, can offer a useful suggestion (retry). The high-level code can just take the suggestion or not; it no longer needs to know low-level details. But why stop there? Since the low-level code knows all the dirty details about the problem, it's in the best position to offer meaningful recovery suggestions. It just has to communicate these possible recovery strategies to the high-level code, and let the high-level code decide what to do. The high-level code doesn't need to know how to implement these strategies -- it's not in the best position to know that anyway. It just knows, here's a list of recovery strategies, I can go ahead with one of them, or just call it quits and unwind the stack. The low-level code is what implements each strategy. Of course, in order for the high-level code to meaningfully choose between alternative strategies, the strategies themselves must be generic concepts; otherwise we're still tying high-level code to low-level details. So we need to identify generic categories of exceptions for which this kind of generic recovery is meaningful -- which is what I've done in another post. I won't repeat the details here, but I just want to say that I think this angle merits some investigation. It allows us to factor out exceptions which can be resolved by commonly used recovery strategies so that we don't have to keep writing tons and tons of exception-specific recovery code everywhere. Some specific code is still needed, no doubt, there's always special cases that need specific handling. But if enough exceptions can be adequately dealt with generically, then we don't need to write specific code for them. We can simply reuse generic recovery solutions. T 100% Agree. Additionnaly, I would mention that the transient isn't a caracteristic of the Exception, but of the recovery strategy.
Re: The Right Approach to Exceptions
Le 21/02/2012 20:01, H. S. Teoh a écrit : On Tue, Feb 21, 2012 at 07:57:37PM +0100, deadalnix wrote: Le 21/02/2012 03:33, Robert Jacques a écrit : [...] Aren't __traits and opDispatch fun? opDispatch is nice, but rather incomplete. It doesn't handle template methods for example. Does RTTI handle template methods? I'm not aware of any language that have both template and RTTI. So I'm clueless about this.
Re: Towards a better conceptual model of exceptions (Was: Re: The Right Approach to Exceptions)
Le 22/02/2012 08:32, H. S. Teoh a écrit : On Tue, Feb 21, 2012 at 08:56:03PM +0100, deadalnix wrote: Le 21/02/2012 20:00, H. S. Teoh a écrit : [...] You're right, that would be unnecessary duplication, especially since an unhandled Condition becomes a thrown Exception anyway, and it's a very bad idea to duplicate the entire Exception hierarchy in Condition. Only thing is, then the handler will have to downcast the Exception to get to the useful info. This may lead to messy code. But it's something we should investigate. Yes, I'm aware of the problem and it is real. I have no good solution for it now. I have an idea. What if handlers took *two* arguments, a Condition, and a (possibly derived) Exception object? The raise system would then match conditions to handlers by both the Condition type and derived Exception type. The handler then has access to the particular derived object that it wants to handle without needing to downcast. So it sorta simulates a catch(DerivedException). Well I have no idea how to implement that. Let's put some thinking into this, it certainly an option. However, this require to create the Exception, even if in many cases, it will not be usefull. About the name, it matters, but isn't the big issue here. When we have something working well, we could think about names. As the concept may be changed again, naming isn't that important.
Re: The Right Approach to Exceptions
On 2012-02-22 08:33, Jonathan M Davis wrote: On Wednesday, February 22, 2012 08:22:21 Jacob Carlborg wrote: Now I'm completely lost. According to what I've read this is thread this is exactly what you want to do, put the formatting inside the exceptions. No. He wants to provide a way for an external function to generically generate strings according to the format that you want when you generate the string. So, some function would take a formatting string of some kind and then read the corresponding values form the Variant[string] in Exception and generate a string according to that format string. How exactly that works, I don't understand (something about a string template language), but that's the idea. So, while you could still use toString, there would be a way to generate strings formatted the way that _you_ want rather than how toString would do it - and to do it in a generic manner. As long as this doesn't mean using Variant[string] as the way to inject all of the extra data into exceptions and we still use an exception hierarchy with the appropriate data members in derived exceptions, then I don't really see that as a problem. The problem is if we then also get rid of the hierarchy and/or try and put all of the data is the Variant[string] and only in the Variant[string]. I agree. From the sounds of it, we have _some_ agreement to have an exception hierarchy with data members in derived classes where appropriate but to add the Variant[string] bit to Exception to enable the passing of other data that you might want but is not in the exception type normally as well as enable the fancy string formatting stuff that Andrei wants. But this thread is so long and complicated that I think that many of us are just confused. - Jonathan M Davis Ok, I see. -- /Jacob Carlborg
Re: The Right Approach to Exceptions
On Wednesday, February 22, 2012 08:22:21 Jacob Carlborg wrote: > Now I'm completely lost. According to what I've read this is thread this > is exactly what you want to do, put the formatting inside the exceptions. No. He wants to provide a way for an external function to generically generate strings according to the format that you want when you generate the string. So, some function would take a formatting string of some kind and then read the corresponding values form the Variant[string] in Exception and generate a string according to that format string. How exactly that works, I don't understand (something about a string template language), but that's the idea. So, while you could still use toString, there would be a way to generate strings formatted the way that _you_ want rather than how toString would do it - and to do it in a generic manner. As long as this doesn't mean using Variant[string] as the way to inject all of the extra data into exceptions and we still use an exception hierarchy with the appropriate data members in derived exceptions, then I don't really see that as a problem. The problem is if we then also get rid of the hierarchy and/or try and put all of the data is the Variant[string] and only in the Variant[string]. >From the sounds of it, we have _some_ agreement to have an exception hierarchy with data members in derived classes where appropriate but to add the Variant[string] bit to Exception to enable the passing of other data that you might want but is not in the exception type normally as well as enable the fancy string formatting stuff that Andrei wants. But this thread is so long and complicated that I think that many of us are just confused. - Jonathan M Davis
Re: Towards a better conceptual model of exceptions (Was: Re: The Right Approach to Exceptions)
On Tue, Feb 21, 2012 at 08:56:03PM +0100, deadalnix wrote: > Le 21/02/2012 20:00, H. S. Teoh a écrit : [...] > >You're right, that would be unnecessary duplication, especially since > >an unhandled Condition becomes a thrown Exception anyway, and it's a > >very bad idea to duplicate the entire Exception hierarchy in > >Condition. > > > >Only thing is, then the handler will have to downcast the Exception > >to get to the useful info. This may lead to messy code. But it's > >something we should investigate. > > > > Yes, I'm aware of the problem and it is real. I have no good solution > for it now. I have an idea. What if handlers took *two* arguments, a Condition, and a (possibly derived) Exception object? The raise system would then match conditions to handlers by both the Condition type and derived Exception type. The handler then has access to the particular derived object that it wants to handle without needing to downcast. So it sorta simulates a catch(DerivedException). It's a bit harder to implement, though... we'll need to store TypeInfo for each handler and match it up with the Condition being raised. But I'm assuming this isn't too much more than what the runtime is already doing with Exceptions anyway, so performance-wise it should be acceptable. And while we're at it, I think we should just stop pretending this is still the same system as Lisp, and just rename Condition to RecoveryStrategy or RecoveryOpts or something along those lines, since that's what it is. [...] > >To maximize usability and minimize redundancy and bloat, I'm thinking > >we should define categories based on what recovery actions are > >*actually available*, rather than what actions are *potentially* > >available. So to that end, it's not really categorization per se, but > >more of a way of describing what recovery strategies are actually > >available. > > > > Yes indeed. Exception should provide data about what the problem IS, > condition on what are option to recover. Yeah, I think it makes sense to rename Condition to RecoveryStrategy or RecoveryOpts. The recovery choices aren't a "condition"; the Exception object is the condition, the choices are recovery strategies. [...] > >Each operation should have a single condition with a well-defined set > >of recovery methods, not some arbitrary combination of multiple > >conditions. > > > >What do you think? > > > > It is exactly what lead me to create template for such cases. > Additionnaly, thoses templates should include a way to generate the > Exception, but I ran into a strangeness of D when exprimenting with > and did had time to come up with something working for that. The > condition handling and the actual operation must be separated to avoid > code duplication and ensure separation of concerns. Yep. > The current design allow to create new Conditions, in the lib but also > in user code. > > If we can find a way to handle properly the Exception crazy casting > problem we have here a very nice way to handle errors. Would it be possible to add that second argument to handlers? I think that will solve the problem. But it will take a bit more effort to implement. T -- "How are you doing?" "Doing what?"
Re: The Right Approach to Exceptions
On 2012-02-21 22:08, Andrei Alexandrescu wrote: On 2/21/12 2:42 PM, Jacob Carlborg wrote: On 2012-02-21 21:27, Andrei Alexandrescu wrote: On 2/21/12 2:26 PM, Jacob Carlborg wrote: As I said, it seems you want to push up implementation details specific to a given subclass to the base class even though it shouldn't be pushed up. I explained that doing so allows for proper formatting of error messages. So it should pushed up. Andrei Well, I don't think that is the right approach. As many others have explained, error messages are only a small part of exception handling. I agree. Also, one interface function is only a small part of a class hierarchy. If you do want to have a generic way of getting an error message out of an exception, what's wrong with toString? Or a new method that formats the error messages. No need to push up the instance variables to the base class. This has been answered in the long thread. In brief, toString loses too much information and putting formatting inside exceptions is not the right place. Andrei Now I'm completely lost. According to what I've read this is thread this is exactly what you want to do, put the formatting inside the exceptions. -- /Jacob Carlborg
Re: The Right Approach to Exceptions
On Tue, Feb 21, 2012 at 07:43:32PM -0500, Jonathan M Davis wrote: > On Tuesday, February 21, 2012 14:15:03 Andrei Alexandrescu wrote: > > I thought I was pushing the generics angle, and OO people explained > > it to me that that was wrong. I've changed my mind. Now I'm trying to see if the generics angle has some possibilities. Maybe, maybe not, but we'll never know without experimenting with it. I think your is_transient idea can be expanded upon. The way exceptions are currently implemented, they only carry information, not behaviour, as Jonathan said rightly. The try/catch mechanism essentially reduces to "I've hit a problem I don't know how to solve, here's a description of it". There's no behaviour in there. The throwing code has already given up. It's up to the catcher to interpret the description of the problem and figure out how to recover. To recover well, the catcher must know the intimate details of the problem well. So you have the situation of a specific catcher catching a specific Exception subclass. This is not an ideal situation, because now high-level code needs to know the specifics of low-level errors. With your is_transient idea, though, this begins to change. Now we're no longer just describing the problem. When is_transient=1, it means the thrower is suggesting that perhaps retrying would help. Of course, it's up to the catcher whether or not to follow through with this suggestion, but it's one step up from "here's a description of the problem, figure out the solution yourself". But now the catcher doesn't necessarily have to know the specifics of the low-level problem. It knows at least one strategy that might fix the problem, regardless of what the problem is: retry the operation. This is good, because the low-level code, which knows the problem best, can offer a useful suggestion (retry). The high-level code can just take the suggestion or not; it no longer needs to know low-level details. But why stop there? Since the low-level code knows all the dirty details about the problem, it's in the best position to offer meaningful recovery suggestions. It just has to communicate these possible recovery strategies to the high-level code, and let the high-level code decide what to do. The high-level code doesn't need to know how to implement these strategies -- it's not in the best position to know that anyway. It just knows, here's a list of recovery strategies, I can go ahead with one of them, or just call it quits and unwind the stack. The low-level code is what implements each strategy. Of course, in order for the high-level code to meaningfully choose between alternative strategies, the strategies themselves must be generic concepts; otherwise we're still tying high-level code to low-level details. So we need to identify generic categories of exceptions for which this kind of generic recovery is meaningful -- which is what I've done in another post. I won't repeat the details here, but I just want to say that I think this angle merits some investigation. It allows us to factor out exceptions which can be resolved by commonly used recovery strategies so that we don't have to keep writing tons and tons of exception-specific recovery code everywhere. Some specific code is still needed, no doubt, there's always special cases that need specific handling. But if enough exceptions can be adequately dealt with generically, then we don't need to write specific code for them. We can simply reuse generic recovery solutions. T -- Real Programmers use "cat > a.out".
Re: new std.variant (was Re: The Right Approach to Exceptions)
On 2/21/12 6:11 PM, Robert Jacques wrote: On Tue, 21 Feb 2012 09:12:57 -0600, Adam D. Ruppe wrote: On Tuesday, 21 February 2012 at 02:33:15 UTC, Robert Jacques wrote: Nope. See (https://jshare.johnshopkins.edu/rjacque2/public_html/ ) Any luck in getting the required patches into phobos? I'd love to see this full thing in there for the next release. It rox. I'm in the process of cleaning up the required patches and submitting them. The first one's already in (https://github.com/sandford/phobos/commit/8b845d2a50bc20993afed7306f3d84921e4ac54b). I've almost got appender ready. Are the changes substantial enough to put them through the review process? Andrei
Re: The Right Approach to Exceptions
On Tuesday, February 21, 2012 14:15:03 Andrei Alexandrescu wrote: > I thought I was pushing the generics angle, and OO people explained it > to me that that was wrong. You were just talking about applying OO policy to exceptions, which just doesn't make sense for most things, because they're just not polymorphic in terms of how they're handled. > I'm sorry, I was unable to derive information from this post. It's a > string of assertion without any backing. Just look at exceptions. You catch them by type and then use that specific type. You don't generally operate on them via polymorphic functions. Derived classes usually hold more data but do not change the behavior of any existing functions. Rather, you look at the type of the exception and the values of the member variables in that concrete type to decide how to handle the exception based on that information. That's not particularly OO or polymorphic at all. Yes, you can catch more generic exceptions instead of the concrete ones, but that doesn't generally mean that you end up calling virtual functions on the base class. Rather, it means that your code cares less about what exactly went wrong. You're still not likely to be calling a virtual function on the exception type, because exceptions carry information, not behavior. The one major exception to this is toString. Having generic error message generating capabilities is useful. And not even that needs to be polymorphic. With Exception, the return value of toString is generated from the msg property which was passed in via its constructor. And toString isn't something that you normally override with Exception. Rather, it's the msg argument which changes. At minimum, if you do override toString, you need to call the base class' toString or you'll lose the stack trace. It's just not really designed with overriding in mind. I really don't see how anyone can make much of an argument for exceptions being OO beyond the fact that they're objects given that handling them means doing the complete opposite of typical OO. With exceptions, it's how they're handled that changes from type to type, not their internal behavior, whereas OO focuses on changing the behavior of functions in derived classes. And with exceptions, you use the concrete types and not an abstract interface, whereas in OO, the idea is to use an abstract interface. So, I don't see much point in trying to force OO principles on exceptions. Trying to treat them more generically with regard to generating error messages makes some sense, but that's about it. And that still isn't particularly OO - especially when the proposed solution is to use Variant[string] rather than to do something with toString. But if you want to change the message formatting at the catch point (just like all of the other exception behavior is generally done at the catch point), you can't do it with toString (at least, not without changing the internal state of the exception before calling toString). OO just doesn't fit. - Jonathan M Davis
Re: new std.variant (was Re: The Right Approach to Exceptions)
On Tue, 21 Feb 2012 09:12:57 -0600, Adam D. Ruppe wrote: On Tuesday, 21 February 2012 at 02:33:15 UTC, Robert Jacques wrote: Nope. See (https://jshare.johnshopkins.edu/rjacque2/public_html/ ) Any luck in getting the required patches into phobos? I'd love to see this full thing in there for the next release. It rox. I'm in the process of cleaning up the required patches and submitting them. The first one's already in (https://github.com/sandford/phobos/commit/8b845d2a50bc20993afed7306f3d84921e4ac54b). I've almost got appender ready.
Re: The Right Approach to Exceptions
On Tue, 21 Feb 2012 23:29:32 +0100, H. S. Teoh wrote: On Tue, Feb 21, 2012 at 03:15:19PM -0600, Andrei Alexandrescu wrote: [...] A more debatable aspect of exceptions is the first-match rule in catch blocks. All of OOP goes with best match, except here. But then all code is together so the effect is small. Does it make sense to make it best-match? Or is that too risky since everyone expects it to be first-match? Catch statements that are not in best-match order are a compile time error, g++ does that as a warning too.
Re: Towards a better conceptual model of exceptions (Was: Re: The Right Approach to Exceptions)
On Tue, Feb 21, 2012 at 07:15:29PM +0100, Artur Skawina wrote: > On 02/21/12 17:56, H. S. Teoh wrote: [...] > I don't think something like this can reliably work - handling unknown > error conditions in code not expecting them is not a good idea. I'm not proposing we do this for *every* error. Only for those for which it makes sense. And the code *does* have to expect what it's handling: a particular category of problems, a category for which it makes sense to handle recovery in a generic way. I'm *not* proposing this: try {...} catch(Exception e) {// catch everything // generic code that magically handles everything } If we could do that, this thread would've been over after 3 posts. :) > After all, if the new error is of a similar nature as another one it > could have been mapped to that one, or handled internally. Which is what I'm trying to do with the categorization. Internal handling, of course, can and should be done in those cases where it's crystal clear how one should proceed. I'm addressing the case where it can't be handled internally (else why would it throw an exception / raise a condition in the first place?) > Note that with my scheme the delegates can eg call another delegate > provided in the exception from the lower level code - so things like > that are possible. It's just that i don't think it's a good idea for > low level code to use the exception mechanism to ask "Should I retry > this operation?". The code either knows what to do (whether retrying > makes sense) or could be provided with a predefined policy. The delegate is the predefined policy. Except that it's much more flexible than a global on/off setting. It has access to the registering function's scope, based on which it can make decisions based on the large-scale state of the program, which the low-level code doesn't (and shouldn't) know about. Multiple delegates can be registered for the same condition, and the most relevant one (the closest to the problem locus) takes priority. Each delegate can implement its own policy on how to deal with problems. The higher-level delegates only see problems that the lower-level delegates decide to pass on. In this way, lower-level delegates filter out stuff that they can handle on their own, only deferring to the higher-level delegates when they don't know how to proceed. Your higher-level handler won't be handling all sorts of trivial low-level problems, only the major issues that the lower-level handlers can't already handle. > If retrying occurs often during normal operation then throwing an > exception every time is probably not the best way to handle this. In fact, some experimental code that deadalnix & I are playing with currently implements retry without try/catch, except when we need to unwind the stack. So no performance hit there. > And if it's a rare event - this kind of error handling adds too much > overhead - programmer-wise, hence more bugs in the rarely executed > parts of the program and probably java-style > catch-everything-just-to-silence- -the-compiler situations, which are > then never properly fixed... [...] We're currently playing with using templates to generate the boilerplate stuff, so all you need to do is to write the template name and wrap a block around the code to be retried. I think that's acceptable overhead -- it's no worse than writing "try ... catch", and arguably far more powerful. T -- Trying to define yourself is like trying to bite your own teeth. -- Alan Watts
Re: The Right Approach to Exceptions
Le 21/02/2012 22:15, Andrei Alexandrescu a écrit : What if instead of catching by class, we catch by attribute matching? So instead of writing: try { ... } catch(SomeExceptionType e) { ... } catch(SomeOtherExceptionType e) { ... } catch(YetAnotherSillyException e) { ... } we write: try { ... } catch(e: exists!e.filename&& e.failedOp is File.open) { // something } catch(e: e.is_transient&& e.num_retries< 5) { // something else } // And why should we even need an exception object in the first // place? catch(time()>= oldtime+5000) { // This thing's been running for way too long, time to // do something drastic } Flamesuit on! ;-) The only problem I see here is ascribing e a type. That is why I proposed here : http://forum.dlang.org/thread/jhos0l$102t$1...@digitalmars.com?page=25#post-jhtus4:2416tp:241:40digitalmars.com some alternative syntax. To make it short : try { // Stuff . . . } catch(Exception e) if(e.transient == false) { // Handling . . . } For the longer explanation, see the link.
Re: The Right Approach to Exceptions
On 02/18/2012 09:09 PM, Jim Hewes wrote: > I think of exception handling as tied to contract programming. I think your use of the word 'contract' is colliding with the contract programming feature. What you describe later does not match with the contract programming and I guess is the reason why Andrei is pointing out two chapters from TDPL. I will reread those chapters later today but I think Andrei is referring to the distinction between assert() and std.exception.enforce(). > A > function has a specific job that it's supposed to do. If for any reason > it cannot do that job successfully, an exception should be thrown. Agreed. That is how we have been using exceptions in C++ successfully at where I work. It makes everything simple. > That > can include even bad parameters Yes, enforce() in D is great for that. I think Andrei agrees. > (although if you have bad parameters to > internal functions I'd think that is a design bug and could be handled > by asserts). Yes. Contract programming uses asserts(). > If what you mean is that exceptions should not be used to return > information when the function is successful, I agree. But it can be used > to return extra details about errors when a function fails. Agreed. Again, this is how we have been using exceptions. We have the equivalents of detailed exception types that Jonathan M Davis has been mentioning. Come to think of it, less than a dozen. > Not every > exception coming out of a function was generated by that function. It > may have come from several levels below that. Of course. It is very useful. > Maybe you can handle the > former because it is more immediate but not the latter. So without > exception types how would you know the difference between them? You > could use error codes and switch on them but it defeats one of the main > purposes of exception handling. So I think if there are exceptions > generated from further away, it's an argument _for_ exception types > rather than against it. Agreed. > For example, just have one BadParameter exception and then store > information about which parameter is bad and why in the exception. Agreed. I would like to add that exceptions are thrown by code that doesn't know how the exception will be useful. For that reason, especially a library function must provide as much information as possible. Going with the same examples in this thread, FileException is not a good exception to throw when the actual problem is a FilePermissionException or a FileNotFoundException. Ali
Re: The Right Approach to Exceptions
On Tue, Feb 21, 2012 at 03:15:19PM -0600, Andrei Alexandrescu wrote: [...] > A more debatable aspect of exceptions is the first-match rule in > catch blocks. All of OOP goes with best match, except here. But then > all code is together so the effect is small. Does it make sense to make it best-match? Or is that too risky since everyone expects it to be first-match? [...] > >So I'm going to throw (har har) this very crazy and wild idea out > >there and let's see if it's viable: > > > >What if instead of catching by class, we catch by attribute matching? > >So instead of writing: > > > > try { ... } > > catch(SomeExceptionType e) { ... } > > catch(SomeOtherExceptionType e) { ... } > > catch(YetAnotherSillyException e) { ... } > > > >we write: > > > > try { ... } > > catch(e: exists!e.filename&& e.failedOp is File.open) { > > // something > > } > > catch(e: e.is_transient&& e.num_retries< 5) { > > // something else > > } > > // And why should we even need an exception object in the first > > // place? > > catch(time()>= oldtime+5000) { > > // This thing's been running for way too long, time to > > // do something drastic > > } > > > >Flamesuit on! ;-) > > The only problem I see here is ascribing e a type. [...] True, for this to work in its full generality would require duck-typing (the catch block can use any property it tests for, regardless of type). Which D doesn't have. So it looks like we're back to catch conditions, that has come up a few times in this thread: try { ... } catch(Exception e: e.is_transient && ... || ...) { // or whatever the latest proposed syntax is, the idea // is the same. } catch(Exception e: !e.is_transient && ...) { // the previous block doesn't catch if conditions fail, // so we can still get Exception here. } This does start to look like it might make sense to switch to best-match instead of first-match for catch clauses. T -- Your inconsistency is the only consistent thing about you! -- KD
Re: Towards a better conceptual model of exceptions (Was: Re: The Right Approach to Exceptions)
On Tue, Feb 21, 2012 at 02:40:30PM -0500, Jonathan M Davis wrote: > On Tuesday, February 21, 2012 00:15:48 H. S. Teoh wrote: > > TRANSITIVITY > > I still contend that this useless, because you need to know what went wrong > to > know whether you actually want to retry anything. And just because the > particular operation that threw could be retried again doesn't mean that the > code that catches the exception can retry the function that _it_ called which > resulted in an exception somewhere down the call stack. And often, you don't > even know which function it was that was called within the try block. So, I > don't see how transivity really matters. If you know what what wrong - which > the type of the exception will tell you - then _that_ is what helps your code > make a useful decision as to what to do, not a transivity property. The point of my little exercise was not to try to solve *every* case of exception handling, but to isolate those cases for which generic handling *does* make sense. I don't pretend that my categories cover *every* case. They obviously don't, as you point out. For those cases where it doesn't make sense, you still catch the specific exception and do your specific recovery, as before. What I'm trying to do is to evaluate the possibility/feasibility of an error recovery system where you *don't* have to know what the specific error is, in order to do something useful with it. What are the errors that *can* be recovered this way, if there are such errors. This is why transitivity is useful, because it allows you to not have to worry about what the specific problem is, and yet still be able to do something meaningful, because the problem doesn't change in nature as you move up the call stack. Obviously, non-transitive errors have to be handled on a case-by-case basis; I'm not negating that at all. The importance of transitivity to generic handling can be seen as follows: Suppose X calls Y and Y calls Z. Z encounters a problem of some sort, let's say for instance it's an input error. So from Y's point of view, the problem can be corrected if it passes different input to Z. But from X's point of view, this is not necessarily true: X's input to Y may have nothing to do with Y's input to Z. So trying to generically handle Z's problem at X's level makes no sense. The problem is not transitive, so no generic handling is possible. You have to catch by type and recover by type. End of story. But suppose Z encounters a transitive problem. Say for instance it's a component failure: one of the functions that Z calls has failed. Well, by extension, that also means Z itself has failed. Now, from Y's point of view, it can attempt to recover by calling W in lieu of Z, if there exists a W that performs an equivalent function to Z. But since the problem is transitive, we can go up to X's level. From X's point of view, it knows nothing about Z, but it *does* know that Y has encountered a component failure (since component failure is transitive). Viewed from X's perspective, Y is the problem (it doesn't know about Z). So if there's an alternative to Y that performs an equivalent function, X can call that in lieu of Y. So you see, even though X has absolute no idea what kind of problem Z encountered, or even that such a thing as Z exists, it *can* make a meaningful effort to recover from the problem that Z encountered. That's what I'm trying to get at: generic handling. Now, to avoid misunderstandings, I'm *not* saying that at every level of the call stack there needs to be a recovery mechanism, such that Y has an alternative to Z and X has an alternative to Y, and so on, all the way up the stack. That would be foolish, since you'll be wasting time writing alternative versions of everything. How this approach benefits real-life programs is that you can insert recovery mechanisms at strategic points in the call chain, so that when problems occur lower down the stack, you can handle it at those points without needing to unwind the stack all the way to the top. Obviously, the try-catch mechanism already does this; but the difference is that those strategic points may not be high enough up the call chain to be able to make a decision about *which* recovery approach to take. They know how to implement the recovery, but they don't know if they should try, or just give up. So this is here is where the high-level delegates come in. *They* know how to decide whether to attempt recovery or just give up, but they *don't* know how to implement recovery, because the lower-level code is a black box to them. By tying the two together via the "Lispian system", you have the possibility of making decisions high up the call stack, and yet still be able to effect low-level recovery strategies. To go back to the earlier example: if Z fails, then from X's point of view Y has also failed, since it doesn't know what Z is. However, if X registers a component failure handler and then calls Y, and Y is capa
Re: The Right Approach to Exceptions
On 2/21/12 2:47 PM, H. S. Teoh wrote: On Tue, Feb 21, 2012 at 09:32:35PM +0100, deadalnix wrote: [...] So it doesn't help. Dulb subclasses of Exceptions are done mostly to be able to catch them. To avoid useless subclasses, we need a more precise way to catch Exception than the type only. [...] This is a good point. Has anybody even considered, *why* does catch match by type? Is there a good reason for that? Or is it just inherited from the rah rah days of OOP where "everything is an object", so since exception is part of everything, exception is an object, therefore we can just catch by object type? I think a reason is that exceptions should be able to transport an arbitrary amount of information. That means heterogeneous types at least. Then, as you discussed in the beginning of the thread, placing types in a hierarchy allows client code to catch several exceptions sharing a common super type, theory that was well understood. A more debatable aspect of exceptions is the first-match rule in catch blocks. All of OOP goes with best match, except here. But then all code is together so the effect is small. From all the heated debate in this thread, it's clear that exceptions don't map very well to a class hierarchy, at least not without points of contention and what amounts to workarounds and hacks. So I'm going to throw (har har) this very crazy and wild idea out there and let's see if it's viable: What if instead of catching by class, we catch by attribute matching? So instead of writing: try { ... } catch(SomeExceptionType e) { ... } catch(SomeOtherExceptionType e) { ... } catch(YetAnotherSillyException e) { ... } we write: try { ... } catch(e: exists!e.filename&& e.failedOp is File.open) { // something } catch(e: e.is_transient&& e.num_retries< 5) { // something else } // And why should we even need an exception object in the first // place? catch(time()>= oldtime+5000) { // This thing's been running for way too long, time to // do something drastic } Flamesuit on! ;-) The only problem I see here is ascribing e a type. Andrei
Re: The Right Approach to Exceptions
On 2/21/12 2:42 PM, Jacob Carlborg wrote: On 2012-02-21 21:27, Andrei Alexandrescu wrote: On 2/21/12 2:26 PM, Jacob Carlborg wrote: As I said, it seems you want to push up implementation details specific to a given subclass to the base class even though it shouldn't be pushed up. I explained that doing so allows for proper formatting of error messages. So it should pushed up. Andrei Well, I don't think that is the right approach. As many others have explained, error messages are only a small part of exception handling. I agree. Also, one interface function is only a small part of a class hierarchy. If you do want to have a generic way of getting an error message out of an exception, what's wrong with toString? Or a new method that formats the error messages. No need to push up the instance variables to the base class. This has been answered in the long thread. In brief, toString loses too much information and putting formatting inside exceptions is not the right place. Andrei
Re: The Right Approach to Exceptions
Le 21/02/2012 01:38, Andrei Alexandrescu a écrit : On 2/20/12 6:25 PM, H. S. Teoh wrote: On Mon, Feb 20, 2012 at 05:15:17PM -0600, Andrei Alexandrescu wrote: Formatting should use class reflection. We already discussed that, and we already agreed that was the superior approach. Jose's argument convinced me otherwise. I retract my agreement. When you're catching a specific exception, you're catching it with the view that it will contain precisely information X, Y, Z that you need to recover from the problem. If you don't need to catch something, then don't put the catch block there. That's extremely rare in my experience, and only present in toy examples that contain a ton of "..." magic. I think you experience here is biased by C++ .
Re: The Right Approach to Exceptions
On Tue, Feb 21, 2012 at 09:32:35PM +0100, deadalnix wrote: [...] > So it doesn't help. Dulb subclasses of Exceptions are done mostly to > be able to catch them. To avoid useless subclasses, we need a more > precise way to catch Exception than the type only. [...] This is a good point. Has anybody even considered, *why* does catch match by type? Is there a good reason for that? Or is it just inherited from the rah rah days of OOP where "everything is an object", so since exception is part of everything, exception is an object, therefore we can just catch by object type? >From all the heated debate in this thread, it's clear that exceptions don't map very well to a class hierarchy, at least not without points of contention and what amounts to workarounds and hacks. So I'm going to throw (har har) this very crazy and wild idea out there and let's see if it's viable: What if instead of catching by class, we catch by attribute matching? So instead of writing: try { ... } catch(SomeExceptionType e) { ... } catch(SomeOtherExceptionType e) { ... } catch(YetAnotherSillyException e) { ... } we write: try { ... } catch(e: exists!e.filename && e.failedOp is File.open) { // something } catch(e: e.is_transient && e.num_retries < 5) { // something else } // And why should we even need an exception object in the first // place? catch(time() >= oldtime+5000) { // This thing's been running for way too long, time to // do something drastic } Flamesuit on! ;-) T -- Famous last words: I *think* this will work...
Re: The Right Approach to Exceptions
On 2012-02-21 21:27, Andrei Alexandrescu wrote: On 2/21/12 2:26 PM, Jacob Carlborg wrote: As I said, it seems you want to push up implementation details specific to a given subclass to the base class even though it shouldn't be pushed up. I explained that doing so allows for proper formatting of error messages. So it should pushed up. Andrei Well, I don't think that is the right approach. As many others have explained, error messages are only a small part of exception handling. If you do want to have a generic way of getting an error message out of an exception, what's wrong with toString? Or a new method that formats the error messages. No need to push up the instance variables to the base class. -- /Jacob Carlborg
Re: The Right Approach to Exceptions
Le 21/02/2012 00:23, Andrei Alexandrescu a écrit : On 2/20/12 4:44 PM, Juan Manuel Cabo wrote: HAhaha, it sometimes feel as though people are afraid that the Variant[string] idea is to never use plain old variables and never use exception subclasses. :-) On the contrary, the idea is so that plain old variables and exception subclasses can be created for the right reasons, and to remove cases where they need to be created for the wrong reasons. Yah, I think there's a lot of confusion and consequent apprehension regarding this. Thanks for attempting to clarify things. Andrei So it doesn't help. Dulb subclasses of Exceptions are done mostly to be able to catch them. To avoid useless subclasses, we need a more precise way to catch Exception than the type only. This Variant[string] doesn't help.
Re: The Right Approach to Exceptions
On 2/21/12 2:26 PM, Jacob Carlborg wrote: As I said, it seems you want to push up implementation details specific to a given subclass to the base class even though it shouldn't be pushed up. I explained that doing so allows for proper formatting of error messages. So it should pushed up. Andrei
Re: The Right Approach to Exceptions
On 2012-02-21 21:06, Andrei Alexandrescu wrote: On 2/21/12 12:03 PM, Jacob Carlborg wrote: On 2012-02-21 17:57, Andrei Alexandrescu wrote: On 2/21/12 10:50 AM, Juan Manuel Cabo wrote: I thought that an alternative to Variant[string] would be to have some virtual functions overrideable (getExceptionData(string dataName) or something). but they would all have to return Object or Variant, so it's the same thing. Exactly. By and large, I think in the fire of the debate too many people in this thread have forgotten to apply a simple OO design principle: push policy up and implementation down. Any good primitive pushed up the exception hierarchy is a huge win, and any design that advocates reliance on concrete types is admitting defeat. Andrei That because you can't (shouldn't) push up implementations specific to a given subclass. Why don't we only have one class, Object, and add a Variant[string] there. Do you see how stupid that is. I think I do. It's also fair to ask you if you are sure you understood my point. Andrei As I said, it seems you want to push up implementation details specific to a given subclass to the base class even though it shouldn't be pushed up. -- /Jacob Carlborg
Re: The Right Approach to Exceptions
On 2/21/12 1:17 PM, Jonathan M Davis wrote: On Tuesday, February 21, 2012 10:57:20 Andrei Alexandrescu wrote: On 2/21/12 10:50 AM, Juan Manuel Cabo wrote: I thought that an alternative to Variant[string] would be to have some virtual functions overrideable (getExceptionData(string dataName) or something). but they would all have to return Object or Variant, so it's the same thing. Exactly. By and large, I think in the fire of the debate too many people in this thread have forgotten to apply a simple OO design principle: push policy up and implementation down. Any good primitive pushed up the exception hierarchy is a huge win, and any design that advocates reliance on concrete types is admitting defeat. Exceptions do _not_ lend themselves to polymorphism. Having them in a type hierarchy is useful. It allows you to deal with them at varying levels of abstractions. But ultimately, you deal with the concrete types, _not_ an abstract interface. In that sense, they're not OO _at all_. Well this is just a series of assertions that conveys no information. Adding a Variant[string] property to allow adding on additional information if a particular application finds it useful may be a good thing to do. But it should be an _add on_, not the core design. Again, just an assertion. Aside from printing strings, trying to deal with exceptions generically just does not make sense. Assertion. At best, you might care about a common exception rather than a more specific one in particular case (e.g. catching IOException rather than FileException). But if you're trying to actually handle the exception in any real way rather than just print out a message, you need the concrete type, not an abstract interface. Assertion. I think that you're pushing the OO angle too hard onto exceptions. I thought I was pushing the generics angle, and OO people explained it to me that that was wrong. They're not completely separated from it, but they really aren't classic OO and shouldn't be treated as such. If anything, they're inverted, because you frequently try and deal with as concrete a type as possible rather than as abstract a type as possible. The hierarchy aspect is really the only truly OO aspect of exceptions IMHO. For the most part, polymorphism just doesn't enter into it. And Exception really already declares the few functions where it does. I'm sorry, I was unable to derive information from this post. It's a string of assertion without any backing. Andrei
Re: The Right Approach to Exceptions
On 2/21/12 12:03 PM, Jacob Carlborg wrote: On 2012-02-21 17:57, Andrei Alexandrescu wrote: On 2/21/12 10:50 AM, Juan Manuel Cabo wrote: I thought that an alternative to Variant[string] would be to have some virtual functions overrideable (getExceptionData(string dataName) or something). but they would all have to return Object or Variant, so it's the same thing. Exactly. By and large, I think in the fire of the debate too many people in this thread have forgotten to apply a simple OO design principle: push policy up and implementation down. Any good primitive pushed up the exception hierarchy is a huge win, and any design that advocates reliance on concrete types is admitting defeat. Andrei That because you can't (shouldn't) push up implementations specific to a given subclass. Why don't we only have one class, Object, and add a Variant[string] there. Do you see how stupid that is. I think I do. It's also fair to ask you if you are sure you understood my point. Andrei
Re: The Right Approach to Exceptions
On 2012-02-21 19:33, Juan Manuel Cabo wrote: That because you can't (shouldn't) push up implementations specific to a given subclass. Why don't we only have one class, Object, and add a Variant[string] there. Do you see how stupid that is. As stupid as any database API which returns result items as Variant[string] or string[string], but it works. (the sad part is that one has to rely a bit on convention, but convention can be standardized (string constants) and measures taken when deviated so that it is done gracefuly). That's because we are limited by the database API. If you created a new database from scratch, completely written in D, perhaps even object oriented, you could return the correct object form the beginning. Do you have an alternative solution that allows to extend an exception object with extra information, while keeping it the same class? No, but that's what subclasses are used for. So if one removes the bad reasons to create new Exception types, then the ones that DO get created are solid, standard, reusable, and can withstand the test of time. Because they would be open for extension but closed for source code modification. -- /Jacob Carlborg
Re: Towards a better conceptual model of exceptions (Was: Re: The Right Approach to Exceptions)
On 2012-02-21 09:15, H. S. Teoh wrote: All of this heated debate has led me to reconsider our whole concept of exceptions. It seems that we're squabbling over little details in existing paradigms. But what of the big picture? What *is* an exception anyway? We all know the textbook definition, but clearly something is missing since we can't seem to agree how it should be implemented. ... I think I like the idea in general. -- /Jacob Carlborg
Re: Towards a better conceptual model of exceptions (Was: Re: The Right Approach to Exceptions)
Le 21/02/2012 20:00, H. S. Teoh a écrit : On Tue, Feb 21, 2012 at 06:24:01PM +0100, deadalnix wrote: Le 21/02/2012 18:10, H. S. Teoh a écrit : [...] True, and there's nothing to stop you from digging into the details of the raised Condition if you want to. I did consider implementing Conditions as some kind of class hierarchy, so that the generic categories are at the top, underneath Condition, then actual specific Conditions can extend them. If your handler knows of a specific Condition, then you can access any pertinent additional info, and make decisions based on that. But what I wanted to know was, can you still do something meaningful even if you knew nothing beyond the top-level generic categories of Conditions? That way, your handler will still work with new Conditions that you've never seen before. [...] And here come an idea of mine : If the Condition has a way to provide the Exception associated. So you can get the hierarchy from the Exception, and you don't need to creat two hierachy of classes just for the bloat. You're right, that would be unnecessary duplication, especially since an unhandled Condition becomes a thrown Exception anyway, and it's a very bad idea to duplicate the entire Exception hierarchy in Condition. Only thing is, then the handler will have to downcast the Exception to get to the useful info. This may lead to messy code. But it's something we should investigate. Yes, I'm aware of the problem and it is real. I have no good solution for it now. You'll fond attached some sketching code. What do you think of it ? Is this the same code you attached in an earlier post? I did look over it, sorry, didn't have time to respond earlier. I like the idea of wrapping retryable code in the runTransient template, that way we minimize the amount of boilerplate needed to actually use this feature. Oh wait, you've added some new stuff. Let's see... Hmm, I like the idea of providing default handlers for some commonly-encountered situations. Reduces the amount of boilerplate. And there's always the option of manually defining a handler if you need to. +1. I was considering more last night how to implement this system. I think I change my mind about having a common Condition base class. The whole idea is that every major category would be defined by what kinds of actions are available, so they are quite distinct from each other. I don't want to reinvent another hierarchy to represent problems; we already have an Exception hierarchy for that. So the different Conditions need to be qualitatively different. To maximize usability and minimize redundancy and bloat, I'm thinking we should define categories based on what recovery actions are *actually available*, rather than what actions are *potentially* available. So to that end, it's not really categorization per se, but more of a way of describing what recovery strategies are actually available. Yes indeed. Exception should provide data about what the problem IS, condition on what are option to recover. In other words, an input error where you can recover by skipping the bad data is qualitatively different from an input error where skipping doesn't fix the problem. These two should be treated as distinct Conditions. Basically, I want to guarantee that for some Condition c, action A will *always* be available to the handler. Then the handler won't need excessive checking (if A1 is available but A2 is not, then use A1; else if A1 is not available but A2 is available, ... etc.). It can count on all options being actually present. Agreed. Back to your code. We can implement this idea by defining a template for each of the possible conditions. So code in runTransient always raises a transient condition if it fails, runReroutable always raises a reroutable condition if it fails (reroutable = failing component X can be replaced by component Y). I don't know if this is too inflexible, but trying to mix multiple conditions into a single operation seems to turn the code into spaghetti: int x, y, z; retry1: doSomething(x, y, z); retry2: if (problem1) raise(condition1); else if (problem2) raise(condition2); ... handleCondition(condition1): if (recoveryAction1) { fiddleWith(x); goto retry1; } else if (recoveryAction2) { doSomethingElse(x,y,z); goto retry2; } handleCondition(condition2): if (recoveryAction3) { fiddleWith(y); goto retry1; } else if (recoveryAction4) { fiddleWith(z); doSomethingElse(x,y,z); goto retry2; } So I don't think this is the right way to go.
Re: The Right Approach to Exceptions
On Tuesday, February 21, 2012 08:15:29 Andrei Alexandrescu wrote: > On 2/21/12 4:40 AM, Vincent wrote: > > On Saturday, 18 February 2012 at 18:52:05 UTC, Andrei Alexandrescu wrote: > >> From experience I humbly submit that catching by type is most of the > >> time useless. > > > > Completely disagree. Types allow to control place for "catch". Say, some > > deeply nested function catches its own exceptions, while outer function > > catches the rest - exceptions higher in hierarchy. But to have benefit > > you have to create exceptions hierarchy - this is the main point. > > As the next hundreds of messages discuss, matters are not all that > simple :o). It probably should be though. I think that there's a lot of overthinking going on here. We need to have our exceptions organized into a hierarchy so that you can catch them based on what went wrong. That is the main thing that we're missing IMHO. Adding extra capabilities along side that is fine as long as they make sense. Adding a Variant[string] property for the purpose of putting non-standard information in your exceptions which don't merit a new exception type makes some sense. But if it makes sense to catch an exception based on that information, then it probably merits a new type. Adding improved string formatting cabilities also makes some sense. But I don't think that we need to drastically overhaul how exceptions work, and a lot of this discussion is straying into left field. - Jonathan M Davis
Re: Towards a better conceptual model of exceptions (Was: Re: The Right Approach to Exceptions)
On Tuesday, February 21, 2012 00:15:48 H. S. Teoh wrote: > TRANSITIVITY I still contend that this useless, because you need to know what went wrong to know whether you actually want to retry anything. And just because the particular operation that threw could be retried again doesn't mean that the code that catches the exception can retry the function that _it_ called which resulted in an exception somewhere down the call stack. And often, you don't even know which function it was that was called within the try block. So, I don't see how transivity really matters. If you know what what wrong - which the type of the exception will tell you - then _that_ is what helps your code make a useful decision as to what to do, not a transivity property. So, yes. A particular exception type is generally transitive or not, but you know that by the nature of the type and the problem that it represents. I don't see how focusing on transivity is useful. > The try-catch mechanism is not adequate to implement all the recovery > actions described above. As I've said before when discussing what I > called the Lispian model, some of these recovery actions need to happen > *in the context where the exception was thrown*. Once the stack unwinds, > it may not be possible to recover anymore, because the execution context > of the original code is gone. > This is where the Lispian model really shines. To summarize: try-catch and Exceptions are built into the language. Exceptions are part of not only the standard library but the runtime. They're not perfect, but they _work_. I really think that this whole thing is being way overthought and blown out of proportion. Using another recovery mechanism in your programs (built on top of exceptions or otherwise) is fine, but I really don't see a need to seriously alter how error handling is done in the language as a whole. We're _way_ past the point where it makes sense to completely redesign how exceptions work. And I _really_ don't think that it's a good idea to change anything major about how error handling is done in the standard library without a lot of real world experience with whatever you want to replace it with. And we don't have that. So, I see no problem with you experimenting, but I don't think that we should be drastically changing how the standard library functions with regards to exceptions. We would definitely gain something by cleaning up how they're organized, and maybe adding additional capabilites to improve their printing abilities like Andrei wants to do would be of some value, but we don't need a complete redesign, just some tweaks. - Jonathan M Davis
Re: The Right Approach to Exceptions
On Tuesday, February 21, 2012 10:57:20 Andrei Alexandrescu wrote: > On 2/21/12 10:50 AM, Juan Manuel Cabo wrote: > > I thought that an alternative to Variant[string] would be to have some > > virtual functions overrideable (getExceptionData(string dataName) or > > something). but they would all have to return Object or Variant, so it's > > the same thing. > Exactly. By and large, I think in the fire of the debate too many people > in this thread have forgotten to apply a simple OO design principle: > push policy up and implementation down. Any good primitive pushed up the > exception hierarchy is a huge win, and any design that advocates > reliance on concrete types is admitting defeat. Exceptions do _not_ lend themselves to polymorphism. Having them in a type hierarchy is useful. It allows you to deal with them at varying levels of abstractions. But ultimately, you deal with the concrete types, _not_ an abstract interface. In that sense, they're not OO _at all_. Adding a Variant[string] property to allow adding on additional information if a particular application finds it useful may be a good thing to do. But it should be an _add on_, not the core design. Aside from printing strings, trying to deal with exceptions generically just does not make sense. At best, you might care about a common exception rather than a more specific one in particular case (e.g. catching IOException rather than FileException). But if you're trying to actually handle the exception in any real way rather than just print out a message, you need the concrete type, not an abstract interface. I think that you're pushing the OO angle too hard onto exceptions. They're not completely separated from it, but they really aren't classic OO and shouldn't be treated as such. If anything, they're inverted, because you frequently try and deal with as concrete a type as possible rather than as abstract a type as possible. The hierarchy aspect is really the only truly OO aspect of exceptions IMHO. For the most part, polymorphism just doesn't enter into it. And Exception really already declares the few functions where it does. - Jonathan M Davis
Re: The Right Approach to Exceptions
On Tue, Feb 21, 2012 at 07:57:37PM +0100, deadalnix wrote: > Le 21/02/2012 03:33, Robert Jacques a écrit : [...] > >Aren't __traits and opDispatch fun? > > opDispatch is nice, but rather incomplete. It doesn't handle template > methods for example. Does RTTI handle template methods? T -- Answer: Because it breaks the logical sequence of discussion. / Question: Why is top posting bad?
Re: Towards a better conceptual model of exceptions (Was: Re: The Right Approach to Exceptions)
On Tue, Feb 21, 2012 at 06:24:01PM +0100, deadalnix wrote: > Le 21/02/2012 18:10, H. S. Teoh a écrit : [...] > >True, and there's nothing to stop you from digging into the details > >of the raised Condition if you want to. I did consider implementing > >Conditions as some kind of class hierarchy, so that the generic > >categories are at the top, underneath Condition, then actual specific > >Conditions can extend them. If your handler knows of a specific > >Condition, then you can access any pertinent additional info, and > >make decisions based on that. > > > >But what I wanted to know was, can you still do something meaningful > >even if you knew nothing beyond the top-level generic categories of > >Conditions? That way, your handler will still work with new > >Conditions that you've never seen before. [...] > And here come an idea of mine : > > If the Condition has a way to provide the Exception associated. So you > can get the hierarchy from the Exception, and you don't need to creat > two hierachy of classes just for the bloat. You're right, that would be unnecessary duplication, especially since an unhandled Condition becomes a thrown Exception anyway, and it's a very bad idea to duplicate the entire Exception hierarchy in Condition. Only thing is, then the handler will have to downcast the Exception to get to the useful info. This may lead to messy code. But it's something we should investigate. > You'll fond attached some sketching code. What do you think of it ? Is this the same code you attached in an earlier post? I did look over it, sorry, didn't have time to respond earlier. I like the idea of wrapping retryable code in the runTransient template, that way we minimize the amount of boilerplate needed to actually use this feature. Oh wait, you've added some new stuff. Let's see... Hmm, I like the idea of providing default handlers for some commonly-encountered situations. Reduces the amount of boilerplate. And there's always the option of manually defining a handler if you need to. +1. I was considering more last night how to implement this system. I think I change my mind about having a common Condition base class. The whole idea is that every major category would be defined by what kinds of actions are available, so they are quite distinct from each other. I don't want to reinvent another hierarchy to represent problems; we already have an Exception hierarchy for that. So the different Conditions need to be qualitatively different. To maximize usability and minimize redundancy and bloat, I'm thinking we should define categories based on what recovery actions are *actually available*, rather than what actions are *potentially* available. So to that end, it's not really categorization per se, but more of a way of describing what recovery strategies are actually available. In other words, an input error where you can recover by skipping the bad data is qualitatively different from an input error where skipping doesn't fix the problem. These two should be treated as distinct Conditions. Basically, I want to guarantee that for some Condition c, action A will *always* be available to the handler. Then the handler won't need excessive checking (if A1 is available but A2 is not, then use A1; else if A1 is not available but A2 is available, ... etc.). It can count on all options being actually present. Back to your code. We can implement this idea by defining a template for each of the possible conditions. So code in runTransient always raises a transient condition if it fails, runReroutable always raises a reroutable condition if it fails (reroutable = failing component X can be replaced by component Y). I don't know if this is too inflexible, but trying to mix multiple conditions into a single operation seems to turn the code into spaghetti: int x, y, z; retry1: doSomething(x, y, z); retry2: if (problem1) raise(condition1); else if (problem2) raise(condition2); ... handleCondition(condition1): if (recoveryAction1) { fiddleWith(x); goto retry1; } else if (recoveryAction2) { doSomethingElse(x,y,z); goto retry2; } handleCondition(condition2): if (recoveryAction3) { fiddleWith(y); goto retry1; } else if (recoveryAction4) { fiddleWith(z); doSomethingElse(x,y,z); goto retry2; } So I don't think this is the right way to go. Each operation should have a single condition with a well-defined set of recovery methods, not some arbitrary combination of multiple conditions. What do you think? T -- The day Microsoft makes something that doe
Re: The Right Approach to Exceptions
Le 21/02/2012 03:33, Robert Jacques a écrit : On Mon, 20 Feb 2012 15:21:56 -0600, H. S. Teoh wrote: On Mon, Feb 20, 2012 at 02:57:08PM -0600, Andrei Alexandrescu wrote: On 2/20/12 1:45 PM, Jonathan M Davis wrote: >On Monday, February 20, 2012 20:42:28 deadalnix wrote: >>Le 20/02/2012 20:27, Jonathan M Davis a écrit : >>>On Monday, February 20, 2012 11:15:08 H. S. Teoh wrote: That's why I proposed to use runtime reflection to scan the exception object for applicable fields. Then you get the best of both worlds: the message formatter doesn't need to know what the fields are, and you get full compile-time type checking for catching code that directly accesses the fields. >>> >>>That would certainly be better. >>> >>>- Jonathan M Davis >> >>This is way better than Variant[string], but unimplemented ATM. > >Yes, but you can use compile-time constructs to generate it. And as you >pointed out in another post, tupleof should do the trick. Regardless, the >point is that using reflection of some kind is a much better solution than >using variant. Agreed. Ideally adding a sort of mixin to any class would enable it for advanced run-time information: class DRoxException : Exception { mixin(enableRTTI); ... normal implementation ... } [...] Doesn't this need compiler/language support? Nope. See (https://jshare.johnshopkins.edu/rjacque2/public_html/ ) Variant e = new MyException(); writeln( e.filename, e.line, e.column); Aren't __traits and opDispatch fun? opDispatch is nice, but rather incomplete. It doesn't handle template methods for example.
Re: The Right Approach to Exceptions
> That because you can't (shouldn't) push up implementations specific to a > given subclass. Why don't we only have one > class, Object, and add a Variant[string] there. > > Do you see how stupid that is. As stupid as any database API which returns result items as Variant[string] or string[string], but it works. (the sad part is that one has to rely a bit on convention, but convention can be standardized (string constants) and measures taken when deviated so that it is done gracefuly). Do you have an alternative solution that allows to extend an exception object with extra information, while keeping it the same class? So if one removes the bad reasons to create new Exception types, then the ones that DO get created are solid, standard, reusable, and can withstand the test of time. Because they would be open for extension but closed for source code modification. --jm On 02/21/2012 03:03 PM, Jacob Carlborg wrote: > On 2012-02-21 17:57, Andrei Alexandrescu wrote: >> On 2/21/12 10:50 AM, Juan Manuel Cabo wrote: >>> I thought that an alternative to Variant[string] would be to have some >>> virtual >>> functions overrideable (getExceptionData(string dataName) or something). >>> but they would all have to return Object or Variant, so it's the same >>> thing. >> >> Exactly. By and large, I think in the fire of the debate too many people >> in this thread have forgotten to apply a simple OO design principle: >> push policy up and implementation down. Any good primitive pushed up the >> exception hierarchy is a huge win, and any design that advocates >> reliance on concrete types is admitting defeat. >> >> Andrei > > That because you can't (shouldn't) push up implementations specific to a > given subclass. Why don't we only have one > class, Object, and add a Variant[string] there. > > Do you see how stupid that is. >