Exception handling proof-of-concept

Monkey Forums/Monkey Programming/Exception handling proof-of-concept

Samah(Posted 2011) [#1]
I made some modifications to trans just to prove it could be done. Tested HTML5 build in IE8 and FF5.

Failing example:
Function Main:Int()
	Print "trying invalid cast: StringObject(Object(New StringList))"
	Print StringObject(Object(New StringList))
	' program dies with javascript error "TypeError: 'null' is null or not an object"
	' does not print the next line
	Print "continue with the rest of the program"
	Return 0
End

Exception handling example:
Function Main:Int()
	Try
		Print "trying invalid cast: StringObject(Object(New StringList))"
		Print StringObject(Object(New StringList))
		' program doesn't die from the error and instead jumps to the catch block
	Catch
		Print "caught the error"
	End
	' program continues as if nothing is wrong
	Print "continue with the rest of the program"
	Return 0
End

Output:
trying invalid cast: StringObject(Object(New StringList))
caught the error
continue with the rest of the program

And of course if there was no error, the catch block doesn't get executed.

This was REALLY easy to add, and all current targets support some form of exception handling. I've only done the HTML5 translation for now, and the Throw keyword is not implemented. I also need to make the error message available to the developer in the catch block.

Given that this is essentially a fork, I don't want to have to maintain it nor merge it with every major release. Please add this Mark!!!


marksibly(Posted 2011) [#2]
Hi,

It's a bit harder than that - the debug stack state also needs to be tracked, and popped appropriately too.

If there's a big enough demand, I'll consider adding exceptions, but I do not personally like them and could quite happily live without them.

To me, the biggest problem is that it makes it much harder for an object to maintain an invariant (ie: a consistent state) if any method call you make can potentially do a 'goto' behind your back. You can never be sure the next instruction will be executed, so any method which performs multiple actions has to be written very carefully.

Even in your example above, I would prefer having to test an object for null before using it - this at least indicates I know where it can fail. Your code above really has a logic error in it anyway - if a downcast fails, there's something wrong with the code and exceptions can't help fix that...

Some more (biased!) thoughts on exceptions:

http://soundadvice.id.au/blog/2004/10/16/
http://schlitt.info/opensource/blog/0724_python_good_bad_evil_03_flow_control_exceptions.html
http://neil.fraser.name/writing/exception/
http://ptgmedia.pearsoncmg.com/images/020163371x/supplements/Exception_Handling_Article.html

I don't necessarily agree that exceptions are 'evil', but I do believe they are MUCH harder to use safely than most people realize.


Samah(Posted 2011) [#3]
You can never be sure the next instruction will be executed, so any method which performs multiple actions has to be written very carefully.

This is the difference between a checked and unchecked exception. In Java and other languages, checked exceptions must be declared to be thrown. It's not possible to write code that ignores them. Unchecked exceptions (array index out of bounds, null pointer, etc.) can be thrown at any time (as they currently are) so are only caught if you explicitly declare that you want to.

Your code above really has a logic error in it anyway - if a downcast fails, there's something wrong with the code and exceptions can't help fix that...

I just quickly put something in to make it fail at runtime. I was going to use an invalid Int cast, but it seems that only fails on Android.

An example where you might want to use exception handling rather than some arbitrary result:
Function Main()
	Local s:String = "foo"
	Print Int(s)
End
This prints "NaN".

Function Main()
	Print Int("foo")
End
This will print "0" (although the developer would be silly to hardcode this anyway).

Function Main()
	Print Int(" 1")
End
This will print "1" on all targets (I think) except Android, which will fail unless you Trim() it first.

If the string you're casting is not a valid integer, I would expect it to throw an unchecked exception. Otherwise you would need to iterate through every character in the string to ensure it's numeric before you try to cast it, in case you get a result you weren't expecting.

Something like this would be much better:
Function Main()
	Local s:String = "foo"
	Try
		Print Int(s)
	Catch ex:CastException
		Print ex.Message
	End
End
Of course you probably shouldn't be relying on exceptions for that kind of thing, but it's just an example.

I don't necessarily agree that exceptions are 'evil', but I do believe they are MUCH harder to use safely than most people realize.

I agree that they can be misused, but if you don't fully understand them, you probably shouldn't be using them. But you could say that for a lot of paradigms, and it doesn't stop newish programmers (or even experienced ones) from making silly mistakes.

At least give some thought to it for developers who wish to use them. If you're really worried about the "evils of random gotos", maybe only allow user-defined exception handling (the Throw and Throws keywords), and any random system generated ones will just explode as they currently do.

Some more (biased!) thoughts on exceptions:

Aww, that's a little unfair. :)

Edit:
Pleeaasseee give us a forum post preview button! I've taken to hitting "reply" on a random post in the blitz forums so I can use the preview on there.


marksibly(Posted 2011) [#4]
Hi,

> It's not possible to write code that ignores them.

Yes it is - you just add 'throws Blah' to the prototype. This way your entire method can just pass on the exception and ignore it totally. I used to do it all the time back at school!

> This prints "NaN".

Oops - that's a bug! Looks like the const precompiler acting up again...

> Of course you probably shouldn't be relying on exceptions for that kind of thing, but it's just an example.

Yes, good examples of exception use are hard to come by!

> At least give some thought to it for developers who wish to use them.

Will do...


Samah(Posted 2011) [#5]
Yes it is - you just add 'throws Blah' to the prototype. This way your entire method can just pass on the exception and ignore it totally. I used to do it all the time back at school!

That's not ignoring it though, that's deferring it. :)

My point is that checked exceptions must be caught SOMEWHERE in the call stack, unless you declare every method (including Main) to throw it. Unchecked exceptions do not need to be caught or declared as thrown - they're implicitly thrown. If you don't catch it somewhere, it'll simply get bumped up the call stack and crash the thread (as it should).

Edit:
C#/.NET has a cool TryParse method so that you can parse strings to primitives and fail silently with a false return value. Can we have something like this? A native implementation would be nice. :)
If IsInt(mystring) Then 'do int stuff



Roger Lockerbie(Posted 2011) [#6]
Excuse me if this is really dumb as I have nowhere near as much knowledge as Mark/Samah. But isn't putting cleanup of allocations etc in the finally block the way to deal with the 'if a random goto' has occurred how do I know whats run and what has not? (with tests on objects in the finally block to see if they have been instantiated etc, prior to being cleaned up, yes I know monkey has GC but I'm thinking of a contrived example)

That is you defer anything at risk of needing to be resolved regardless of exception to the finally stanza surrounded by checking code to make sure the cleanup is required?

Maybe Mark, you mean its undertaking that mental sorting of what lives in the try/catch block and what is deferred to final cleanup is what you mean by 'written very carefully'

Fascinating exchange anyway, made me think much more about the value versus complexity that exceptions resolve or introduce.

Roger.


Samah(Posted 2011) [#7]
But isn't putting cleanup of allocations etc in the finally block the way to deal with the 'if a random goto' has occurred how do I know whats run and what has not?

Yep, but I'm not sure that all targets support a finally keyword; I didn't look that far into it. Regardless, as Mark said, it requires that the developer knows what he/she is doing. :)