Help us understand the problem. What is going on with this article?

Haxeから見る厳密に型定義されたPHP

More than 3 years have passed since last update.

PHPが型を厳密に定義するためにはいくらかのテクニックが必要である

PHPという言語は皆様御存知の通り動的に型付けを行う言語であり、予め値や振る舞いがどのような型を持つかを定義することが出来ません。

そもそも実行時にコンパイルされる言語なので型という概念をもって云々言うのは違う気もしなくもないのですが、
最近の便利なツールのお陰で静的型付け言語のような事前の型情報チェックというのはPHPでもある程度可能になってきています。

ある程度の型付けが可能とは言っても、多くの静的型付け言語のような型を持つことを前提に言語機能が実装されている訳ではないため、静的型付け言語のもつ機能を実現しようとした場合にはある種テクニックのようなものが必要となってきます。

Haxeという静的型付け言語が変換するPHPのコードを見て、 「PHPが静的な型付けで書かれるならこうなる!」 というPHPので静的型付け言語のような機能を実現する場合にテクニックを見てみることにしましょう。

Haxeとは?

ここで詳細に説明するより良い説明が沢山あるのでリンク先参照して下さい。
http://ja.wikipedia.org/wiki/Haxe
http://www.slideshare.net/sipojp/haxe-24876418
http://ics-web.jp/lab/archives/1329

簡単に言うとHaxeは静的型付け言語で、Haxeで書いたコードは他の色々な言語に変換出来るという特徴を持った言語です。
昨年あたりのAltJsブームで名前ぐらいは耳にしたことがある人もいるかと思います。

まずは定番のHelloWolrd

import php.Lib;

class Index {
    staitc function main() {
        Lib.print('Hello world');
    }
}

たかだかHello worldするだけなのにクラスを定義してメソッドを作って、ライブラリをインポートして…
とても冗長ですね。
静的型付け言語であればこのような冗長さはどうしようもないので目を瞑りましょう。

さて、このプログラムをコンパイルしたらどのようなPHPが出力されるでしょうか?

出力ディレクトリ構成

output-dir/
  lib/
    haxe/
      ds/
        IntMap.class.php
        StringMap.class.php
    php/
      Boot.class.php
      Lib.class.php
    IMap.interface.php
    Index.class.php
    Std.class.php
  index.php

たかだかHello worldを出力するだけなのに大分クラスファイルが作成されていますね。
output-dir/index.phpがアプリケーションのエントリポイントになり、
output-dir/lib/Index.class.phpがHello worldを出力するためのクラスになります。
output-dir/lib/Boot.class.phpにはオートローダーの定義や、Haxeの機能をPHPで表現するための関数、クラス群が定義されています。
その他はHaxeが提供する標準ライブラリ群が定義されます。

Haxeが変換したPHPの中を見てみる

<?php

class Index {
    public function __construct(){}
    static function main() {
        php_Lib::hprint("Hello world");
    }
    function __toString() { return 'Index'; }
}

php_Lib::hprintというメソッドを利用してHello worldを出力しています。
php_Lib::hprintの中身は単純なechoではなくオブジェクトでも配列でも文字列として出力する処理になります。

ちなみにPHPでのHello worldは…

https://twitter.com/webbingstudio/status/356304880555659264

最小のコード?で実現可能です、PHPの真骨頂ですね!

スカラー値のオブジェクト

PHPでは配列も文字列も数値もオブジェクトではありません。
なので、これらの値に対して色々操作しようとすると、便利関数を何重にもかけないといけないため可読性が損なわれてしまいます。

Haxeでは配列も文字列も数値もオブジェクトなのでスッキリプログラムを書くことが出来ます。

class Index {
    static function main() {
        'Hello world'.substr(0, 5).toLowerCase(); // -> hello

        [1,2,3,4,5] // -> [4, 8]
            .filter(function(num) {
                return if (num%2 == 0) true else false;
            })
            .map(function(num) {
                return Math.pow(num, 2);
            });
    }
}

