February 20, 2012
On 02/20/2012 04:44 PM, Sean Kelly wrote:
> On Feb 20, 2012, at 11:44 AM, deadalnix wrote:
> 
>> That wouldn't work, because you'll erase the stacktrace.
> 
> It wouldn't be difficult to not overwrite a stack trace if one already exists on throw.

That would be very nice!!

Java doesn't have a stacktrace reset problem:

I tried the following in java. You can see by the output
that the stacktrace of the exception object is
preserved (I didn't leave blank lines on purpouse so you can
count the line number shown in the output unequivocally):

public class bla {
	public static void main(String[] args) throws Exception {
		anotherfunc();
	}
	public static void anotherfunc() throws Exception {
		try {
			System.out.println("another func");
			badfunc();
		} catch (Exception ex) {
			//rethrow the same exception:
			throw ex;
		}
	}
	public static void badfunc() throws Exception {
		System.out.println("bad func");
		throw new Exception("badfunc");
	}
}

another func
bad func
Exception in thread "main" java.lang.Exception: badfunc
        at bla.badfunc(bla.java:16)
        at bla.anotherfunc(bla.java:8)
        at bla.main(bla.java:3)

--jm

February 20, 2012
"Andrei Alexandrescu" <SeeWebsiteForEmail@erdani.org> wrote in message news:jhucla$1tuf$7@digitalmars.com...
> On 2/20/12 2:45 PM, Jacob Carlborg wrote:
>> 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.
>
> How did you infer that? I don't want to put internationalization into exceptions /at all/.
>

You've suggested adding "Variant[string] info" to Exception for the sake of i18n. I think that's what he's referring to. You *could* argue that's not technically i18n, but so far i18n seems to be the only real use-case for it (although even that much has been disputed in light of reflection).


February 20, 2012
On Monday, February 20, 2012 17:22:13 Andrei Alexandrescu wrote:
> 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.

A particular exception type says that a particular thing happened, and that's almost always tied to a particular set of information. If you have a SocketException, then there's always an IP and port involved no matter what function it's thrown from and _that_ context doesn't change. Certain types of data almost always go with certain problems and therefore certain exception types.

If you mean context in terms of where it's being thrown from, then no, that's not generally connected at all.

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

Not really. You put them in there if you need them. And shifting them into an AA of Variants doesn't help any. It just reduces the size of the class itself. You still have to set them, and code that uses them still has to reference them. You also end up using more memory.

And when it comes to exceptions, having a few more pieces of data that you don't always need but might be useful doesn't really cost you much, because you don't construct them often, and you're not going to have very many instantiated at once. They almost always have a very short lifespan. I'd be far more worried about restricting the number of member variables in types which are going to actually be commonly instantiated and potentially have many of them in memory. We obviously shouldn't go to town adding everything and the kitchen sink to every exception type, but having a few extra member variables on them doesn't seem like a big deal to me.

- Jonathan M Davis
February 20, 2012
"Andrei Alexandrescu" <SeeWebsiteForEmail@erdani.org> wrote in message news:jhub5b$1rj5$2@digitalmars.com...
> On 2/20/12 1:13 PM, Sean Kelly wrote:
>> but either way I think the point of the table is for
>> localized error messages.  I wouldn't expect any data relevant for
>> filtering the exception within the table.
>
> Exactly.
>
> The need for Variant instead of string is mainly to distinguish numeric info from string info when doing things like singular vs. plural or Arabic numeral rendering vs. textual ("one", "two" etc).
>

So like Jacob mentioned and you denied, you *are* advocating putting at least some i18n into exceptions.


February 21, 2012
"Andrei Alexandrescu" <SeeWebsiteForEmail@erdani.org> wrote in message news:jhuccs$1tuf$4@digitalmars.com...
> 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).
>

It's easy to always be right when you twist your argument after-the-fact to match what was eventually agreed.


February 21, 2012
"foobar" <foo@bar.com> wrote in message news:rsfpoesjcefsiwxoxgzm@forum.dlang.org...
> On Monday, 20 February 2012 at 20:18:58 UTC, Nick Sabalausky wrote:
>> "foobar" <foo@bar.com> wrote in message news:uphvtoqkvtshnzlqoaus@forum.dlang.org...
>>>
>>> I meant -
>>> what's the benefit of:
>>> throw createEx!AcmeException("....");
>>> vs.
>>> throw new AcmeException("....");
>>>
>>
>> Fixed.
>
> Huh? <confused>
>

You forgot the "new" in the second example. I make that mistake in my own code all the time :)

>
> I just want to add to the above valid points one comment:
> instead of the proposed inheritCtors D could e perhaps modified to make
> ctors more uniform with other methods. ctors could be automatically
> inherited if sub class does not define its own ctors and does not add new
> fields.
>
> the above would simply become:
>
> class AcmeException : Exception {} // inherits super ctors automatically
>

