読者です 読者をやめる 読者になる 読者になる

PHP5.4とScalaのトレイト比較

PHP Scala

PHP5.4にはトレイトという文法が導入されて、多重継承の便利さが享受できるようになるそうです。
いろいろ試してみることにしました。

トレイトといえば代表選手はScalaですね。Scalaでいうトレイトは、インターフェースの定義とメソッドの実装を同時にやってくれる便利な概念でした。単一継承のOOP言語では「〜は〜の一種」という制約が強すぎて、「ペンギンは鳥の飛行メソッドによって飛行できてしまう」という問題があります。でもちゃんと「ペンギンは飛べないが泳げる鳥」を綺麗に解決する必要はあって、その解答として、トレイトのあるScalaではこんな解が導けます。

abstract class Animal

abstract class Bird extends Animal {
    def hasBeak = true  // 鳥にはかならずクチバシがある
    def hasWings = true  // 翼もあり(飛ぶためかどうかは別として骨格的に)
    def hasFeather = true  // かならず羽毛もある
}

// 飛べる動物
trait FlyingAnimal {
    def fly = "flying"
}

// 泳げる動物
trait SwimingAnimal {
    def swim = "swimming"
}

// ツバメは鳥類であり飛べる動物
class Swallow extends Bird with FlyingAnimal

// ペンギンは鳥類だが飛べず、代わりに泳げる動物
class Penguin extends Bird with SwimingAnimal

var pingu = new Penguin
println(pingu)

if(pingu.isInstanceOf[Bird])
    println("has beak")

if(pingu.isInstanceOf[FlyingAnimal])
    println("can fly")

if(pingu.isInstanceOf[SwimingAnimal])
    println("can swim")

これと同じようにトレイトが型システムとして機能するか、つまり、 instanceof に反応するところが等しく振る舞うかを、PHP5.4で調べてみることにします。

<?php
//(前略)
trait SwimmingAnimal {
    public function swim() {
        return "swimming";
    }
}
class Penguin extends Bird {
    use SwimmingAnimal;
}
$pingu = new Penguin();
if($pingu instanceof SwimmingAnimal) {
    echo "can swim\n"; // ==> 無反応
}

はい、反応ありませんこれ。PHP5.4のトレイとは型システムとしては機能していません。
そりゃまあ当然で、PHP5.4のトレイトは、使用者側でアドホックにメソッド名の置き換えすらできてしまうので、「必ず特定のメソッドに応答することを保証する」インターフェースとしては使えないです。

http://simas.posterous.com/new-to-php-54-traits

というわけで、PHPScalaのサンプルと同じことをやろうとすると、こうなります。

<?php
abstract class Animal {
}
abstract class Bird extends Animal {
    public function hasBeak() {
        return true;
    }
    public function hasWings() {
        return true;
    }
    public function hasFeather() {
        return true;
    }
}

interface FlyingAnimal {
    public function fly();
}
trait FlyingAbility {
    public function fly() {
        return "flying";
    }
}

interface SwimmingAnimal {
    public function swim();
}
trait SwimmingAbility {
    public function swim() {
        return "swimming";
    }
}

class Swallow extends Bird implements FlyingAnimal {
    use FlyingAbility;
}

class Penguin extends Bird implements SwimmingAnimal {
    use SwimmingAbility;
}

$pingu = new Penguin();
echo "pingu\n";

if($pingu instanceof Bird)
    echo "has beak\n";

if($pingu instanceof FlyingAnimal)
    echo "can fly\n";

if($pingu instanceof SwimmingAnimal)
    echo "can swim\n";

インターフェースで「〜な動物」を示し、トレイトでその実装差分として「〜する能力」を補う感じで、Scalaがひとつのシンタックスでやってくれることを、シンタックスを組み合わせて手動でやるとそれっぽくなりました。

Scalaのトレイトは継承できたりメソッドシグネチャ競合時の選択方法があるなど、かなりシンタックスに凝っています。が、PHPインタプリタでそんなのをやるとパフォーマンスに問題があるような気がしてなりません。また、ちょっとScalaのtraitの実験が怪しい結果になったので、エイリアスできると言い切れなくなりました。が、少なくともインターフェースは限定するのですでにダックタイピングに対応して作られたライブラリが、インターフェースにがっつり来られると困る、という場合もあるかもしれませんし。
というわけでPHPでは、インターフェースと実装差分を完全に切り分けて、そのあたりの問題をシンタックスで解決するのを避けたのかなと思いました。

で、それはたぶんそんなに悪くない(PHPはこの「そんなに悪くない」が大事)選択なんじゃないかと思います。

ここで、「同じ泳げる鳥でも、白鳥とペンギンでは意味が違う」という問題を出します。白鳥は水面を泳ぎ、ペンギンは水中を泳ぎますね。言葉上は同じでも、その実現方法はだいぶ違っています。Scalaではこういうとき、トレイトを継承します。

trait InWaterSwimingAnimal extends SwimingAnimal {
    override def swim = "swimming in water"
}
trait OnWaterSurfaceSwimingAnimal extends SwimingAnimal {
    override def swim = "swimming on water surface"
}

class Penguin2 extends Bird with InWaterSwimingAnimal

class Swan extends Bird with FlyingAnimal with OnWaterSurfaceSwimingAnimal

var pingu2 = new Penguin2
var swan = new Swan

println("pingu2 is " + pingu2.swim);
println("swan is " + swan.swim);

たしかに、instanceof SwimingAnimal ではあるけれど、少し見た目が悪くなりました。

PHP5.4の方法ではこうです。

<?php
trait InWaterSwimmingAbility {
    public function swim() {
        return "swimming in water";
    }
}
trait OnWaterSurfaceSwimmingAbility {
    public function swim() {
        return "swimming on water surface";
    }
}

