LoginSignup
5
4

More than 5 years have passed since last update.

PHPでScalaのcase class っぽいことをする。

Last updated at Posted at 2013-07-02

ごにょごにょと黒魔術を書く事で、

<?php
caseClass("EAdd","l","r");
caseClass("EMul","l","r");
caseClass("ELdc","l");
function f($e) {
    switch(get_class($e)) {
        case "EAdd": return f($e->l) + f($e->r);
        case "EMul": return f($e->l) * f($e->r);
        case "ELdc": return $e->l;
    }
}
echo f(EMul(ELdc(11),ELdc(100)))."\n";

って感じで、簡単な計算機を作れるようになりますってのを作ってみました。
やってることは、PHPのコードを内部で生成して、evalしてるだけですけど。
たとえば、ELdcは以下のようなコードが生成されます。
PHPコードの生成は、PHPのタグを使う事で奇麗に書けます。
構文木弄るのではなく、文字列生成だけど、Lispのマクロのような事が出来るPHPは素晴らしいのです。まさに黒魔術。

<?php
class ELdc {
    function __construct($l, $r) {
        $this->l = $l;
        $this->r = $r;
    }
    function __toString() {
        return "ELdc(".$this->l.",".$this->r.")";
    }
}
function ELdc($l) { return new ELdc($l); }

あと、ついでに、パターンマッチングで値のバインディングをするようにしたものも作ってみました。visitorパターンの発展系的な奴と言うかなんというか。

<?php
class eval2 extends Match {
    function f_EAdd($e, $l, $r) {
        return self::f($l) + self::f($r);
    }
    function f_EMul($e, $l, $r) {
        return self::f($l) * self::f($r);
    }
    function f_ELdc($e, $l) {
        return $l;
    }
    function p_EAdd($e, $l, $r) {
        return "EAdd(" . self::p( $l ) . ")";
    }
    function p_EMul($e, $l, $r) {
        return "EMul(".self::p($l).",".self::p($r).")";
    }
    function p_ELdc($e, $l) {
        return "EAdd(".$l.")";
    }
}
echo eval2::f(EMul(ELdc(11),ELdc(100)))."\n";
echo eval2::p(EMul(ELdc(11),ELdc(100)))."\n";

こいつの良い所は、普通のvisitorパターンと違って複数の関数を1つのクラス内に書ける所です。上の例では、評価機と、プリンターの2つの機能を実装しています。あと、値のバインディングもやってくれてるので、$e->lってかかないで、$lと書けます。いちいち、classの定義見に行かなくても、意味が分かるのがいい所です。

しかしまぁ、self::って書かなきゃ行けないので嬉しくないっていうあたりが、ああPHPだな残念ってところです。

class Matchで黒魔術をごにょごにょすることで、短く書けて嬉しいような、意味の無いような、そして、ワーニングでたら、staticとかも書いてねっていう感じで、関数呼び出し増えて、リフレクション使うような事やってて遅くなるだけだし、素直にswitchで書けってことになるのかと思います。あと、構文木のコードもevalするんじゃなくて、ジェネレータにして、1回はきだしたらincludeとかして使ったほうが良いと思いますけどまぁ、いいや。

ってことで、以下がソース全体でございます。
好きに煮るなり、焼くなりしてください。

<?php
function caseClass() {
    $args = func_get_args();
    $name = array_shift($args);
    $args2 = array_map(function($e){return '$'.$e;},$args);
    $args3 = array_map(function($e){return '$this->'.$e;},$args);
    ob_start();
?>
class <?php echo $name?> {
    function __construct(<?php echo implode(",", $args2) ?>) {
        <?php foreach($args as $v) { ?>
            $this-><?php echo $v ?> = $<?php echo $v ?>;
        <?php } ?>
    }
    function __toString() {
        <?php if (count($args3)) { ?>
        return "<?php echo $name?>(".<?php echo implode('.",".', $args3) ?>.")";
        <?php } else { ?>
        return "<?php echo $name?>()";
        <?php } ?>
    }
}
function <?php echo $name?> (<?php echo implode(",", $args2) ?>) {
    return new <?php echo $name?>(<?php echo implode(",", $args2) ?>);
}
<?php
    $contents = ob_get_contents();
    ob_end_clean();
    echo eval($contents);
}

class Match {
    static function __callStatic($name,$args) {

        $class = $name."_".get_class($args[0]);
        if(!method_exists(get_called_class(), $class)) {
            throw new Exception("undefined method ".$class."::".get_called_class());
        }
        return call_user_func_array(array(get_called_class(), $class), array_merge($args,array_values((Array)$args[0])));
    }
}

caseClass("EAdd","l","r");
caseClass("EMul","l","r");
caseClass("ELdc","l");
function f($e) {
    switch(get_class($e)) {
        case "EAdd": return f($e->l) + f($e->r);
        case "EMul": return f($e->l) * f($e->r);
        case "ELdc": return $e->l;
    }
}

echo EMul(ELdc(11),ELdc(100))."\n";
echo f(EMul(ELdc(11),ELdc(100)))."\n";

class eval2 extends Match {
    function f_EAdd($e, $l, $r) {
        return self::f($l) + self::f($r);
    }
    function f_EMul($e, $l, $r) {
        return self::f($l) * self::f($r);
    }
    function f_ELdc($e, $l) {
        return $l;
    }
    function p_EAdd($e, $l, $r) {
        return "EAdd(" . self::p( $l ) . ")";
    }
    function p_EMul($e, $l, $r) {
        return "EMul(".self::p($l).",".self::p($r).")";
    }
    function p_ELdc($e, $l) {
        return "EAdd(".$l.")";
    }
}
echo eval2::f(EMul(ELdc(11),ELdc(100)))."\n";
echo eval2::p(EMul(ELdc(11),ELdc(100)))."\n";
5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4