新言語Hackで最強PHPerになろう!

  • 638
    いいね
  • 0
    コメント

新言語 Hack とは

こんにちは、新言語 Hack が Facebook より OSS としてリリースされましたね。

詳細は以下の通り。
https://code.facebook.com/posts/264544830379293/hack-a-new-programming-language-for-hhvm/

新言語 Hack は
HHVM 向けの開発言語で静的型付き言語の性質を取り入れ PHP での素早い開発を実施し、PHPとの互換性があります

http://hacklang.org/
http://hhvm.com/

HHVM (HipHop Virtual Machine for PHP) とは、Facebook が OSS で開発している PHP 用の JIT コンパイラです。
HHVM は PHP のコードをダイナミックにバイナリコードへと変換することで高速実行の実現を目指しています。

これは、PHPer にとって最強 Hacker になれる大チャンスです!
HipHop 大好きエンジニアには持ってこいではないでしょうか。


2017年5月追記
本記事は2014年のものです。2017年3月時点で Hack 本を書きましたので、最新情報はそちらも参考にしてください。

プログラミングHHVM Hack 執筆しました


=
追記
HHVM Hackをすぐに試せるサーバをAWS AMIを公開しましたので、こちらを参考にすぐに動かして試して下さい
http://qiita.com/yone098@github/items/c996f5fd8a25af9b1ce3

=

エンジニアの皆さんなら、コードを見た方が早いですね。
Hack のコードは以下のような感じになります。
今まで <?php だったものを <?hh とするだけでgenerics、Nullable Type、Collection, Lambda, Async, Await, Tuple, Override Attribute, Type aliasing などが利用出来るようになります。
つまり、互換性があるため既存のPHPのシステムを全てHHVMに乗り換える事も出来ます。(PHPファイルの先頭を一括置換するだけ)

mopemope.hh

<?hh
// generics
class Mopemope<T> {
  public function __construct(private T $data) {}
  public function get(): T { return $this->data; }
  public function set(?T $x): void { $this->data = $x; }
}

function generics_test(): Mopemope<string> {
  $x = new Mopemope("松原!");
  return $x;
}

function apply_int<T>((function(int): T) $callback, int $value): T {
    return $callback($value);
}

$func = function(int $num) : string {
  return "mopemope is " . $num . " years old.";
};
var_dump(apply_int($func, 28));


// newtype を宣言
newtype user_name = string;

function get_user_name(): user_name {
  return "yone098";
}

// 引数は user_name 型しか受け付けない
function get_user_id(user_name $name) : int {
  // user_name で DB から検索処理など
  $user_id = find_by_user_name($name);
  return $user_id;
}

// 以下は型が違うためエラー
// get_user_id(12345);

echo(get_user_id("mopemope") . "\n");

$ hhvm mopemope.hh
で実行します。
なんだか PHP4 を触っていた人には別言語に見えますね。

Hack を実際に動かす

軽くコードだけみてだいたいは分かるとは思いますが、せっかくなので実際にインストールして動かしてみましょう。

install

私は macosx にインストールを試みましたが、以下にあるように正式にサポートされてないのと、issueと同様のエラーが出てインストール出来なかったので ubuntu 13 で試しました。
Mac OS X and FreeBSD are not officially supported or tested but for those who want to experiment or help out:

インストールはこちら
https://github.com/facebook/hhvm/wiki#installing-pre-built-packages-for-hhvm

ubuntu 13 install

私は ubuntu 13 にインストールを行いました。

===

注意 2014/4/2 追記
この記事はHHVM2.4であり最新版HHVM3.0.1はsudo apt-get install hhvmだけでインストール出来ます。
HHVM Hack(HHVM3.0.1)をインストール済み環境のAWS AMIを用意しましたので、そちらで手軽に試して下さい。
http://qiita.com/yone098@github/items/c996f5fd8a25af9b1ce3

またHHVM3系からは、hhvmビルドインのwebserverは使用出来なくなりましたので、webserver fastcgt hhvmの構成で使用します。
設定ファイルも、hdfではなくphp.iniserver.iniに変わっていますのでご注意ください。

===

