February 25, 2008

Abusing try..catch and throw

While looking something else up in the AS3 documentation this long weekend, I ran across this comment:

Typically, you throw instances of the Error class or its subclasses (see the Example section).

Emphasis added by me. If one typically does something, that means you can also not do it :) The throw statement takes an expression as its argument. The convention is to have it be a subclass of Error. This post isn't about that. In fact this post is a mental exercise as I don't recommend breaking from that convention, but I'm sure it might get someone's creative juices flowing, as I've not thought of a compelling use case, yet.

To demonstrate the alternative usage of throw, the code below simulates a switch statement branching based on the type of object being passed in. This code doesn't do anything with the value, but considering its all fun and games anyway extrapolating to doing some type conversion based on the incoming type should be easy. I've included all the standard primitive and top level classes, which are more examples than are needed to demonstrate the idea, but I was curious:

// This is an example of an AS3 language
// possibility, don't write code like this.
private function catchAsSwitch(value:*):void {
    try {
        throw value;
        // Tested alphabetically unless noted
        // otherwise       
    } catch(type:Array) {
        trace("Array");
    } catch(type:Boolean) {
        trace("Boolean");
    } catch(type:Class) {
        trace("Class");
    } catch(type:Date) {
        trace("Date");
        // This is the normal use case
    } catch(type:Error) {
        trace("Error");
    } catch(type:Function) {
        trace("Function");
    } catch(type:int) {
        trace("int");
        // uint must come before Number
        // since a uint satisfies both
        // is uint and is Number
    } catch(type:uint) {
        trace("uint");
    } catch(type:Namespace) {
        trace("Namespace");
    } catch(type:Number) {
        trace("Number");
    } catch(type:QName) {
        trace("QName");
    } catch(type:RegExp) {
        trace("RegExp");
    } catch(type:String) {
        trace("String");
    } catch(type:XML) {
        trace("XML");
    } catch(type:XMLList) {
        trace("XMLList");
        // Object must go last since everything
        // above satisfies is Object
    } catch(type:Object) {
        trace("Object");
        // Like switches' default
    } catch(type:*) {
        trace("*");
    }
}

An example function that calls the method with the various types:

private function go():void
{
    catchAsSwitch(new Array());
    catchAsSwitch(true);
    catchAsSwitch(Class);
    catchAsSwitch(new Date());
    catchAsSwitch(new Error());
    catchAsSwitch(function():void{});
    catchAsSwitch(-42);
    catchAsSwitch(0xFFFFFFFF);
    catchAsSwitch(new Namespace("com.neophi.test"));
    catchAsSwitch(42.42);
    catchAsSwitch(new QName("foo"));
    catchAsSwitch(/regexp/);
    catchAsSwitch("foo");
    catchAsSwitch(<foo/>);
    catchAsSwitch(<foo/>.children());
    catchAsSwitch(new Button());
    catchAsSwitch(null);
    catchAsSwitch(undefined);
}

The output of running that function is:

Array
Boolean
Class
Date
Error
Function
int
uint
Namespace
Number
QName
RegExp
String
XML
XMLList
Object
*
*

As noted in the comments of catchAsSwitch() catch clauses are evaluated in order so in some cases if the value could satisfy multiple different types the more specific is listed first. This is something to keep in mind when using the typical case, in that Error should be listed after more specific subclasses like ArgumentError.

Now that this alternative syntax exists could we do something with it? Well as I mentioned above I've not thought of a compelling case yet. In fact this entire post is the result of a big tangent on my part. It started off with reading some of Francis Cheng's recent posts about Proper Tail Calls (PTC). Having taken a compiler course from William Clinger in the past in which I had to implement PTC I found the topic interesting.

Reading those posts led me to the EMCAScript site and looking over the ES4 documentation. In one document comments about scoping issues with catch blocks had me discover that throw took an expression. Which made me remember some discussion about using throw instead of return for functions when people were first exploring Java. The net result of that discussion was a comparison to the considered harmful idea since it obfuscated the code's intent. I can't find a reference to the discussion but Cheng's PTC example prompted me to write a throw equivalent. It still suffers from stack overflow but again maybe it will spark someone else's imagination for a way to use the technique.

private function factorial(x:int):int {
    try {
        calculateFactorial(x, 1);
    } catch(result:int) {
        return result;
    }
    return 1;
}

private function calculateFactorial(x:int, result:int):void {
    if (x < 1) {
        throw result;
    }
    calculateFactorial(x - 1, result * x);
}

I don't know enough about the internals of the AVM to know if popping up the stack looking for a catch block is better than just returning the value in the current AVM. Obviously if PTC is added to ES4 and the AVM picks it up any such approach like the one above will be vastly slower. The nice mental exercise is that you have a function "returning" a value without returning a value.

In the process of experimenting with alternative try..catch use cases I ran into some compiler issues. Being a nonstandard use case I'm not surprised they didn't work. Yet another reason to not try this at home.

Update: Shortly after posting this I found a post that talks about using try..catch as an alternative flow control mechanism. This captures most of the same thoughts as that old Java thread I mentioned above.

Tags: as3 catch throw try