Tuesday, May 13, 2008

ActionScript and Essence vs Ceremony

Over two years ago I started working primarily with Ruby. Since those days I haven't written a line of C# or Java, and I've often wondered if I would be annoyed by the ceremony that both C# and Java generally require.

I've recently been working a bit with Flex.Overall, I've enjoyed working with something new, and I can definitely see the appeal of RIA in general. It's been fairly easy to get started with Flex because it feels very similar to working with HTML and Javascript.
MXML, an XML-based markup language, offers a way to build and lay out graphic user interfaces. Interactivity is achieved through the use of ActionScript, the core language of Flash Player that is based on the ECMAScript standard. --Wikipedia Flex Information
ActionScript might be based on the ECMAScript standard, but it feels a lot more like Java than Javascript.

[Disclaimer: My experience with ActionScript is very limited, so there may be a better way that I'm not aware of]

I was working with a few MXML views the other day and I ran into a situation that made me think about essence and ceremony. The following example code is a simplified example of what I was working on.

<?xml version="1.0"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:ComboBox id="typeCombo"/>
<mx:Box id="typeDetailsBox"/>
</mx:HBox>

<!-- ~/views/OptionOne.mxml -->
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Label text="You chose option 1"/>
</mx:HBox>

<!-- ~/views/OptionTwo.mxml -->
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Label text="You chose option 2"/>
</mx:HBox>

The intent of the simplified example is to change what's inside the typeDetailsBox based on what's selected in the typeCombo.

To solve this in ActionScript I wrote code similar to the following.

public static const Option1:String = "1"; 
public static const Option2:String = "2";

public function init():void {
typeCombo.dataProvider = new ArrayCollection([{label:Option1, data:Option1}, {label:Option2, data:Option2}]);
typeCombo.addEventListener("close", function(e:Event):void{ doTypeSelected(); });
doTypeSelected();
}

public function doTypeSelected():void {
if (typeDetailsBox.numChildren > 0) {
typeDetailsBox.removeChildAt(0);
}
switch (typeCombo.selectedItem.data) {
case Option1:
typeDetailsBox.addChild(new OptionOne());
break;
case Option2:
typeDetailsBox.addChild(new OptionTwo());
break;
}
}

The code is easy enough to follow, but it feels much more verbose than what I prefer. I like being able to pass classes around as objects, so if I could, I'd probably make the classes themselves the "data" for the drop down. If that wasn't an option, I'd prefer an eval method that let me do the following.

public function init():void {
typeCombo.dataProvider = new ArrayCollection([{label:"1", data:"new OptionOne()"}, {label:"2", data:"new OptionTwo()"}]);
typeCombo.addEventListener("close", function(e:Event):void{ doTypeSelected(); });
doTypeSelected();
}

public function doTypeSelected():void {
if (typeDetailsBox.numChildren > 0) {
typeDetailsBox.removeChildAt(0);
}
typeDetailsBox.addChild(eval(typeCombo.selectedItem.data));
}

Unfortunately, ActionScript seems to be a language a bit more concerned with ceremony than I am.

Don't read this as a dismissal of Flex or ActionScript. Whether or not to use a language is a very complicated question and I wouldn't ever base my answer on a switch statement or the ability to eval. Like I said before, working with Flex has been a largely enjoyable experience, but, I must admit, after working 7 years with high ceremony languages and the past 2 with a low ceremony language, I know which I prefer.

4 comments:

  1. Hi Jay,

    You mention that you'd like to pass classes around as objects. Well, in ActionScript 3 that's perfectly valid...you can pass a class reference (not an instance, a proper class prototype reference) as you would any other variable or parameter.

    The best way to store references to classes is to type the associated variables as objects or simply untyped:

    var classRef:Object=someClass;
    -or-
    var classRef:*=someClass;

    Glad to hear you've had positive experiences with ActionScript. If you (or any of your readers) are looking for more indepth explanations of ActionScript or want to be able to use the Flash IDE (nothing against Flex, just not my cup of tea), drop on by http://www.peabee.com/

    Cheers,
    Patrick

    ReplyDelete
  2. ... I realized I should've clarified...the class references can then be instantiated where you need them. For example:

    var classRef:Object=MovieClip; //The standard MovieClip class reference
    var newClip:MovieClip=new classRef(); //Creates a new MovieClip

    The same can be done in a method:

    public function someFunc(classRef:Object) {
    var myInstance:*=new classRef();
    }

    Again, I go over all this and *much* more on http://www.peabee.com/

    Hope to see you there!

    ReplyDelete
  3. Hey Jay,

    Having worked with the SWF stack (ActionScript 1, 2, 3, and the Flex Framework) for the past 8 years, I hear where you're coming from. I bet you would have liked AS2. In the places where AS3 feels like Java and C#, AS2 feels like Ruby. There was less, as you say, "ceremony" with AS2. You should check out haXe [http://www.haxe.org/] - a language with both strong typing and type inference that compiles to Flash Player 6-9 bytecode.

    Great blog and good post. It's not entirely accurate, though. Your AS3 code example is only more verbose than what you prefer because of the way it's written. In ActionScript, you can definitely pass classes around as objects, and if you decided against that, there's also an eval-like method called getDefinitionByName() [http://livedocs.adobe.com/flex/3/langref/flash/utils/package.html#getDefinitionByName()] that would also work for your illustrated use case.

    Modified versions of your example code follow...


    //----------------------------------------------------
    passing classes around as objects to themselves be used as the "data" for the drop down
    --------------------------8<--------------------------
    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    creationComplete="doTypeSelected()"
    >
    <mx:Script>
    <[CDATA[
    import mx.collections.ArrayCollection;

    import views.OptionOne;
    import views.OptionTwo;

    [Bindable]
    protected var options:ArrayCollection = new ArrayCollection([
    {label:"1", data:OptionOne},
    {label:"2", data:OptionTwo}
    ]);

    public function doTypeSelected():void {
    if (typeDetailsBox.numChildren > 0) {
    typeDetailsBox.removeChildAt(0);
    }

    typeDetailsBox.addChild(new typeCombo.selectedItem.data());
    }
    ]]>
    </mx:Script>

    <mx:HBox>
    <mx:ComboBox
    id="typeCombo"
    dataProvider="{options}"
    close="doTypeSelected()"
    />
    <mx:Box id="typeDetailsBox" />
    </mx:HBox>

    </mx:Application>


    <-- ~/views/OptionOne.mxml -->
    <--
    <?xml version="1.0" encoding="utf-8"?>
    <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Label text="You chose option 1"/>
    </mx:HBox>
    -->

    <-- ~/views/OptionTwo.mxml -->
    <--
    <?xml version="1.0" encoding="utf-8"?>
    <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Label text="You chose option 2"/>
    </mx:HBox>
    -->
    -------------------------->8--------------------------


    //----------------------------------------------------
    using ActionScript's eval-like getDefinitionByName() method
    --------------------------8<--------------------------
    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    creationComplete="doTypeSelected()"
    >
    <mx:Script>
    <[CDATA[
    import flash.utils.getDefinitionByName;

    import mx.collections.ArrayCollection;

    import views.OptionOne;
    import views.OptionTwo;

    protected var optionOneRef:OptionOne;
    protected var optionTwoRef:OptionTwo;

    [Bindable]
    protected var options:ArrayCollection = new ArrayCollection([
    {label:"1", data:"views.OptionOne"},
    {label:"2", data:"views.OptionTwo"}
    ]);

    public function doTypeSelected():void {
    if (typeDetailsBox.numChildren > 0) {
    typeDetailsBox.removeChildAt(0);
    }

    var ClassRef:Class = getDefinitionByName(typeCombo.selectedItem.data) as Class;
    typeDetailsBox.addChild(new ClassRef());
    }
    ]]>
    </mx:Script>

    <mx:HBox>
    <mx:ComboBox
    id="typeCombo"
    dataProvider="{options}"
    close="doTypeSelected()"
    />
    <mx:Box id="typeDetailsBox" />
    </mx:HBox>

    </mx:Application>


    <-- ~/views/OptionOne.mxml -->
    <--
    <?xml version="1.0" encoding="utf-8"?>
    <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Label text="You chose option 1"/>
    </mx:HBox>
    -->

    <-- ~/views/OptionTwo.mxml -->
    <--
    <?xml version="1.0" encoding="utf-8"?>
    <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Label text="You chose option 2"/>
    </mx:HBox>
    -->
    -------------------------->8--------------------------


    //----------------------------------------------------
    using regular old object instances
    --------------------------8<--------------------------
    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    creationComplete="doTypeSelected()"
    >
    <mx:Script>
    <[CDATA[
    import mx.collections.ArrayCollection;

    import views.OptionOne;
    import views.OptionTwo;

    [Bindable]
    protected var options:ArrayCollection = new ArrayCollection([
    {label:"1", data:new OptionOne()},
    {label:"2", data:new OptionTwo()}
    ]);

    public function doTypeSelected():void {
    if (typeDetailsBox.numChildren > 0) {
    typeDetailsBox.removeChildAt(0);
    }

    typeDetailsBox.addChild(typeCombo.selectedItem.data as DisplayObject);
    }
    ]]>
    </mx:Script>

    <mx:HBox>
    <mx:ComboBox
    id="typeCombo"
    dataProvider="{options}"
    close="doTypeSelected()"
    />
    <mx:Box id="typeDetailsBox" />
    </mx:HBox>

    </mx:Application>


    <-- ~/views/OptionOne.mxml -->
    <--
    <?xml version="1.0" encoding="utf-8"?>
    <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Label text="You chose option 1"/>
    </mx:HBox>
    -->

    <-- ~/views/OptionTwo.mxml -->
    <--
    <?xml version="1.0" encoding="utf-8"?>
    <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Label text="You chose option 2"/>
    </mx:HBox>
    -->
    -------------------------->8--------------------------


    Don't get me wrong. I completely agree that Ruby is a lower ceremony language that ActionScript, but I felt obliged to point out that AS isn't really as high a ceremony language as your making it out to be. Honestly, you can make AS pretty loose if you use dynamic classes and setting the MXMLC -strict flag to "false". Some links you might be interested in follow:

    ActionScript's Class :: http://livedocs.adobe.com/flex/3/langref/Class.html
    getDefinitionByName() :: http://livedocs.adobe.com/flex/3/langref/flash/utils/package.html#getDefinitionByName()
    getQualifiedSuperclassName() :: http://livedocs.adobe.com/flex/3/langref/flash/utils/package.html#getQualifiedSuperclassName()
    registerClassAlias() :: http://livedocs.adobe.com/flex/3/langref/flash/net/package.html#registerClassAlias()
    getClassByAlias() :: http://livedocs.adobe.com/flex/3/langref/flash/net/package.html#getClassByAlias()
    As3Eval (ActionScript 3 eval library) :: http://eval.hurlant.com/
    Dynamic Classes in ActionScript :: http://livedocs.adobe.com/flex/3/html/03_Language_and_Syntax_10.html
    MXMLC compiler options :: http://livedocs.adobe.com/flex/201/html/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Book_Parts&file=compilers_123_24.html
    Francis Cheng's blog (AS language designer) :: http://blogs.adobe.com/fcheng/

    Enjoy, and keep up the great blog!


    Ali

    ReplyDelete
  4. Very cool information, thank you. I was hoping someone would point me in a better direction.

    Cheers, Jay

    ReplyDelete

Note: Only a member of this blog may post a comment.