case class on PHP

入って来たデータを調べて分岐させていくと、だんだんとプログラムは複雑化していきます。
if文とgoto文だけでは、あまり奇麗に書けない。
それを奇麗に書くのに生まれたのが、switch 文。switchを使えば、変数の値によって、
とび先がわかりますよと明示的に示せるのでプログラムの流れが把握しやすくなります。
switch分がなくても、if文だけで書く事は可能だけど、switch文をうまく使うとプログラムソースは奇麗になります。
さて、しかし、switch文だけでは奇麗に書けない分岐はたくさんあります。
例えば、型によって違う分岐。これは、オブジェクトの多態性を用いる事でさらっと分けて書く事が出来ます。
複雑な構造も、ビジターパターンを使えば、奇麗にかけるのですが、メソッド呼び出しのオーバーヘッドが多すぎない?
って問題が出てきます。
で、って事で、もうちょっと、instanceofを書かないけど書いたのと同じ意味になる分岐をできるだけスッキリかけるようにしよう。
というのが、パターンマッチです。パターンマッチはあるデータ構造に大して、このかたはこの形に合ってるかな?ってチェックして
合ってたら、その合ってたってことを知らせるとともに、こことここのデータは欲しいってところには、その値を入れて返してくれると
何かと便利だよねーって考えかた。

x + y っていうパターンがあったら、 x を評価した値と y を評価した値を足して返す。
x * y っていうパターンがあったら、xを評価した値とyを評価した値を掛けて返す。

というのをプログラムで短く書くとすると、

function exp($a) {
  switch ($a) {
  case $x + $y:  return exp($x) + exp($y);
  case $x * $y: return exp($x) * exp($y);
  }
}
return exp(1+2*3);

と言ったかんじで掛けたら嬉しいよね。というのを実現できたら、プログラムはもっと奇麗に書ける。
それがScalaのマッチ構文。
scala の case class を使えば、このパターンマッチをデータ構造に適応することが出来るので嬉しい。
要するに高機能な switch文 こうやってがんばっていった先に Scala の case class があるのです。

PHP なら output buffer と eval とを使えば似たような機能は作れるので、作ってみました。
Scalaのmatch構文はもっといろいろ便利な機能があるけど、とりあえず、便利そうってことがわかってもらえれば幸いです。

caseClass関数はswitch文中でパターンマッチ的に使えるclassと便利関数を作る関数です。
PHPのswitch の caseには好きな式を書く事が可能で、 switch(true) {...}と書く事で上から順番にtrueになる式を探すっていう風に
使う事が出来ます。PHP 5.3から入った、__invokeを使う事で、オブジェクトを関数として呼び出す事が可能なので、若干短く書けました。
第一引数は変数の型、2引数以降はそのオブジェクトが保持するメンバ変数です。
PHPの引数を参照として使い、そこに、オブジェクトの値を代入する事で、値のバインディングを実現しています。
case classでは、new Hoge()する代わりにHoge()と書く事も出来るので、class Hoge と function Hogeを作成する事で対処しました。
ワーニングが出てしまうので、@で抑制しています。
case XXXX && gard:
と && を書く事でガードの役割をさせることも出来ます。

<?php

class Atom {}
caseClass("Add","a","b","Atom"); // Atom を継承して Addっていうクラスを作ります。
caseClass("Mul","a","b","Atom");// Atom を継承して Mulっていうクラスを作ります。
caseClass("Val","a","Atom"); // Atomを継承して Valっていうクラスを作ります。

function calc($a) {
    switch (true) {
    case @$a(Val, $x):     return $x;
    case @$a(Add, $x, $y) && calc($x) < 0: return -calc($x) + calc($y);
    case @$a(Add, $x, $y): return -calc($x) + calc($y);
    case @$a(Mul, $x, $y): return calc($x) * calc($y);
    }
}

echo calc(Add(Val(-1), Mul(Val(2), Val(3))));


function caseClass($name) {
        $names = func_get_args();
        $class = array_shift($names);
        $extends = array_pop($names);
        $name1s = array();
        $name2s = array('$type');
        foreach($names as $name) {
            $name1s[] = '$'.$name;
            $name2s[] = '&$'.$name;
        }
    ob_start();
    ?>
    class <?php echo $class; ?> extends <?php echo $extends ?> {
        function <?php echo $class; ?>(<?php echo implode(", ", $name1s); ?>) {
            <?php foreach($names as $name) { ?>
            $this-><?php echo $name; ?> = $<?php echo $name; ?>;
            <?php } ?>
        }
        function __invoke(<?php echo implode(", ", $name2s); ?>) {
            if(! $this instanceof $type) return false;
            <?php foreach($names as $name) { ?>
            $<?php echo $name; ?> = $this-><?php echo $name; ?>;
            <?php } ?>
            return true;
        }
    }
    function <?php echo $class; ?>(<?php echo implode(", ", $name1s); ?>) {
        return new <?php echo $class; ?>(<?php echo implode(", ", $name1s); ?>);
    }
    <?php
    $contents = ob_get_contents();
    ob_end_clean();
    eval($contents);
    
}