February 20, 2012
On 2/20/12 3:06 PM, Juan Manuel Cabo wrote:
> I like one golden rule that I have:
>
>   "You should only create a new exception type, if it makes sense to write
>    a  catch(MyNewShinyType ex){}  "

The problem with this is that anyone may come with an example created on the spot where they would, actually, want to catch a specific exception. It's very hard to make non-existential proofs.

> other reasons for creating a new exception class I don't consider them valid
> (but that's just me!).
> This is because, exception types (in my head) are only a way to distinguish
> whether to select them or let them go when I write a catch(), and to
> help the catch recover.  Now, if I can't distinguish the *what* of the error,
> I cannot recover well. The *cause* of the error goes inside the exception
> object, not encoded in the type. Other details of the error go inside of
> the exception object, not encoded in the type name.
>
> So all I care about is the *what* of the error, so that it will fall in
> the correct catch statement. Other criteria obscures that.
>
> The Variant[string] helps keep the hierarchy clean. The hierachy should
> tell the *what* of the error so that I can pick one when writing a catch block.

Don't forget that you always have the option of creating a type on the spot.

void fun()
{
   if (oops)
   {
       class MyException : Exception
       {
          string reasonForOops;
          ...
       }
       throw new MyException; // RTTI will find reasonForOops
   }
}

But after seeing a variety of pros and cons, I tend to agree that the Variant[string] is more attractive.


Andrei

February 20, 2012
On 2/20/12 3:08 PM, Jacob Carlborg wrote:
> On 2012-02-20 20:12, Nick Sabalausky wrote:
>> If so, then I don't see any usefulness of "Variant[string] info" other
>> than
>> to start treating exceptions like JS's abomination of an "object" (Or at
>> least AS2's objects anyway - not 100% certain how much of AS2 is taken
>> from
>> JS). If not, then could you clarify what you meant?
>>
>> In either case, I am interested to hear in more detail how you see
>> "Variant[string] info" being used to address i18n. I haven't really dealt
>> with a lot of i18n myself.
>
> Internationalization in Ruby on Rails works something like this:
>
> # set the locale
> I18n.locale = :se
>
> This will then, at an appropriate time load, a YAML file containing the
> translations, defaults too <rails_app>/config/<locale>.yml. The YAML
> file can look something like this:
>
> se:
> customer:
> address:
> first_name: Förnamn
> last_name: Efternamn
>
>
>
>
> Then to use the translation, it looks like this:
>
> I18n.translate("customer.address.first_name")
>
> Results in "Förnamn".
>
> It's also possible to pass variables to the translation:
>
> se:
> product:
> price: %{price} kr
>
> I18n.translate("product.price", :price => 300)
>
> ":price => 300" is a hash map-literal.

This uses very nicely Ruby's built-in, elegant dynamic type information. The closest thing in D would use a hash Variant[string].


Andrei


February 20, 2012
I forgot to add, that you could define standard details names
as string constants, and even document them in the string constant
definition.

--jm


On 02/20/2012 06:11 PM, Juan Manuel Cabo wrote:
> Yeah.. that is a problem! :-) Thanks for liking the idea, now we can talk about the fine details!!
> 
> One way is to not let the user direct access to the associative array,
> but wrap the e.info["MyDetail"] call in a nothrow function, such as
> e.info("MyDetail"), and an e.hasInfo("MyDetail"), and of course:
> e.addInfo("MyDetail", value) and e.allInfoNames() or something.
> 
> The nothrow function would return an empty value if not found (I fear that it might not be of the same Variant subtype as the Variant value was intended when present).
> 
> --jm
> 
> 
> On 02/20/2012 05:53 PM, H. S. Teoh wrote:
>> 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
>>
> 

February 20, 2012
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?

Barring that, a temporary workaround for lack of RTTI would be to have a template that generates actual fields along with an index of fields via mixins, something like this:

	// NOTE: not actual syntax
	template GenFormattingFields(T..., T fields) {
		...
	}

	class RoxorException : Exception
	{
		GenFormattingFields!(
			string, "filename",
			int, "line",
			int, "column"
		);

		int nonFormattingField;
		...
	}

