February 20, 2012
On Monday, February 20, 2012 11:57:07 Andrei Alexandrescu wrote:
> On 2/20/12 11:44 AM, foobar wrote:
> > This extra processing is orthogonal to the exception. the same exception can be logged to a file, processed (per above example) and generate graphical notification to the user, etc. The exception contains the information pertaining only to what went wrong. the rest is not part of this discussion.
> 
> Exactly. I don't see how a disagreement follows from here. So isn't it reasonable to design the exception such that it can offer information pertaining to what went wrong, in a uniform manner?

In most cases, that's not even vaguely possible. An exception for a socket would probably include an IP and port in its additonal information. An exception for a file operation would include the file name that it tried to operate on. An exception for malformed unicode would include information on the string and the bad character. An exception from parsing XML would give you information about which part of the XML had the problem and what the problem was. All of these situations are completely different. Using a Variant is the equivalent of using a void* and casting to the actual information based no the type of the exception or on a type code in the exception. How is that better than simply having an actual exception hierarchy with each of the derived exceptions having fields specific to their circumstances? I contend that it's far worse. It's throwing away type information simply in an effort to genericize. Genericity can be very useful, but it can also be over-applied.

I don't see how you could possibly make that uniform. It's very non-uniform by its very nature. The handling _needs_ to be non-uniform.

