February 20, 2012
On Monday, 20 February 2012 at 21:02:21 UTC, Andrei Alexandrescu wrote:
> On 2/20/12 2:01 PM, foobar wrote:
>> Seconded. Reflection seems a much better solution for this.
>
> Absolutely.
>
>> The i18n table would map (exception type) -> (language, format string)
>> and the i18n formatter would use reflection to map field values to the
>> format string.
>> a format string would be e.g. "File {filename} not found" and the
>> formatter would replace {filename} with e.class.getField("filename").
>>
>> This does not require any modifications to the exception mechanism.
>
> Yah, but the reflection engine would have to present RTTI in a uniform manner... such as, Variant[string]. Bane of my life: I'm always right :o).
>
>
> Andrei

Unfortunately you aren't :)
The Variant[string] approach causes coupling between two separate concerns: i18n and error handling. Reflection does *not* cause such coupling. More over, Reflection is a general language mechanism with many benefits such as allowing the user to use the i18n library with other non exception types.
Lastly, good RTTI is hardly a simple Variant[string]. Java for instance has pretty good RTTI API whereas the c++ one is horrible (and incomplete).

Conclusion - *not* the same thing!
February 20, 2012
On Mon, Feb 20, 2012 at 07:44:30PM -0300, Juan Manuel Cabo wrote: [...]
> > (2) It's overly flexible. Anyone along the call stack can insert
> > (hopefully NOT delete!!) additional data into the Exception object, as
> > the stack is unwound.
> 
> As is currently the case.
> Did you know that anyone can overwrite any field of the exception and
> rethrow it? Such as msg field and so on?

This is an implementation bug. Exceptions should always be const in the catch block. I believe this issue has been filed, and will eventually be fixed.


> > By the time it gets to the final catch() block, you cannot guarantee a particular field you depend on will be defined.
> 
> If you want to guarantee it, then use a plain old variable for that piece of data.
> 
> I just would like a way to add data to an exception without creating a new type.  If I create a new exception type for the wrong reasons, I'm polluting the exception hierarchy.

Point taken.

So I think what we should have is *both* data stored in fields in Exception subclasses, and some kind of way to attach auxilliary data to the exception. Say with Variant[string], or whatever way you prefer.

But Variant[string] should not be used for *everything*. That only leads to problems. But then, it limits the usefulness of Variant[string], because then you can't just pass it to the i18n formatter, since now some fields are static but they may need to be part of the formatted message.

So we haven't really solved anything, we just added a new feature to Exception which I'm not sure how useful it is. Do you have actual use cases that requires adding data to exceptions? Without concrete examples we're just arguing about hypotheticals.


T

-- 
This is not a sentence.
February 20, 2012
> Do you have actual use
> cases that requires adding data to exceptions? Without concrete examples
> we're just arguing about hypotheticals.

I posted a few hypothetical cases during in the thread, but this is one long megathread!

I'm not facing an urgent need right now for the Variant[string] capability.

I just realized that the majority of twisted exception hierarchies are grown trying to encode transversal traits of the exceptions in their types names.

The exception types should encode only one thing: the error *what*.

Now, for real use cases for the Variant[string], you just have to look around and they are everywhere.

For instance: a C library wrapper, which gets the library errors encoded as some error code and throws them as exceptions. Shouldn't the library throw a FileNotFoundException when that's the error, instead of throwing a LibraryException that has the error code in a field?