The template would generate code along these lines:

	// Generated code (not actual syntax)
	class RoxorException : Exception
	{
		string filename;
		int line;
		int column;

		override Variant[string] getFormattingFields() {
			return [
				"filename": filename,
				"line": line,
				"column": column
			];
		}

		int nonFormattingField;
		...
	}

where getFormattingFields is a virtual function defined by Exception. Then you can get the fields for formatting at runtime, *and* have static type checking of direct accesses to RoxorException.filename, etc., at compile-time.


T

-- 
Trying to define yourself is like trying to bite your own teeth. -- Alan Watts
February 20, 2012
On 2/20/12 3:14 PM, deadalnix wrote:
> Le 20/02/2012 22:05, Andrei Alexandrescu a écrit :
>> On 2/20/12 2:31 PM, 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.
>>> 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.
>>
>> Good point. One thing somewhat particular to D, you can always throw a
>> new exception linked to the current one.
>>
>
> And this is great design.

I'm not so sure. It's more like an incompetent design by someone who obviously knew nothing about exceptions.

> We should continue in that direction.

That has its own disadvantages, particularly the proliferation of types simply to embody state, but it does work.


Andrei
February 20, 2012
On Mon, Feb 20, 2012 at 09:45:50PM +0100, deadalnix wrote:
> I don't think the Condition should be an Exception. I think it should provide a method that throw.
> 
> class Condition {
>     void throw() pure {           // Or make it abstract
>         throw new Exception();
>     }
> }
> 
> If no handler is found, this method is called. The handler can also call it.
> 
> It is very important to separate the Condition and the Exception. Several Condition may use the same Exception.

Good point. Conflating the two will only lead to bad design and confusion later on. Best to keep them separate. So Conditions will be an alternative to Exceptions, and if they are not handled, then revert back to throwing an Exception like before.


> BTW, I don't think that something that can be implemented as lib should be added in the core language.

OK.

Although if we implement it as a lib, then we'll need to "abuse" the exception throwing mechanism in order to handle error recovery strategies. For example:

	auto func() {
		try {
			retryOperation1:
			...
			raise(new MyCondition);
			...
			retryOperation2:
			...
			raise(new MyOtherCondition);
			...
		} catch(MyConditionRetry e) {
			goto retryOperation1;
		} catch(MyOtherConditionRetry e) {
			goto retryOperation2;
		}
	}

Alternatively, we can use some mixins that generate goto's, maybe something like this:

	template BeginRetryBlock(...) {...}
	template EndRetryBlock(...) {...}
	template RecoveryBlock(...) {...}
	template RestartBlock(...) {...}

	auto func() {
		BeginRetryBlock!("retryblock1");
			int some_parameter;
			if (!operation1())
				raise(new MyCondition);

		RecoveryBlock!(MyCondition.Strategy1)
		{
			RestartBlock!("retryblock1");
		}
		RecoveryBlock!(MyCondition.Strategy2)
		{
			fiddleWith(some_parameter);
			RestartBlock!("retryblock1");
		}
		EndRetryBlock!();

		return result;
	}

The templates translate func() into something like this:

	// generated code
	auto func() {
		retryblock1:
			RecoveryStrategy __rs = void;

			int some_parameter;
			if (!operation1()) {
				__rs = __raise(new MyCondition);
				goto __recoveryBlock;
			}
			goto __success;

		__recoveryBlock:
			if (cast(MyCondition.Strategy1) __rs)
			{
				goto retryblock1;
			}

			if (cast(MyCondition.Strategy2) __rs)
			{
				fiddleWith(some_parameter);
				goto retryblock1;
			}

			// No matching strategy, give up
			throw __currentCondition.getException();

		__success:
			return result;
	}

This way, we don't need to use throw until we actually have to.


> Condition may be use using something like raise(new
> MyCondition(params)); with raise from std.condition .
> 
> Alternatively, we could go metaprogramming for Condition, just like we did for Range.
[...]

