More on Enum

Enums are one of the most powerful aspects of the Haxe compiler, but also one of the trickier concepts to completely understand and take advantage of. There's already a good deal of information on enums given in the official documentation. However, this page gives further examples taken from the Haxe mailing list.

Handling Mixed-Type Iterables

One scenario where enums can be used is in handling certain mixed-type Iterables. Typically, mixed-type iterables can be typed with typedefs, presenting a single shared Type template that can be used during iteration.

For instance, consider the two classes:

class Foo {    
    public function new(){ name = 'Foo Object'; }
    public var name : String;
    public function baz() : String { return 'baz!';}
}

and

class Bar
{      
    public function new() { name = 'Bar Object'; }
          public var name : String;
}

If you are just interested in accessing the "name" field of the objects, the two classes can be typed as:

typedef GenericType = { name: String}

and then it is possible to create Lists or other Iterables including both Foo and Bar types:

var l = new List<GenericType>();
l.add(new Foo());
l.add(new Bar());
for (i in l){
    trace(i.name);
}

However, what if you want to call the Foo's 'baz()' function inside the Iterable? You can't access this function through the given typedef, and you can't modify the typedef to accommodate both the Foo and Bar classes in the Iterable. At this point, it is not uncommon to simply override the compiler. Reflect only gives property access, and won't even tell you if a method exists or not for a given object. Typically, you'll need to use some sort of trick to make sure that you're dealing with the right kind of object:

for (i in l){
     if (i.name == 'Foo Object') { untyped trace(i.baz()); }
}        

This will let you access the function, at the cost of losing strict typing over a section of the code. This can be dangerous in many situations. Luckily, this situation can be handled through the use of an enum:

enum GenericEnum {
    eFoo(f:Foo);
    eBar(b:Bar);
}

This a parameterized enum, meaning that it accepts parameters in its constructor. We are going to use this enum as a 'wrapper' around our mixed-type classes, and then use the enum constructor parameter to access the fields of the different classes.

First, we will have to type the List differently. The List will now contain the enums given above:

var l2 = new List<GenericEnum>();

Now, we'll insert the Foo and Bar instances using the enum wrapper:

l2.add(eFoo(new Foo()));
l2.add(eBar(new Bar());

Finally, inside the Iterable, we will use a switch on the enum to access the relevant fields:

for (i in l2){
    switch(i){
        case eFoo(f) : { trace(f.name + ' ' + f.baz());} 
        case eBar(b) : { trace(b.name);}
    }
}

Now it is possible to access whatever field/method we want from a mixed-type Iterable, without resorting to overriding the compiler and performing potentially unsafe field accesses.

Here's the code in full:

enum GenericEnum {
    eFoo(f:Foo);
    eBar(b:Bar);
}

typedef GenericType = { name: String}

class Test
{

   
    public static function main(): Void
    {
        var l = new List<GenericType>();
        l.add(new Foo());
        l.add(new Bar());
        

        trace('show simple example without accessing baz()');
        for (i in l){
            trace(i.name);
        }
        
        trace("here we are dangerously overriding the compiler with 'untyped', and accessing baz()");
        for (i in l){
           if (i.name == 'Foo Object') { untyped trace(i.baz()); }
        }
        
        var l2 = new List<GenericEnum>();
        l2.add(eFoo(new Foo()));
        l2.add(eBar(new Bar()));
        
        trace("here we are using the enum to access the field properties safely");
        for (i in l2){
            switch(i){
                case eFoo(f) : { trace(f.name + ' ' + f.baz());} 
                case eBar(b) : { trace(b.name);}
            }
        }
        
        
    }
}

// some class
class Foo {    
    public function new(){ name = 'Foo Object'; }
    public var name : String;
    public function baz() : String { return 'baz!'; }
}

// some other class, for simplicity's sake, they have nothing to do with eachother (no extend, implement)
class Bar
{      
    public function new() { name = 'Bar Object'; }
    public var name : String;
}
version #15804, modified 2012-12-18 09:36:31 by jjdonald