So the correct thing to do is: after a library call, the wrapper
checks the last error code number with a switch statement, and deciding
which standard exception type to throw (defaulting to whatever you like
if the error code doesn't map to a standard D exception). Then you
add the error code to the Variant[string].


That way, exception types can be standard.

So, to keep D exception types standard reusable and respected by future code, you must follow the Open-Closed design principle (nicest principle of OO design ever).

--jm



On 02/20/2012 07:57 PM, H. S. Teoh wrote:
> On Mon, Feb 20, 2012 at 07:44:30PM -0300, Juan Manuel Cabo wrote: [...]
>>> (2) It's overly flexible. Anyone along the call stack can insert
>>> (hopefully NOT delete!!) additional data into the Exception object, as
>>> the stack is unwound.
>>
>> As is currently the case.
>> Did you know that anyone can overwrite any field of the exception and
>> rethrow it? Such as msg field and so on?
> 
> This is an implementation bug. Exceptions should always be const in the catch block. I believe this issue has been filed, and will eventually be fixed.
> 
> 
>>> By the time it gets to the final catch() block, you cannot guarantee a particular field you depend on will be defined.
>>
>> If you want to guarantee it, then use a plain old variable for that piece of data.
>>
>> I just would like a way to add data to an exception without creating a new type.  If I create a new exception type for the wrong reasons, I'm polluting the exception hierarchy.
> 
> Point taken.
> 
> So I think what we should have is *both* data stored in fields in Exception subclasses, and some kind of way to attach auxilliary data to the exception. Say with Variant[string], or whatever way you prefer.
> 
> But Variant[string] should not be used for *everything*. That only leads to problems. But then, it limits the usefulness of Variant[string], because then you can't just pass it to the i18n formatter, since now some fields are static but they may need to be part of the formatted message.
> 
> So we haven't really solved anything, we just added a new feature to Exception which I'm not sure how useful it is. Do you have actual use cases that requires adding data to exceptions? Without concrete examples we're just arguing about hypotheticals.
> 
> 
> T
> 

February 20, 2012
On 2/20/12 4:04 PM, H. S. Teoh wrote:
> On Mon, Feb 20, 2012 at 03:12:08PM -0600, Andrei Alexandrescu wrote:
> I still don't like the idea of using Variant[string], though.

I don't like it, either. I mean not "like" like. It's an approach suggested by necessity.

> (1) It doesn't allow compile-time type checking. This is a big minus, in
> my book.

In mine, too. Literally. We're on the same boat.

> (2) It's overly flexible. Anyone along the call stack can insert
> (hopefully NOT delete!!) additional data into the Exception object, as
> the stack is unwound.

But that's a plus. It means the approach scales up to any number of control flows, of which there are combinatorially many. Defining one type for each... well you wouldn't "like" that, either.

> By the time it gets to the final catch() block,
> you cannot guarantee a particular field you depend on will be defined.

Indeed. If you depend on anything you'd want to catch the specific type.

> Say if your call graph looks something like this:
>
> 	main()
> 	  +--func1()
> 	      +--func2()
> 	      |   +--helperFunc()
> 	      |   +--func3()
> 	      |       +--helperFunc()
> 	      +--func4()
> 	          +--helperFunc()
>
> Suppose helperFunc() throws HelperException, which func1's catch block
> specifically wants to handle. Suppose func2() adds an attribute called
> "lineNumber" to its catch block, which then rethrows the exception, and
> func3() adds an attribute called "colNumber".
>
> Now how should you write func1()'s catch block? You will get all
> HelperException's thrown, but you've no idea from which part of the call
> graph it originates.
> If it comes from func3(), then you have both
> "lineNumber" and "colNumber". If it comes before you reach func3(), then
> only "lineNumber" is defined. If it comes from func4(), then neither is
> present.

Exactly. So you suggest adding one type for each possible control flow? Are you sure this scales beyond a toy example?

> So your catch block degenerates into a morass of if-then-else
> conditions.

No, precisely on the contrary. You catch blockS degenerate into a morass of catch (This) { ... } catch (That) { ... } catch (TheOther) { ... }. That is fine if the code in different "..." does very different things, but it's a terrible approach if all do the same thing, such as formatting. That shouldn't make anyone feel better than using a morass of if/else.

The code with Variant[string] does not need combinatorial testing if it wants to do a uniform action (such as formatting). It handles formatting uniformly, and if it wants to look for one particular field it inserts a test.

> And then what do you do if you're depending on a particular
> field to be set, but it's not? Rethrow the exception? Then you have the
> stack trace reset problem.

Don't forget that Variant[string] does not preclude distinct exception types. It's not one or the other.

> Whereas if HelperException always has the the same fields, the catch
> block is very straightforward: just catch HelperException, and you are
> guaranteed you have all the info you need.

HelperException can definitely be there. It can only help if there's additional information associated with it.

> Then if func3() wants to add more info, create a new exception derived
> from HelperException, and add the field there. Then in func1(), add a
> new catch block that catches the new exception, and makes use of the new
> field.

They call that non-scalable code bloat.

> This does introduce a lot of little exception classes, which you could
> argue is class bloat, but I don't see how the Variant[string] method is
> necessarily superior. It comes with its own set of (IMHO quite nasty)
> problems.

Variant[string] is not superior because it doesn't compete against anything. It's a simple addition to the primitives available to the base Exception class.


Andrei
February 20, 2012
On 2/20/12 4:25 PM, Jonathan M Davis wrote:
> In my experience, the type is very much tied to the context.

I thought you said the type is tied to "what happened, not where it happened", which is the polar opposite of the above.

> When a particular
> type of error occurs, there's a particular set of information that goes with
> that, and that doesn't generally change. So, you don't end up with a bunch of
> types which are solely there to add additional information.

But on the way up there's additional contextual information. "Total amount must be a positive number" is more descriptive "Conversion error", although it originated as the latter. Insisting that all that must be encoded as types seems overly rigid, not to mention non-scalable.

> Using variant means moving to dynamic typing and problems that you don't see
> until runtime, whereas having the data as direct member variables is
> statically checked.

That's a given. However I think the most frequent use of exception interfaces is to extract data for formatting purposes, and the exception interface would do good to help with that.

> Having the ability to add extra information via Variant[string] may not be a
> problem, but doing that for everything _would_ be. It's far more bug prone,
> and the fact that it would be error handling code which would have the bugs
> would make them _far_ harder to catch and fix.
>
> I definitely think that we should favor putting data in member variables, not
> in a hashtable of Variants.

Whenever you put data in member variables you set yourself up for code bloat. Do you agree?


Andrei
February 20, 2012
On 2/20/12 4:46 PM, foobar wrote:
> On Monday, 20 February 2012 at 21:02:21 UTC, Andrei Alexandrescu wrote:
>> On 2/20/12 2:01 PM, foobar wrote:
>>> Seconded. Reflection seems a much better solution for this.
>>
>> Absolutely.
>>
>>> The i18n table would map (exception type) -> (language, format string)
>>> and the i18n formatter would use reflection to map field values to the
>>> format string.
>>> a format string would be e.g. "File {filename} not found" and the
>>> formatter would replace {filename} with e.class.getField("filename").
>>>
>>> This does not require any modifications to the exception mechanism.
>>
>> Yah, but the reflection engine would have to present RTTI in a uniform
>> manner... such as, Variant[string]. Bane of my life: I'm always right
>> :o).
>>
>>
>> Andrei
>
> Unfortunately you aren't :)
> The Variant[string] approach causes coupling between two separate
> concerns: i18n and error handling.

