February 20, 2012
Le 20/02/2012 21:12, Gerrit Wichert a écrit :
> On 19.02.2012 01:40, H. S. Teoh wrote:
>> One word: internationalization. Then toString() falls flat on its
>> face. You can't even fix this by having Phobos do the translation
>> internally. You can't expect users of Phobos to only ever support
>> languages that Phobos has been previously translated to, for one
>> thing. That would be a crippling disability. T
>
> Its even better, i want the exception message to be shown to the user in
> the current locales language.
> But for the logs i want it to be in english since i have to maintain
> systems in other countries we sold our software to.
> Its easy to connect to a remote system on the other side of the world,
> but its not so easy to read log files in a foreign
> language.

grep
February 20, 2012
On 2012-02-20 19:47, Andrei Alexandrescu wrote:
> 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

It sure seems you want to but internationalization into exceptions.

-- 
/Jacob Carlborg
February 20, 2012
On 2/20/12 1:15 PM, H. S. Teoh wrote:
> I think what Andrei wants to do is more along these lines:
>
> 	catch(Exception e) {
> 		writeln(formatLocaleString(e.fmt, e.data));
> 	}

Appproximately, except the formatting string is not inside the exception.

> I think there's some merit to this idea. However, I'm still unsure about
> storing stuff in a Variant.
>
> For one thing, you either need some sort of format string in the
> exception object (as I have above), which is bad, as somebody else
> pointed out, because now you're mixing i18n code into exception code, or
> you need some way of figuring out what format to use for which
> exception. So ultimately, you'll still end up with with a huge switch
> statement,

no

> or a global table of all exceptions (not good for
> maintenance, now every time someone changes an exception he has to
> remember to update the table).

Tables (including database-store ones etc.) are a way of life in international applications.

> One solution, perhaps, is to have an i18n file containing mappings of
> exception types to message formats. So that you can have:
>
> 	class Exception : Error {
> 		Variant[string] info;
> 		...
> 	}
>
> 	string[string] exceptionFormats = loadLocaleData();
>
> 	string formatExceptionMsg(LocaleInfo li, Exception e) {
> 		if (typeid(e).name in exceptionFormats) {
> 			return format(exceptionFormats[typeid(e)],
> 				e.info);
> 		}
> 		return e.msg;
> 	}

That's quite sensible.

> This may be acceptable, if the catching code knows which formats are
> actually defined in the locale data, so it will only call
> formatExceptionMsg() if there's actually a format defined for it. This
> way, you don't need to have *every* exception translated, just those
> that your catching code will actually use.

Yah.

> The one objection I have with this, though, is that if the catching code
> wants to specifically catch, say, FileNotFoundException, and extract the
> offending filename from the exception object, it would have to do a
> string lookup in Exception.info, rather than just accessing
> FileNotFoundException.filename directly.

Speed of rendering exception messages is secondary, but point taken.

> Then if the Phobos maintainer renames the field, the code still compiles
> since the compiler has no way to know that Exception.info["filename"] is
> no longer set by the ctor of FileNotFoundException, so the problem will
> go undetected until the exception actually occurs, at which time the
> catching code gets a runtime error (very bad).
>
> Having the field directly in FileNotFoundException is better, because
> you get a compile-time error when the field is renamed, so the developer
> can fix it before it ships, rather than have the customer run into the
> runtime error.

Yah, it also means there's a lot of duplicated code which may contain other bugs.

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