I think metaprogramming is the way to go, otherwise this feature will require too much typing to achieve something simple, and people won't like to use it.


T

-- 
We are in class, we are supposed to be learning, we have a teacher... Is it too much that I expect him to teach me??? -- RL
February 20, 2012
On Mon, Feb 20, 2012 at 03:12:08PM -0600, Andrei Alexandrescu wrote:
> On 2/20/12 2:53 PM, H. S. Teoh wrote:
> >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:
> [snip]
> 
> Yah, it does look we need to better investigate the Variant[string]
> info() primitive.
[...]

I still don't like the idea of using Variant[string], though.

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

(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. By the time it gets to the final catch() block, you cannot guarantee a particular field you depend on will be defined. 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.

So your catch block degenerates into a morass of if-then-else conditions. 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.

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.

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.

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.


T

-- 
One disk to rule them all, One disk to find them. One disk to bring them all and in the darkness grind them. In the Land of Redmond where the shadows lie. -- The Silicon Valley Tarot
February 20, 2012
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>

>
>> As far as I can see, the former has no benefits over the simpler latter option.
>>
>
> The benefit is that you don't have to create any boilerplate ctors in the
> definition of AcmeException.
>
> *However*, I think that:
>
> 1. That's an insufficient improvement to justify breaking the ultra-common
> "throw new NameOfException(args)" idiom.
>
> 2. The solution fails to cover the *entire* scope of the *real* problem:
> Classes that need to write boilerplate ctors which merely forward to the
> base class's ctors.  This issue is *not* limited to Exceptions, but Andrei's
> proposes solution *only* covers the case with Exceptions.
>
> A better solution has already been proposed:
>
>     class AcmeException : Exception
>     {
>         mixin inheritCtors!();  // Actual name open for bikeshedding
>     }
>
> Now, yes, this *is* admittedly more boilerplate than:
>
>     class AcmeException : Exception {}
>
> However, it's superior overall, because:
>
> 1. The solution is *contained* to the author of the exception's class. It's
> *not* a solution that leeks out and infects user code.
>
> 2. It solves the *whole* problem in the general case, not just for
> Exceptions.
>
> And you know what? Even if it's still deemed too much bolierplate, we can
> still just do this:
>
>     mixin( createInheritedClass("AcmeException", "Exception") );
>
> Or maybe this:
>
>     // Second param is optional.
>     // Not sure if this can be a template mixin or has to be string mixin.
>     mixin createException!("AcmeException", "Exception");

I completely agree with the above. Clearly Andrei's template solution here is insufficient and the bigger issue is of course the pollution of user code.

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

February 20, 2012
On Monday, February 20, 2012 15:12:08 Andrei Alexandrescu wrote:
> On 2/20/12 2:53 PM, H. S. Teoh wrote:
> > 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:
> [snip]
> 
> Yah, it does look we need to better investigate the Variant[string]
> info() primitive.

In my experience, the type is very much tied to the context. 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.

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.

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.

- Jonathan M Davis
February 20, 2012
> I still don't like the idea of using Variant[string], though.
> 
> (1) It doesn't allow compile-time type checking. This is a big minus, in
> my book.

When you need compile-time type checking, define a variable in your class.
Just make sure that you are creating that new exception class for a good reason.

When a user needs to add a variable to the exception, he can add it without putting your exception class chained in a new type of exception, that will hide your class from being selected by upstream catch blocks in the call tree.

> 
> (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?

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

If I pollute the exception hierarchy, then catching by exception type
becomes convolluted. It becomes difficult for an exception to fall
in the right catch. And I think that is a worst problem than
not being sure if a piece of data is in the extra info.

Data is data. Types are types. Exceptions should be typed the best way
possible that will allow me to select them and fall in the right
catch block. And that is the *what* of the error, not the data of
the error.


> 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.
> 
> So your catch block degenerates into a morass of if-then-else conditions. 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.
> 
> 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.
> 
> 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.
> 
> 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.
> 
> 
> T
> 


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.

--jm