Not at all. It simply exposes information for any rendering purposes. In fact I imagine a simple renderer would just display to the developer the message formatted as a simple table with <name, value> for each property.

> Reflection does *not* cause such
> coupling. More over, Reflection is a general language mechanism with
> many benefits such as allowing the user to use the i18n library with
> other non exception types.

Sure. The question is whether reflection is enough. Juan made the case that dynamic paths would want to enhance an exception with additional information. Using reflection for that would be forced.

> Lastly, good RTTI is hardly a simple Variant[string]. Java for instance
> has pretty good RTTI API whereas the c++ one is horrible (and incomplete).
>
> Conclusion - *not* the same thing!

Yah, full-blown RTTI would be a fair amount more.


Andrei
February 20, 2012
On 2/20/12 4:46 PM, foobar wrote:
> On Monday, 20 February 2012 at 21:02:21 UTC, Andrei Alexandrescu wrote:
>> On 2/20/12 2:01 PM, foobar wrote:
>>> Seconded. Reflection seems a much better solution for this.
>>
>> Absolutely.
>>
>>> The i18n table would map (exception type) -> (language, format string)
>>> and the i18n formatter would use reflection to map field values to the
>>> format string.
>>> a format string would be e.g. "File {filename} not found" and the
>>> formatter would replace {filename} with e.class.getField("filename").
>>>
>>> This does not require any modifications to the exception mechanism.
>>
>> Yah, but the reflection engine would have to present RTTI in a uniform
>> manner... such as, Variant[string]. Bane of my life: I'm always right
>> :o).
>>
>>
>> Andrei
>
> Unfortunately you aren't :)