> > The exact same exception in the example would also be thrown on a
> > mistyped URL in an application that tries to scrape some info from a
> > website for further processing. The error is still the same - the url is
> > incorrect but different use cases handle it differently. In the former
> > example I might call to a i18n lib (someone already mentioned gettext)
> > while in the latter I'll call a logging library with the the mistyped
> > url (for statistics' sake).
> > in the first I use the url to find a closest match, in the second I want
> > to log said requested url. Both handled by *other* mechanisms.
> > in both cases the exception needs a url field and in both cases I have
> > no need for the Variant[string] map.
> 
> The Variant[string] map saves a lot of duplication whenever you want to format a human-readable string (which is a common activity with exceptions). It transforms this (I'm too lazy to write code anew by hand, so I'll paste Jonathan's):
> 
[snip]
> 
> The stringTemplate function loads the formatting template from a table
> indexed on typeid(e).toString() and formats it with the info. It's
> simple factorization.

That only works in this particular case, because it happens that the code is just figuring out how to translate the exception into an error message without trying to recover. If you want to do anything fancier (like suggest the flag that the user probably meant), you need those extra fields in the exceptions.

Also, much of the time, the application is going to want to print its own error messages, not what library that its calling used. And frequently, the application can give much better messages, because it knows what it's doing, not just the operation that it tried and failed. Exceptions are for _developers_, not users, and in general, exception messages should not be printed to users.

And moving any messages that exceptions have out of the exception itself is _bad_ for debugging. The exception should contain its own information, not have it externalized for internationalization purposes. If its in the exception itself, you can see it in the debugger. That doesn't work anywhere near as well when you externalize its stuff.

I gave the getopt example, because we were talking about getopt, and it was easy to write. It shows one way that the fields in the exception could be useful. But it's an abnormality in that it's whole purpose is to allow you to figure out how to best give an error message to the user. Many, many other exceptions need to be processed by the program without necessarily informing the user of anything. So, getopt was probably a bad example. It just was an easy one to give.

- Jonathan M Davis
February 20, 2012
On 2/20/12 12:11 PM, dennis luehring wrote:
> Am 20.02.2012 17:36, schrieb Andrei Alexandrescu:
>> I understand this seems loose, but again, when you want to do custom
>> formatting or i18n this is the way to go. The job of rendering the
>> exception as a string must be outsourced (heh) outside the exception,
>> and Variant[string] is a simple mechanism to do so.
>>
>> Andrei
>>
>>
>
> it is loose and Variant[string] is a simple mechanism for data
> transport, but evil in form of compile-time checks, the complete
> signature of your information is then (per se) only runtime-checked, how
> to prevent difference in the info[] filling and info[] usage over time?
> i just mistyped my key, removed a key, used a different, etc... multiply
> that by the amount of needed Exceptions and their info-key-variants ...
> hell on earth

This is not hell on earth, it is the classic type dilution when you need to abide to binary interfaces.

> in the end you will have a bunch of modules wich const-string-keys for
> the exception creation and rendering, and then i don't see any real
> difference to a specialize exception

No, you end up with centralized formatting facilities and smaller code.


Andrei
February 20, 2012
On Mon, Feb 20, 2012 at 01:23:04PM -0500, Jonathan M Davis wrote:
> On Monday, February 20, 2012 11:57:07 Andrei Alexandrescu wrote:
[...]
> > Exactly. I don't see how a disagreement follows from here. So isn't it reasonable to design the exception such that it can offer information pertaining to what went wrong, in a uniform manner?
[...]
> I don't see how you could possibly make that uniform. It's very non-uniform by its very nature. The handling _needs_ to be non-uniform.

I think there's a misunderstanding here. What Andrei is trying to achieve is something like this:

	class Exception : Error {
		Variant[string] data;
		string formatMsg(LocaleInfo li) {
			return formatLocale(li, msg, data);
		}
	}

	class GetOptException : Exception {
		this() {
			data["..."] = ...;
			...
		}
	}

I contend that using Variant here is not necessary.  You can easily do this instead:

	class Exception : Error {
		string formatMsg(LocaleInfo li) {
			auto members = typeid(this).getMembers(null);
			string msg;

			foreach (member; members) {
				if (member.name.startsWith("info")) {
					... //format this field
				}
			}
			return msg;
		}
	}

	class GetOptException : Exception {
		string infoOption;	// this gets picked up by formatMsg
		int internalData;	// this is ignored

		// No need to declare anything else except ctor to set
		// the above fields.
	}

This allows for a much cleaner, type-checked access to infoOption, should the catching code know how to deal with GetOptException specifically.


T

-- 
The day Microsoft makes something that doesn't suck is probably the day they start making vacuum cleaners... -- Slashdotter
February 20, 2012
Le 20/02/2012 19:42, H. S. Teoh a écrit :
> On Mon, Feb 20, 2012 at 01:23:04PM -0500, Jonathan M Davis wrote:
>> On Monday, February 20, 2012 11:57:07 Andrei Alexandrescu wrote:
> [...]
>>> Exactly. I don't see how a disagreement follows from here. So isn't
>>> it reasonable to design the exception such that it can offer
>>> information pertaining to what went wrong, in a uniform manner?
> [...]
>> I don't see how you could possibly make that uniform. It's very
>> non-uniform by its very nature. The handling _needs_ to be
>> non-uniform.
>
> I think there's a misunderstanding here. What Andrei is trying to
> achieve is something like this:
>
> 	class Exception : Error {
> 		Variant[string] data;
> 		string formatMsg(LocaleInfo li) {
> 			return formatLocale(li, msg, data);
> 		}
> 	}
>
> 	class GetOptException : Exception {
> 		this() {
> 			data["..."] = ...;
> 			...
> 		}
> 	}
>
> I contend that using Variant here is not necessary.  You can easily do
> this instead:
>
> 	class Exception : Error {
> 		string formatMsg(LocaleInfo li) {
> 			auto members = typeid(this).getMembers(null);
> 			string msg;
>
> 			foreach (member; members) {
> 				if (member.name.startsWith("info")) {
> 					... //format this field
> 				}
> 			}
> 			return msg;
> 		}
> 	}
>
> 	class GetOptException : Exception {
> 		string infoOption;	// this gets picked up by formatMsg
> 		int internalData;	// this is ignored
>
> 		// No need to declare anything else except ctor to set
> 		// the above fields.
> 	}
>
> This allows for a much cleaner, type-checked access to infoOption,
> should the catching code know how to deal with GetOptException
> specifically.
>
>
> T
>

This is bad design IMO. Exception are here to provide information about what is wrong. It has nothing to do with internationalisation whatsoever.
February 20, 2012
On 2/20/12 12:23 PM, Jonathan M Davis wrote:
> On Monday, February 20, 2012 11:57:07 Andrei Alexandrescu wrote:
>> On 2/20/12 11:44 AM, foobar wrote:
>>> This extra processing is orthogonal to the exception. the same exception
>>> can be logged to a file, processed (per above example) and generate
>>> graphical notification to the user, etc. The exception contains the
>>> information pertaining only to what went wrong. the rest is not part of
>>> this discussion.
>>
>> Exactly. I don't see how a disagreement follows from here. So isn't it
>> reasonable to design the exception such that it can offer information
>> pertaining to what went wrong, in a uniform manner?
>
> In most cases, that's not even vaguely possible.

Please hear me out.

> An exception for a socket
> would probably include an IP and port in its additonal information. An
> exception for a file operation would include the file name that it tried to
> operate on. An exception for malformed unicode would include information on
> the string and the bad character. An exception from parsing XML would give you
> information about which part of the XML had the problem and what the problem
> was. All of these situations are completely different. Using a Variant is the
> equivalent of using a void* and casting to the actual information based no the
> type of the exception or on a type code in the exception. How is that better
> than simply having an actual exception hierarchy with each of the derived
> exceptions having fields specific to their circumstances?

It is better because you get to factor out all formatting in one place. You combine a string template that has references to symbolic names, with a context giving the symbolic names. Otherwise the code doing so would need to duplicate that code all over the place - exactly as you did in your example.

> I contend that it's
> far worse. It's throwing away type information simply in an effort to
> genericize. Genericity can be very useful, but it can also be over-applied.

No, it's not throwing away anything. If you want to handle the actual exception, sure, you get typed access to the state. All I want is push one interface method up.

> I don't see how you could possibly make that uniform. It's very non-uniform by
> its very nature. The handling _needs_ to be non-uniform.

No, it doesn't, and if you just hear me out for a second you'll see how. It's very simple. Formatting is all about pairing symbolic names with data. A format engine would load a string template that uses symbolic name such as "file", "line", "IP", "port", and a context which contains bindings of these names to values. That way formatting does not need to know what an IP means etc.

>> The stringTemplate function loads the formatting template from a table
>> indexed on typeid(e).toString() and formats it with the info. It's
>> simple factorization.
>
> That only works in this particular case, because it happens that the code is
> just figuring out how to translate the exception into an error message without
> trying to recover. If you want to do anything fancier (like suggest the flag
> that the user probably meant), you need those extra fields in the exceptions.

Sure. Again, this is not advocating replacement of exception hierarchies with tables!

> Also, much of the time, the application is going to want to print its own
> error messages, not what library that its calling used.

Sure. Then the application has its own string templates. That was the purpose of the whole thing. Exceptions give bindings, applications give formatting templates.

> And frequently, the
> application can give much better messages, because it knows what it's doing,
> not just the operation that it tried and failed. Exceptions are for
> _developers_, not users, and in general, exception messages should not be
> printed to users.

Yup. Yup.

> And moving any messages that exceptions have out of the exception itself is
> _bad_ for debugging.

Sure. Print typeid(e) if you so want, and handle explicit exception specifically as needed.

> The exception should contain its own information, not
> have it externalized for internationalization purposes.

And that information should be accessible in a uniform manner.

> If its in the
> exception itself, you can see it in the debugger. That doesn't work anywhere
> near as well when you externalize its stuff.
>
> I gave the getopt example, because we were talking about getopt, and it was
> easy to write.

Code like that is present in very many catch blocks.

> It shows one way that the fields in the exception could be
> useful.

To me it shows how code can be duplicated without necessity.

> But it's an abnormality in that it's whole purpose is to allow you to
> figure out how to best give an error message to the user. Many, many other
> exceptions need to be processed by the program without necessarily informing
> the user of anything. So, getopt was probably a bad example. It just was an
> easy one to give.

You may want to give another one.


Andrei

February 20, 2012
On 2/20/12 12:42 PM, H. S. Teoh wrote:
> On Mon, Feb 20, 2012 at 01:23:04PM -0500, Jonathan M Davis wrote:
>> On Monday, February 20, 2012 11:57:07 Andrei Alexandrescu wrote:
> [...]
>>> Exactly. I don't see how a disagreement follows from here. So isn't
>>> it reasonable to design the exception such that it can offer
>>> information pertaining to what went wrong, in a uniform manner?
> [...]
>> I don't see how you could possibly make that uniform. It's very
>> non-uniform by its very nature. The handling _needs_ to be
>> non-uniform.
>
> I think there's a misunderstanding here. What Andrei is trying to
> achieve is something like this:
>
> 	class Exception : Error {
> 		Variant[string] data;
> 		string formatMsg(LocaleInfo li) {
> 			return formatLocale(li, msg, data);
> 		}
> 	}
>
> 	class GetOptException : Exception {
> 		this() {
> 			data["..."] = ...;
> 			...
> 		}
> 	}
>
> I contend that using Variant here is not necessary.  You can easily do
> this instead:
>
> 	class Exception : Error {
> 		string formatMsg(LocaleInfo li) {
> 			auto members = typeid(this).getMembers(null);
> 			string msg;
>
> 			foreach (member; members) {
> 				if (member.name.startsWith("info")) {
> 					... //format this field
> 				}
> 			}
> 			return msg;
> 		}
> 	}
>
> 	class GetOptException : Exception {
> 		string infoOption;	// this gets picked up by formatMsg
> 		int internalData;	// this is ignored
>
> 		// No need to declare anything else except ctor to set
> 		// the above fields.
> 	}
>
> This allows for a much cleaner, type-checked access to infoOption,
> should the catching code know how to deal with GetOptException
> specifically.

But this moves i18n code straight inside the exception, which Jonathan argues against. Separated concerns call for separated modules.

Sorry, I don't know what to write beyond the one line above!


Andrei


February 20, 2012
On 2/20/12 12:50 PM, deadalnix wrote:
> This is bad design IMO. Exception are here to provide information about
> what is wrong. It has nothing to do with internationalisation whatsoever.

100% agree.

Andrei
February 20, 2012
On Mon, Feb 20, 2012 at 06:37:07PM +0100, deadalnix wrote:
> Le 20/02/2012 06:57, H. S. Teoh a écrit :
[...]
> >Alright. This thread has gone on for too long with lots of talk but no down-to-earth, real code. So I decided to code up a quick-n-dirty proof-of-concept implementation of a Lispian scheme of exception handling. The source files are attached.
[...]
> I read your code with great interest ! I'm not sure about this. This is a lot of code to not achieve that much. OK, the stack hasn't been destroyed, but your delegate has its own scope and cannot do a lot.

The delegate has full access to the scope of main(), or wherever it was registered from, which the low-level error recovery code has no access to.


> Isn't a retry function provided by phobos and a transeient property as proposed by Andrei a better alternative ?

The problem is that by the time you regain control at the catch, the
stack has already unwound to main(), so if you need to retry, you need
to call openDataFile() all over again.

In this case it's not too bad, because openDataFile() is very simple.
But imagine if this openDataFile() was nested deeply inside a few layers
of functions called from main(), then by the time you unwind the stack
to main(), all the previous work done is lost, and you have to restart
from the beginning.

Also, how does the transient property help main() know that it should prompt the user for a different filename? What if a different exception was caught, like NetworkDriveUnreachable? It wouldn't make sense to prompt the user for another filename in this case.


T

-- 
Nearly all men can stand adversity, but if you want to test a man's character, give him power. -- Abraham Lincoln
February 20, 2012
On Monday, February 20, 2012 12:28:27 Andrei Alexandrescu wrote:
> On 2/20/12 12:20 PM, Jonathan M Davis wrote:
> > On Monday, February 20, 2012 18:05:38 foobar wrote:
> >> Separation of concerns - exceptions are meant to notify the *developer* of errors. User facing error messages is a separate concern that exceptions should not be responsible for. it's not just outsourcing the translation strings, it's the developer's job to determine what if at all should be done with the exception.
> > 
> > Agreed. Users shouldn't be seeing exception messages. They are intended
> > for
> > the developer. Users don't know or care about them. You don't display
> > error
> > codes to users when something goes wrong do you?
> 
> You don't want to duplicate all over the place formatting code do you?

Most of the time that won't happen simply because you need different formatting code for different types of errors and contexts. And if it does, you can just write a function which takes the exception and prints out the message that you want with the formatting that you want.

Also, for contexts where you're going to give a message to the user, toString doesn't cut it, because it's doing stuff like putting the stack trace in the message. So, do you intend to effectively have _two_ error messages with every exception - one for the developer and one for the user?

So, in the cases where you need to save on formatting code, I don't think that it's hard to do so without adding anything to Exception, and it's stuff which frequently needs to be application-specific anyway, in which case, Exception couldn't possibly provide the right message anyway.

> > And adding an
> > internationalization mechanism would actually make things _worse_, because
> > it complicates exceptions for something that they don't generally need,
> > and it makes debugging harder, because you can't see the message as
> > easily in the debugger.
> > 
> > The fields and functions that exceptions have should be geared towards facilitating the program processing and recovering from the exception, not printing out error messages. And a big part of that is giving them fields which can be processed programmatically, not ways to print internationalized strings.
> 
> Yes. Programmatic processing in a class hierarchy means you want to push policy up and implementation down. If you don't do this, you need to duplicate client code to work with each derived class, instead of having client code deal with good primitives.

So, instead of

catch(SpecificException1 e)
{
 //use fields specific to this exception to do whatever you need to do
}
catch(SpecificException2 e)
{
 //use fields specific to this exception to do whatever you need to do
}
catch(GenericException e)
{
 //handle the generic exception as best you can given the lack of
 //a specific one
}

you end up with something effectively along the lines of

catch(GenericException e)
{
 if(/* e is SpecificException1 */)
 {
 //use fields specific to this exception to do whatever you need to do
 }
 else if(/* e is SpecificException2 */)
 {
 //use fields specific to this exception to do whatever you need to do
 }
 else
 {
 //handle the generic exception as best you can given the lack of
 //a specific one
 }
}

How is that better? It's throwing away catch's ability to put the program execution in the correct handling code. I don't see how your suggestion is an improvement over using an actual class hierarchy. You can't generally make the handling uniform unless the catch block is simply going to do something like log the message rather than handle it. And if you're doing that, you can just catch the more generic exception.

It seems like you want to effectively do something like add a method to Exception along the lines of handleException which exception handling code calls so that it's nice and generic. But it's the exception handling code which needs to do specific stuff and thus frequently _can't_ be generic.

Also, how would the whole variant hash work with purity? You can access global variables in pure functions. So, I wouldn't expect it to work in a pure function. Class hierarchies do. I would think that that would kill the whole idea right there, even if it were better than using a class hierarchy (which I don't think that it is).

- Jonathan M Davis
February 20, 2012
On Monday, February 20, 2012 12:45:57 Andrei Alexandrescu wrote:
> On 2/20/12 12:23 PM, Jonathan M Davis wrote:
> > I don't see how you could possibly make that uniform. It's very non-uniform by its very nature. The handling _needs_ to be non-uniform.
> 
> No, it doesn't, and if you just hear me out for a second you'll see how. It's very simple. Formatting is all about pairing symbolic names with data. A format engine would load a string template that uses symbolic name such as "file", "line", "IP", "port", and a context which contains bindings of these names to values. That way formatting does not need to know what an IP means etc.
> 
> >> The stringTemplate function loads the formatting template from a table
> >> indexed on typeid(e).toString() and formats it with the info. It's
> >> simple factorization.
> > 
> > That only works in this particular case, because it happens that the code is just figuring out how to translate the exception into an error message without trying to recover. If you want to do anything fancier (like suggest the flag that the user probably meant), you need those extra fields in the exceptions.
> Sure. Again, this is not advocating replacement of exception hierarchies with tables!

Okay. If all you're looking to do is provide a templating mechanism for exception messages, then fine. If it saves on code, great. I question that it's worth the complexity, but maybe it would be a big improvement. I suspect that we'd have to implement it to really find that out.

But I think that the exceptions themselves should be organized into a class hierarchy such that you can catch them based on type in a hierarchical manner and access their member variables like you'd access any object's member variables. Using a variant for that just complicates the code which actually wants to handle the exceptions rather than simply print out an error message.

- Jonathan M Davis