メソッドチェーンでスッキリ書くことが出来ますね。
このコードがPHPに変換されるとどうなるでしょう?

<?php

class Index {
    public function __construct(){}
    static function main() {
        strtolower(_hx_substr("Hello world", 0, 5));
        _hx_deref((new _hx_array(array(1, 2, 3, 4, 5))))->filter(array(new _hx_lambda(array(), "Index_0"), 'execute'))->map(array(new _hx_lambda(array(), "Index_1"), 'execute'));
    }
    function __toString() { return 'Index'; }
}
function Index_0($num) {
    {
        if(_hx_mod($num, 2) === 0) {
            return true;
        } else {
            return false;
        }
    }
}
function Index_1($num1) {
    {
        return Math::pow($num1, 2);
    }
}

文字列の処理

Haxeで書いたコードのうち、'Hello world'.substr(0, 5).toLowerCase();に対応するPHPのコードがstrtolower(_hx_substr("Hello world", 0, 5))となります。

Haxeでは型の戻り値が一定になるように務めるため、通常のPHPのsubstrでなく戻り値が常にstringになる_hx_substrが利用されます。
(PHPのsubstrfalseを返すケースが存在する)

配列の処理

配列の処理の場合はnew _hx_array(array(1, 2, 3, 4, 5)のように一度内部的に配列のオブジェクトを生成して処理を行います。

引数に指定した無名関数については、Index_0Index_1といった連番で定義されます。
Haxeが変換するPHPはバージョン5.1以上で動作することを保証するために無名関数で出力されることはありません。

Enum

Enumを簡単に説明すると、 特定の値のみをとる型 です。

コードで見てましょう。

enum Status {
    Hima;
    Isogasii;
}

class Index {
    static function main() {
        Iam.now = Status.Hima;
    }
}

class Iam {
    public static var now: Status;
}

HimaIsogasiiという状態をもつStatusenumが定義されました。
Iamクラスのnowプロパティの型をStatusと定義した時に、nowHimaIsogasiiの2つの状態しか持たないことが保証されます。
この状態の保証によってアクセサメソッドを作らなくても意図しない値が設定されるという異常事態が発生しなくなります、便利ですね。

さて、出力されるPHPのコードはどうなっているでしょうか?

// それぞれ別のファイルに出力されますが、便宜上一箇所に記載しています。

class Index {
    public function __construct(){}
    static function main() {
        Iam::$now = Status::$Hima;
    }
    function __toString() { return 'Index'; }
}

class Iam {
    public function __construct(){}
    static $now;
    function __toString() { return 'Iam'; }
}

class Status extends Enum {
    public static $Hima;
    public static $Isogasii;
    public static $__constructors = array(0 => 'Hima', 1 => 'Isogasii');
}
Status::$Hima = new Status("Hima", 0);
Status::$Isogasii = new Status("Isogasii", 1);

class Enum {
    public function __construct($tag, $index, $params = null) {
        $this->tag = $tag;
        $this->index = $index;
        $this->params = $params;
    }

    public $tag;

    public $index;

    public $params;

    public function __toString() {
        return $this->tag;
    }
}

特定の値しか取り得ないというのはPHPで考えればクラスを実装するという方法が取れると思いますが、
Haxeが出力するコードはまさにenumのクラスを実装することで同等の機能を実装しています。

HaxeのEnumは他にもパラメタ付きの状態を定義出来たりするのでとても便利です。
以下の投稿によくまとめられているので興味が湧いてきたら一読しておくと良いと思います。
(WEBサービス開発用のパターンではないので、WEB開発脳のまま読むと分かり難いかもしれませんが…)

http://qiita.com/shohei909/items/fc0ef573057fb40f68cd
http://qiita.com/shohei909/items/454ff5d3be46c6064074

(構造体)匿名オブジェクト

Haxeではわざわざクラスを定義しなくても、匿名オブジェクトという機能で型を定義することが出来ます。

typedef User = {
  name: String,
  birth: Date
}

class Main {
  inline static function main() {
    var user1: User = {
      name: "Tom",
      birth: new Date(1980, 12, 23, 0, 0, 0)
    };
  }
}

PHPではどのように表現されるでしょうか?

class Main {
    public function __construct(){}
    static function main() {
        $user_name = "Tom";
        $user_birth = new Date(1980, 12, 23, 0, 0, 0);
    }
    function __toString() { return 'Main'; }
}

ただの変数になりました。
PHPで匿名の型というのを表現する場合にはHaxeでは命名規則で解決するようです。

但しこのような出力をするのは匿名型を1つのメソッドのスコープ内で参照する場合であり、
匿名型が複数のメソッドに参照をもつ場合は匿名型のオブジェクトが生成されます。

その他の機能

  • プロパティのアクセス制御
  • ジェネリック型
  • パターンマッチ
  • 抽象型
  • マクロ

型に関する機能だけでも、上記のような便利な機能が多くあります。
全て書いていくと長くなりすぎるので(書くのも大変だし…)興味が湧いたら実際に触ってみるといいと思います。
windowsであればHaxeのインストールが簡単にできて、高性能なIDE(FlashDevelop)もフリーソフトとして提供されているので直ぐに利用出来ます。
(Mac、Linuxはインストールがちょっと面倒だった記憶が…)

HaxeでPHPを書く場合の注意点

ここまでHaxeは変換したPHPのコードを見てきましたが、PHPで型を厳密に定義するにはコードをかなり冗長に書かなければならないことが分かると思います。

もし、Haxeでコードを書いてしまえるならPHPをあえて書く必要も無いように思えてきますが(思えてこない人はごめんなさい。)果たしてHaxeで書くPHPは実用的なのでしょうか?

残念ながら、Haxeを利用する場合には下記にあげるようなクリティカルなデメリットが存在します。

外部ライブラリは利用出来ない

Haxeでは文字列も配列の独自のオブジェクトとして扱われるため、
PHPの外部ライブラリが期待するような引数を受け取ることが出来ません。

つまり、Haxeを利用する場合PHPの外部資産に頼ることが出来なくなります。

パフォーマンスが出ない

Haxeでは型を厳密に定義するために、殆どの値をオブジェクトとして定義したPHPを出力します。
このためインスタンス生成のコストが通常のPHPより圧倒的に多くなりパフォーマンスが劣化してしまいます。

リソースの埋め込みやメソッドのinline化などパフォーマンスを改善する方法もありますが、
トータルで見ればやはり通常のPHPのほうが高いパフォーマンスを発揮出来るはずです。

それでもHaxe to PHPで開発してみたいなら…

公式サイトでは、レガシーなホスティング環境で利用する と書かれています。
Haxeが出力するPHPは対象バージョンが5.1以上のためレガシーな環境(PHP5.1、5.2を利用するような環境)を強制されるのであれば、Haxeの利用を一考する価値があるのではないでしょうか?
(逆にそれ以外の環境で利用するメリットはほぼ無いものと考えていいでしょう。)

ドキュメントが圧倒的に少ないのですが、一応はフルスタックなフレームワーク(ufront)も存在しているので開発を行う不便は無いはずです。

終わりに…

Haxeの出力するPHPのコードを見てみると、型の厳密さを定義するということは多くのオブジェクトの定義と冗長な記述が必要になることが分かります。

PHPと付き合うということは(というか動的型付け言語と付き合うということは)、
型の厳密さが無い上で、いかにそのコードの信頼性を担保していくかということを意識していく必要があると考えさせられます。

よりよいツールの利用、ユニットテストなどなど…
PHP開発でもコードの信頼性を担保するための仕組みは十分に整備されています。
普段の開発でもコードの信頼性を担保するという意識を見失わずにより良いコードを生産していきたいものですね。

k-motoyan
プログラミング楽しいよ
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away