Esprit d'escalier! "Bane of my life: nobody admits I'm always right."

Damn I wish I'd come up with that a minute ago before submitting.


Andrei

February 20, 2012
On 2/20/12 4:57 PM, H. S. Teoh wrote:
> So I think what we should have is *both* data stored in fields in
> Exception subclasses, and some kind of way to attach auxilliary data to
> the exception. Say with Variant[string], or whatever way you prefer.
>
> But Variant[string] should not be used for *everything*. That only leads
> to problems. But then, it limits the usefulness of Variant[string],
> because then you can't just pass it to the i18n formatter, since now
> some fields are static but they may need to be part of the formatted
> message.

Great. I'll plant the interface and submit it for destruction.

> So we haven't really solved anything, we just added a new feature to
> Exception which I'm not sure how useful it is. Do you have actual use
> cases that requires adding data to exceptions? Without concrete examples
> we're just arguing about hypotheticals.

I'm seeing plenty of that in our code base at work. They use exception wrapping in conjunction with a fair amount of contortions because the Variant[string] approach has not been designed.


Andrei
February 20, 2012
"Jose Armando Garcia" <jsancio@gmail.com> wrote in message news:mailman.672.1329758926.20196.digitalmars-d@puremagic.com...
>
> 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?
>

I have some comments on that, but first a little story:

At a previous job with a company that shall remain unnamed, our flagship product was this VB6 thing that handled i18n by wrapping all strings intended to be seen by the user like this:

XLate("Hello, all ") + (UBound(customerArr)-LBound(customerArr)) + XLate(" of you suckers!")

For non-english users, that XLate function would then lookup the right localization for the given message. IIRC, any missing translation just returned the original english text.

This was the first time I had actually dealt with i18n, and yet I was pretty certain that was a completely moronic approach (not that it was a stretch to believe: there wasn't much going on at main sequence techn...errmm...I mean at unnamed company, that wasn't completely moronic).

I always felt it would be far better ("better" == "won't screw up everytime someone sneezes near the code" and "doesn't make certain things impossible to translate correctly") to have a statically-checked *identifier* that represented a particular message (and not message fragments, either). And then the user-facing string would be looked up via identifier and any appropriate values substituted in. A fairly obvious way to improve^Wfix the system, really.

Ok, end of story.

Your mention of gettext, and specifically a "gettext 'compiler'", does introduce a way that the same basic idea could be made to actually work:

Obviously, the big problems with XLate above are:

1. No way to make sure all messages actually *are* translated (or to prune unused translations).

2. Changing the english text embedded in the code will automatically break all translations.

3. It fails to handle cases where two inserted values need to be rearranged.

4. If two messages happen to have a common message fragments, the fragments will always be translated the same even if their differing context dictates they be translated differently

The issues #3 and #4 can obviously be solved by using only full messages with named placeholders and passing the substitution data into xlate(). So those issues are alredy out of the way.

By "gettext 'compiler'" I assume you're taking about some preprocessor. Probably appropriate for C/C++, but seems kinda sloppy, potentially error prone, and inefficient. Fortunately, D has kick-ass metaprogramming, so if you require all translations be compiled in (not sure how realistic that is in general), you could do:

xlate!"Hello world"

And then generate a compile-time error (or a mere notice) for all messages that are unhandled. Of course, if you're loading localizations at runtime, you could still do something similar and just throw a runtime error, or log a warning, etc., that lists all unhandled messages upon loading a localization. Or, hell, a hybrid approach: Compile-time errors for built-in localizations and runtime for the rest. Or generate a list of all messages when compiled with a special version(), which is then handed off to the translation team. All sorts of possibilities. That's all sufficient to solve #1 and #2.

Although there is still the other problem Andrei mentioned of over-eagerness (not an issue in the compile-time case, though).


February 20, 2012
"H. S. Teoh" <hsteoh@quickfur.ath.cx> wrote in message news:mailman.710.1329769408.20196.digitalmars-d@puremagic.com...
>
> Unfortunately, I don't think D supports runtime reflection yet. At least, not to the point we need (accessing field values of a general Object).
>
> Although, as some have said, it's possible to work around this by using .tupleof, which is still a better solution than Variant[string], because at least you'd have compile-time type checking.
>

...And compile-time *existence* checking.