I often forget that doesn't already happen. I think what confuses me is that non-inherited classes implicitly define "this(){}" if there's no other ctors (Which is very handy though. Haxe doesn't do that which I always find irritating).


February 21, 2012
"Andrei Alexandrescu" <SeeWebsiteForEmail@erdani.org> wrote in message news:jhuaue$1rj5$1@digitalmars.com...
> On 2/20/12 1:04 PM, foobar wrote:
>> On Monday, 20 February 2012 at 17:47:35 UTC, Andrei Alexandrescu wrote:
>>> On 2/20/12 11:32 AM, foobar wrote:
>>>> On Monday, 20 February 2012 at 17:12:17 UTC, Andrei Alexandrescu wrote:
>>>>> On 2/20/12 11:08 AM, Mafi wrote:
>>>>>> If it's supposed to be simple factorization, then you should replace
>>>>>> "throw r" with "return r". Then the name of that function doesn't
>>>>>> make
>>>>>> much sense anymore. But then you can better search for throw in user
>>>>>> code and the stack traces aren't obfuscated anymore.
>>>>>>
>>>>>> throw createEx!AcmeException("....");
>>>>>
>>>>> I think that's a great idea, thanks.
>>>>>
>>>>> Andrei
>>>>
>>>> I fail to see the point in this. Why is the above better than
>>>> throw AcmeException("....");
>>>>
>>>> If you want to avoid boilerplate code in the definition of AcmeException, this can be better accomplished with a mixin.
>>>
>>> The advantage is that e.g. the compiler can see that flow ends at throw. Other languages have a "none" type that function may return to signal they never end.
>>>
>>> Andrei
>>
>> I meant -
>> what's the benefit of:
>> throw createEx!AcmeException("....");
>> vs.
>> throw AcmeException("....");
>>
>> As far as I can see, the former has no benefits over the simpler latter option.
>
> That's simply a workaround for non-inherited constructors - nothing more should be read into it.
>

The issue is that it's an ugly and unnecessary workaround. A couple alternates have been suggested.


February 21, 2012
On 2/20/12 5:46 PM, Nick Sabalausky wrote:
> "Andrei Alexandrescu"<SeeWebsiteForEmail@erdani.org>  wrote in message
> news:jhucla$1tuf$7@digitalmars.com...
>> On 2/20/12 2:45 PM, Jacob Carlborg wrote:
>>> 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.
>>
>> How did you infer that? I don't want to put internationalization into
>> exceptions /at all/.
>>
>
> You've suggested adding "Variant[string] info" to Exception for the sake of
> i18n. I think that's what he's referring to. You *could* argue that's not
> technically i18n, but so far i18n seems to be the only real use-case for it
> (although even that much has been disputed in light of reflection).

All formatting and rendering of the exception information is helped. And I think that's great because that's the most common activity one would do with exceptions.

Andrei


February 21, 2012
On Mon, Feb 20, 2012 at 05:15:17PM -0600, Andrei Alexandrescu wrote:
> On 2/20/12 4:04 PM, H. S. Teoh wrote:
[...]
> >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.

Except that if the information you depend on is only set in the Variant[string], then you'd have to check and rethrow.


[...]
> >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?

Not each possible control flow, but each meaningful exception type, i.e., at some intermediate level, you catch ConversionError and throw IntOverflowError, as you suggested. You don't need a separate type for every possible place where ConversionError gets thrown.


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

Formatting should use class reflection. We already discussed that, and we already agreed that was the superior approach.

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.

The problem with using Variant[string] is that everything gets lumped into one Exception object, and there's no way to only catch the Exception that happens to have variables "p", "q", and "r" set in the Variant[string]. You have to catch an exception type that includes all sorts of combinations of data in Variant[string], then manually do tests to single out the exception you want, and rethrow the rest. That's where the ugliness comes from.


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

Again, we've already agreed class reflection is the proper solution to this one.


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

Agreed. But it shouldn't be the be-all and end-all of data passed in exceptions. If anything, it should only be rarely used, with most exception classes using static fields to convey relevant information.

I can see the usefulness of using Variant[string] as a way of "decorating" exceptions with "extra attributes", but it shouldn't be the primary way of conveying information from the throw site to the catch site.

As for iterating over the information in the most derived class, for formatting, etc., class reflection is the way to go. We shouldn't be using Variant[string] for this, because there's another problem associated with it: suppose MyException sometimes gets "extra_info1" and "extra_info2" tacked onto it, on its way up the call stack, and sometimes not. Now what should the catcher do? How do you format this exception? Should the format string include extra_info1 and extra_info2, or not? If it doesn't, what's the use of this extra info? If it does, what happens if these fields are missing?

This is what I mean by not being able to depend on whether some data is there. Ultimately, to do anything useful with the info in the object, you need to know what's there. Preferably, the object's type will tell you exactly what's there, then you do a simple map from type to list of available attributes (e.g., map exception type to format string with known, static list of attributes). But if the type doesn't guarantee what data will be present, then your code becomes vastly more complex, you have to deal with potentially all possible combinations of what's there and what isn't. Instead of a single format string for a single exception type, you now have a combinatorial explosion of format strings for every possible combination of missing/present fields in the exception object.

Just because the catch block can just blindly hand this Variant[string] over to the formatter doesn't solve the problem. It merely moves the complexity to the formatter, which *still* has to deal with what happens if what the format string expects to be there isn't there.


T

-- 
Public parking: euphemism for paid parking. -- Flora
February 21, 2012
On 2/20/12 5:51 PM, Nick Sabalausky wrote:
> "Andrei Alexandrescu"<SeeWebsiteForEmail@erdani.org>  wrote in message
> news:jhub5b$1rj5$2@digitalmars.com...
>> On 2/20/12 1:13 PM, Sean Kelly wrote:
>>> but either way I think the point of the table is for
>>> localized error messages.  I wouldn't expect any data relevant for
>>> filtering the exception within the table.
>>
>> Exactly.
>>
>> The need for Variant instead of string is mainly to distinguish numeric
>> info from string info when doing things like singular vs. plural or Arabic
>> numeral rendering vs. textual ("one", "two" etc).
>>
>
> So like Jacob mentioned and you denied, you *are* advocating putting at
> least some i18n into exceptions.

No. I am simply enabling a variety of applications, with i18n as an obvious one. Encoding numbers as strings may force decoding later. It's just unnecessary.

Andrei