Haxe白魔法使い入門、技巧編。上手にEnumを使う。


またまた、Enumの使い方についての紹介です。

前回はこちら↓

Haxe白魔法使い入門、基本編。Enumの実用パターン集。
http://qiita.com/shohei909/items/454ff5d3be46c6064074

今回は、Enumを使うときのテクニックに焦点を当てて、Enumを紹介していきます。

Enumに後から値を入れる。

例えば、以下のようなEnumを扱う場合、dataの文字列を後から変更したい場合があります。

enum Sample {
    HOGE( data:String );
}

1度設定したEnumのパラメータは変更することは出来ませんが、配列や構造体をEnumのパラメータに使えばその中の値は自由に変更することが出来ます。

こんな感じに

class Main {
    static function main() {
        var sample = HOGE( { data : "hoge" } );

        switch( sample ) {
            case HOGE( obj ):
                obj.data = "fuga";
        }
    }
}

enum Sample {
    HOGE( obj:{ data:String } );
}

わりと良く使います。

また、このテクニックを使うことで、Enumのパラメータに自分自身を持たせたり、循環があるグラフをEnumで表現したりすることもできます。

class Main {
    static function main() {
        var data = { graph0 : LEAF( "hoge" ), graph1:null };
        var node = NODE( data );
        data.graph1 = node;
    }
}

enum GRAPH {
    LEAF( data:String );
    NODE( data:{ graph0:GRAPH, graph1:GRAPH } );
}

大きくなりすぎたEnumを整理する。

数式などを表現するのにEnumを使うと1つのenumにたくさんのコンストラクタが出来てしまって、一回switchするにも面倒って場合があります。こういう場合に使えるちょっとしたテクニックを紹介します。

enum Expression {
    INT( int:Int ); //整数
    PLUS( a:Expression ); // (+ a)
    MINUS( a:Expression ); // (- a)
    ADD( a:Expression, b:Expression ); // (a + b)
    SUBTRACT( a:Expression, b:Expression ); // (a - b)
    PRODUCT( a:Expression, b:Expression ); // (a * b)
    DIVIDE( a:Expression, b:Expression ); // (a / b)
    POWOR( a:Expression, b:Expression ); // (a ^ b)
    MOD( a:Expression, b:Expression ); // (a % b)
}

このようなEnumは、以下のように分割することで使い勝手がよくなります。

enum Expression {
    INT( int:Int ); //数字
    UNARY_OP( op:UnaryOperator, a:Expression ); // (op a)
    BINARY_OP( op:BinaryOperator, a:Expression, b:Expression ); // (a op b)
}

//一項演算
enum UnaryOperator { 
    PLUS; // +
    MINUS; // -
}

//二項演算
enum BinaryOperator { 
    ADD; // +
    SUBTRACT; // -
    PRODUCT; // *
    DIVIDE; // /
    POWOR; // ^
    MOD; // %
}

同じ構造のコンストラクタをすべて1つにまとめてしまい。各構造ごとに、enumを作ったことで演算子ごとの列挙も行えてしまうので、使い勝手が増します。

Haxe3はパターンマッチに対応しているので、このように1つのenumでまとめてしまっていた場合より楽に分岐を行うことができます。

例えば、こんなかんじです。

class Sample {
    static public function test( expr:Expression ){
        switch( expr ) {
            case INT( num ):
                //整数の場合。

            case UNARY_OP( op, a ): 
                //1項演算の場合。

            case BINARY_OP( ADD, a, b ) : 
                //足し算の場合。

            case BINARY_OP( op, a, b ) :
                //その他の2項演算の場合。この手の分岐を1つのenumでやろうとするとめんどくさい。
        }
    }
}

良く使うEnumは関数化しておく

Enum便利です。上記のEnumを分割するテクニックも便利です。ですが、ついついEnumをたくさん定義してしまい何重にも入れ子になったようなEnumが出来てしまうと簡単なenumを表現するのも面倒です。例えば、上記の数式のEnumで、"2 + 3 + 4 + 5"というごく簡単な数式を表現しようとすると以下のような面倒なコードになってしまいます。

BINARY_OP( ADD, BINARY_OP( ADD, BINARY_OP( ADD, INT(2), INT(3) ), INT(4) ), INT(5) );

良く使う値は、以下のように関数化して簡単に定義出来るようにしてしまいましょう。

class Main {
    static function main() {
        var expr = ExpressionBind.sum( [ INT(2), INT(3), INT(4), INT(5) ] );
    }
}

class ExpressionBind {
    static public function add( a:Expression, b:Expression ) {
        return Expression.BINARY_OP( BinaryOperator.ADD, a, b );
    }

    static public function sum( array:Array<Expression> ) {
        var length = array.length;
        if ( length == 1 ) return array[ 0 ];
        var copy = array.copy();
        var last = copy.pop();

        return add( sum( copy ), last );
    }
}

また、パラメータ付きEnumをパラメータを省略して記述すると関数として扱うことが出来るので、bindを使って目的のコードを短く記述することも可能です。

class Main {
    static function main() {
        var add = Expression.BINARY_OP.bind( ADD, _, _ );
        var expr = add( add( add( INT( 2 ), INT( 3 ) ), INT( 4 ) ), INT( 5 ) );
    }
}

Enum、使いすぎてと多重の構造になってしまうと定義がめんどくさくなりますが、一度関数化してしまえばあとは楽ちんです。どんどんEnumを使って快適なコーディングを楽しみましょう。

おわり

明日は、@nobkzさんです。よろしくおねがいします。