Runtime reflection is indeed a more general realization of the Variant[string] thingie. A naming convention (e.g. prefix all members in Exception relevant to rendering with "info" would complete a nice scheme.


Andrei
February 20, 2012
On 2/20/12 1:17 PM, Juan Manuel Cabo wrote:
> I like the idea!
>
> Remember please for anyone reading: Use positional arguments in
> format strings.

Not positional, but instead Symbolic is the way.

Andrei

February 20, 2012
On 2012-02-20 19:42, 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.
>
>
> T
>

Currently getMembers doesn't work and if I recall correctly it's not possible to get the value out of what getMembers returns. But that might just be considered implementation issues.

-- 
/Jacob Carlborg
February 20, 2012
On 2/20/12 1:23 PM, deadalnix wrote:
> Why not use tupleof on the Exception instead of Variant[string] ?

The one-liner police shall be alerted.

You can't use tupleof because that's compile-time information.


Andrei
February 20, 2012
On 2/20/12 1:32 PM, Juan Manuel Cabo wrote:
> So, if your boss wants the URL of the request that was made
> when the standard library threw you a FileNotFoundException,
> you can do:
>
>
> 	try {
> 	      ...
>          } catch (Exception ex) {
>                  //Rethrow the exception with the added detail:
> 		ex.details["custom_url"] = getenv("URI");
>                  throw ex;
>          }

That's a very interesting angle!

Andrei


February 20, 2012
On 2/20/12 1:38 PM, H. S. Teoh wrote:
> On Mon, Feb 20, 2012 at 08:23:05PM +0100, deadalnix wrote:
> [...]
>> Why not use tupleof on the Exception instead of Variant[string] ?
>
> +1.
>
> Compile-time type-checking and runtime generic access to fields.  Best
> of both worlds. Excellent idea!

Doesn't scale. You need to literally write one catch handler for each exception type caught.

Andrei
February 20, 2012
On 2012-02-20 18:28, Jose Armando Garcia wrote:
> On Mon, Feb 20, 2012 at 2:37 PM, Andrei Alexandrescu
> <SeeWebsiteForEmail@erdani.org <mailto:SeeWebsiteForEmail@erdani.org>>
> wrote:
>
>     On 2/20/12 10:16 AM, Nick Sabalausky wrote:
>
>         "Andrei Alexandrescu"<SeeWebsiteForEma__il@erdani.org
>         <mailto:SeeWebsiteForEmail@erdani.org>>  wrote in message
>         news:jhtq31$u8q$1@digitalmars.__com...
>
>
>             Again, I think this thread clarified we need the
>             "Variant[string] info;"
>             member however we define the hierarchy.
>
>
>         I disagree. I don't see a need for that.
>
>
>     How would we address custom formatting of e.g. error messages?
>
>
> This may not be D. Gettext says to solve it as follow:
>
> throw new Exception(gettext("Cool English message at
> %s.").format(DateTime.now()))
>
> The gettext "compiler" goes through the code an generates all the
> strings that need to be localized. The translation teams modifies those
> string and you store them in file/map for that language. At runtime the
> i18n library turns gettext(...) into a query into that map and returns
> the actual localized string. There is a map for the entire process.
>
> Localization can also be disable at compile time by making gettext a
> template and generating a "noop" for that operation.
>
> I have been thinking of making a module for i18n model after gettext but
> taking advantage of D's language features. Is there some interest in this?
>
> Thanks,
> -Jose
>

Yes, but not for this purpose.

-- 
/Jacob Carlborg
February 20, 2012
On Mon, Feb 20, 2012 at 05:31:28PM -0300, Juan Manuel Cabo wrote:
> > ...
> > Sure. Again, this is not advocating replacement of exception hierarchies with tables!
> > ...
> > 
> > Andrei
> > 
> 
> I think that the case of rethrowing an exception with added detail is the worst enemy of clean Exception hierarchies.

Hmm. This is a valid point. Sometimes you want to add contextual details to an exception in order to provide the final catching code with more useful information. Otherwise you may end up with a chain of mostly redundant exception classes:

	class UTFError : Exception {...}
	class LexUTFError : LexUTFError {
		int line, col;
		...
	}
	class ConfigFileParseError : LexUTFError {
		string cfgfile_name;
	}

	auto decodeUTF(...) {
		...
		throw new UTFError;
	}

	auto configLexer(...) {
		try {
			...
			decodeUTF(...);
		} catch(UTFError e) {
			throw new LexUTFError(...);
		}
	}

	auto configParser(...) {
		try {
			...
			configLexer(...);
		} catch(LexUTFError e) {
			throw new ConfigFileParseError(...);
		}
	}


> The idea of Variant[string] remedies that case without creating a new exception class just for the added fields. If that case is solved, then the tipical need for creating new exception types that don't really aid selecting them for catching and recovery is solved too.
[...]

However, I still hesitate about using Variant[string]. How would you address the following problem:

	// Module A
	class MyException : Exception {
		this() {
			info["mydetail"] = ...;
		}
	}

	// Module B
	auto func() {
		try {
			...
		} catch(MyException e) {
			if (e.info["mydetail"] == ...) {
				...
			}
		}
	}

If module A's maintainer renames "mydetail" to "detail", then module B will still compile with no problem, but now e.info["mydetail"] doesn't exist and will cause a runtime error at worst. At best, the catch block won't be able to recover from the error as it did before, because now it can't find the info it was looking for.

If "mydetail" had been a field stored in MyException, then module B would get a compile-time error, and the problem can be fixed immediately, instead of going unnoticed until it blows up at the customer's production server.


T

-- 
Some ideas are so stupid that only intellectuals could believe them. -- George Orwell