$ sudo apt-get install autoconf automake binutils-dev build-essential cmake g++ git \
  libboost-dev libboost-filesystem-dev libboost-program-options-dev libboost-regex-dev \
  libboost-system-dev libboost-thread-dev libbz2-dev libc-client-dev \
  libc-client2007e-dev libcap-dev libcurl4-openssl-dev libdwarf-dev libelf-dev \
  libexpat-dev libgd2-xpm-dev libgoogle-glog-dev libgoogle-perftools-dev libicu-dev \
  libjemalloc-dev libmcrypt-dev libmemcached-dev libmysqlclient-dev libncurses-dev \
  libonig-dev libpcre3-dev libreadline-dev libtbb-dev libtool libxml2-dev zlib1g-dev libevent-dev \
  libmagickwand-dev libxslt1-dev ocaml-native-compilers

$ mkdir dev
$ cd dev
$ export CMAKE_PREFIX_PATH=`pwd`
$ git clone git://github.com/facebook/hhvm.git
$ cd hhvm
$ git submodule init
$ cd ../hhvm
$ git submodule update --init
$ cmake .
$ make

ビルドには少々時間がかかりますが HipHop でも聞きながら気長に待って下さい。

ビルドが成功したら、hphp/hhvm/hhvmフォルダにPATHを通して下さい。
正しくインストールされたかは hhvm コマンドを実行して確認出来ます。

yone098$ hhvm --version
HipHop VM 3.0.0-dev (rel)
Compiler: heads/master-0-g5b00837d9b2ecc28e6225336296be88449637b2e
Repo schema: b602fe3a78ec9eec7b65ec874110b9323ceabf88

実行方法

実行方法は2種類あります。
hhvm コマンドで hack ファイルを指定するものと、ビルドインされている webserver を起動させる方法です。

hhvm your_hhvm_file

sudo hhvm -m server -c config.hdf

これであなたも HipHop な Hacker の仲間入りです。

Hack の特長

それではサンプルコードを見ながら Hack の特長を見てみましょう。

Type Annotations

Hack の静的型付け。関数の引数や戻り値に明示的に型を指定出来ます。

garsue.hh
<?hh
// 引数と戻り値は bool
function garsue(bool $x): bool {
  return !$x;
}

$ret = garsue(true);
var_dump($ret);

// 型が異なるためにエラー
$str = "hoge";
garsue($str);

引数に文字列を指定して実行するとエラーが発生します。
yone098$ hhvm garsue.hh
bool(false)
HipHop Fatal error: Argument 1 passed to garsue() must be an instance of bool, string given in /home/yone098/1.hh on line 5

Nullable

null を許容するかどうかを ? で指定出来ます。
少し実用的なコードのサンプルです。

garsue.hh
///////////////////////////////////////////////////////
// ## Nullable Types
// ? 付きでNullable
interface mohikan {
  public function say(): string;
}

class mopemope implements mohikan {
  public function say(): string {
    return "コード書け!";
  }
}

// $mohikan に null が渡ってくる場合があるのでチェック
function say_args_null_ok(?mohikan $mohikan): string {
  if ($mohikan !== null) {
    return $mohikan->say();
  }
  return "mohikan が null です。";
}

// 引数 $mohikan が null なら実行されない
function say_args_null_ng(mohikan $mohikan): string {
  return $mohikan->say();
}

function test_nullable_type(): void {
  // null を指定
  say_args_null_ok(null);

  // mopemope インスタンスを指定
  $mopemope = new mopemope();
  say_args_null_ng($mopemope);

  // null 指定でエラー
  say_args_null_ng(null);
}

test_nullable_type();

実行すると、say_args_null_ng 関数の引数に null を指定しているところでエラーが発生します。

yone098$ hhvm garsue.hh
HipHop Fatal error: Argument 1 passed to say_args_null_ng() must be an instance of mohikan, null given in /home/yone098/garsue.hh on line 39

Collections

Collection として Vector, Set, Map, Pair が用意されています。
Collection には generics で明示的に型が指定出来ます。
filter 関数も用意されていますので実際にコードを動かして試してみて下さい。
関数の引数に指定する際に Map<string, int>Map<string, MyClass<string>> のような指定で可能です。
例)
function mope(Map<string, Map<string, Vector<int>>> args): void { };