class Penguin2 extends Bird implements SwimmingAnimal {
    use InWaterSwimmingAbility;
}

class Swan extends Bird implements FlyingAnimal, SwimmingAnimal {
    use FlyingAbility, OnWaterSurfaceSwimmingAbility;
}

$pingu = new Penguin2();
$swan = new Swan();

echo "pingu2 is " . $pingu->swim() . "\n";
echo "swan is " . $swan->swim() . "\n";

型システムとしては SwimmingAnimal のまま、実装差分を切り替えて機能を変更しました。オブジェクト指向言語としては、本物のトレイトに比べると美しくないですが、現実的なやりかただと思いませんか。instanceof SwimmingAnimal は壊れていません。それどころか、Scalaのそれよりも宣言に変化が少なくて済みました。

イメージしてみてください、たとえば、ISecureGateway というインターフェースがあり、TMemberAuth と TAdminAuth というトレイトがあって、それらが Controller の派生クラスで使い分けされる様子を。

PHPの正義はとにかくさっさと使えることです。なので、「トレイトとはメインの継承概念に付随する属性概念でありうんぬんかんぬん」とモデリングにまる1日かけて完璧な言語を目指すことより、「あー、毎回このセキュリティ設定メソッドを全コントローラクラスにコピペするのは面倒だなー。一箇所変えたらメソッド全部書き換わったらいいのに。もうまじ、classの直下にrequire書けないかな」を達成するのがPHPですよね。

ついでに、PythonRubyでも同じことをしてみました。

class Animal:
    pass

class Bird(Animal):
    def hasBeak(self):
        return True
    def hasWings(self):
        return True
    def hasFeather(self):
        return True

class FlyingAnimal:
    def fly(self):
        return "flying"

class SwimmingAnimal:
    def swim(self):
        return "swimming"

class Swallow(Bird, FlyingAnimal):
    pass

class Penguin(Bird, SwimmingAnimal):
    pass

pingu = Penguin()
print "pingu"

if isinstance(pingu, Bird):
    print "has beak"
if isinstance(pingu, FlyingAnimal):
    print "can fly"
if isinstance(pingu, SwimmingAnimal):
    print "can swim"


class InWaterSwimmingAnimal(SwimmingAnimal):
    def swim():
        return "swimming in water"

class OnWaterSurfaceSwimmingAnimal(SwimmingAnimal):
    def swim():
        return "swimming on water surface";

class Penguin2(Bird, InWaterSwimmingAnimal):
    pass

class Swan(Bird, FlyingAnimal, OnWaterSurfaceSwimmingAnimal):
    pass

pingu = Penguin2()
swan = Swan()

print "pingu2 is ", pingu.swim()
print "swan is ", swan.swim()

Pythonの多重継承はScalaのトレイトに似ています。というか実は逆に、Scalaトレイトが多重継承のスーパーセットを目指して設計されているのが実際なんですけどね。Scalaはまだその段階にはないかもです

class Animal
end
class Bird < Animal
    def hasBeak()
        return true
    end
    def hasWings()
        return true
    end
    def hasFeather()
        return true
    end
end

module FlyingAnimal
    def fly
        return "flying"
    end
end

module SwimmingAnimal
    def swim
        return "swimming"
    end
end

class Swallow < Bird
    include FlyingAnimal
end

class Penguin < Bird
    include SwimmingAnimal
end

pingu = Penguin.new
puts "pingu"

if pingu.is_a? Bird
    puts "has beak"
end

if pingu.is_a? FlyingAnimal  #moduleを含んでいるとis_aに応答する
    puts "can fly"
end

if pingu.is_a? SwimmingAnimal  #moduleを含んでいるとis_aに応答する
    puts "can swim"
end


module InWaterSwimmingAnimal
    def swim
        return "swimming in water";
    end
end
module OnWaterSurfaceSwimmingAnimal
    def swim
        return "swimming on water surface";
    end
end

class Penguin2 < Bird
    include InWaterSwimmingAnimal
end

class Swan < Bird
    include FlyingAnimal
    include OnWaterSurfaceSwimmingAnimal
end

pingu = Penguin2.new
swan = Swan.new

puts "pingu2 is #{pingu.swim}"
puts "swan is #{swan.swim}"

Rubyのmix-inがまさにちょうど、PHP5.4のtraitオンリー版(インターフェースによる制約のまったくない完全なダックタイピング)と似ています。mixinを持っているかどうかの判別にis_aを使えるぶん、PHP5.4のよりもやや型っぽいですが、本質的には型制約じゃないイメージ。

という意味では、PHP5.4は(いつものように)実はちょっと用語選択を間違っているのかも。本当は、mixin というシンタックスを作り、それを interface と組み合わせて上手に使うことを、概念上の言葉として「トレイト」と呼ぶのが本当なんだろうなと、そんな気がしないでもないです。

そういえばちなみに、あてずっぽうで動かしたこんなコードがPHP5.4.0alpha1で動作しました。

<?php
$ref = new ReflectionObject($pingu);
print_r($ref->getInterfaceNames());
print_r($ref->getTraitNames());
<?php
$ref = new ReflectionObject($pingu);
print_r($ref->getInterfaces());
print_r($ref->getTraits());

そうとう頑張れば、「特定のトレイトを持っているかどうか」をマーカーインターフェース的に使って、挙動の判別に使うことはできるかもしれません。まあ、ユーザコードでやるようなことではないので、そこは次世代のフレームワークに任せたほうがいいですね。ところで次世代っていつの話やねん。

最後に、PHP5.4.0を単体でCLI実行する気にさせてもらえたあの記事に感謝します。
現状のPHP環境はそのままで、PHP 5.4 を試す - Shin x blog