garsue.hh
///////////////////////////////////////////////////////
// Collections
function test_collections(): void {
  // Vector
  echo "Collections Vection<T>\n";
  $vector = Vector<int> { 100, 200, 300, 400 };
  $vector[] = 700; // 追加
  $vector[0] = 111; // 上書き
  echo "vector->get(1):" . $vector->get(1) . "\n";
  var_dump($vector);

  $vector->removeKey(2);
  foreach ($vector as $v) { echo $v . "\n"; }

  // filter
  $v_filter = $vector->filter(function($v) { return 150 <= $v; });
  foreach($v_filter as $key => $val) {
    echo "key:" . $key . "=>" . $val . "\n";
  }

  // Map
  echo "Collections Map<K, V>\n";
  $map = Map<string, long> {"a" => 100, "b" => 200, "c" => 300};

  $map["d"] = 400;
  echo "map[0] = " . $map["a"] . "\n";
  // get
  echo "map->get(\"b\") = ". $map->get("b") . "\n";

  // contains
  var_dump($map->contains("a"));

  // map filterWithKey
  $map_filter = $map->filterWithKey(function($k, $v) {
    return ($k === "b" || $k === "c");
  });
  var_dump($map_filter);
}

test_collections();

実行結果

Collections Vection<T>
vector->get(1):200
object(HH\Vector)#1 (5) {
  [0]=>
  int(111)
  [1]=>
  int(200)
  [2]=>
  int(300)
  [3]=>
  int(400)
  [4]=>
  int(700)
}
111
200
400
700
key:0=>200
key:1=>400
key:2=>700
Collections Map<K, V>
map[0] = 100
map->get("b") = 200
bool(true)
object(HH\Map)#6 (2) {
  ["b"]=>
  int(200)
  ["c"]=>
  int(300)
}

Override

PHP における override は継承元クラスと同名のメソッドを実装することにより実現しますが typo に気付きづらい部分があると思います。
hacklang の <<Override>> にはそれを解決するものだと思います。
say_hello メソッドを sya_hello と override しても親のメソッドがコールされて継承したクラスのメソッドが呼ばれない事に気がつきにくいと思います。
hacklang の場合は、<<Override>> してメソッドをコールした際にメソッドが名が異なっている場合にはエラーが発生します。

garsue.hh
///////////////////////////////////////////////////////
// Override
class Yone {
  public function yone_098(): string {
    return "yone_098";
  }
}

class Yone098 {
  // override なのに親メソッドと異なるメソッド名
  <<Override>> public function yone_123(): string {
    return "yone_123";
  }
}

function test_override(): void {
  echo "call test_override()\n";

  $yone = new Yone098();
  echo "yone->yone_098() : " . $yone->yone_098() . "\n";
}

test_override();

実行結果

call test_override()

Fatal error: Call to undefined method Yone098::yone_098 from anonymous context in /home/yone098/hhvm_work/garsue.hh on line 115

Type Aliasing

type aliasing ではC言語でのtypedefのような機能があります。

garsue.hh
///////////////////////////////////////////////////////
// Type Aliasiling
type mopeInt = int;
function sum(mopeInt $v): int {}

newtype yone_name = string;
// 引数は yone_name 型しか受け付けない
function foo(yone_name $v): void {}

newtype mopePoint = (long, long);
function test_point(mopePoint $v): void {}

Async

async の signature example。

async_sample.hh
<?hh
async function genFoo(): Awaitable<Foo> { return new Foo(); }
async function cached_result<T>(T $x): Awaitable<T> { return $x; }
async function gen_void(): Awaitable<void> { return; }
async function gen_add(Awaitable<int> $genA, Awaitable<int> $genB): Awaitable<int> { 
  list($a, $b) = await genva($genA, $genB); 
  return $a + $b; 
}
class Preparable<T> implements Awaitable<T> { ... }
class MyPreparable extends Preparable<MyPreparable> { ... }

まとめ

この他にも、Traits, Lambda, Tuples, Typing XHP などの機能があり今PHPで開発している現場にいれてもいいのではないかと思います。

これから簡単なウェブアプリケーションを作ってパフォーマンスを計測してみようと思います。

皆さんも hacklang で素敵な PHPライフをお過ごしください!
そして最強の PHPer になりましょう!