Edited at

HackのMemoizeとは


Memoize

MemoizeはHackのみに用意されている機能で、

PHPにはない機能です。

が、PHPでよく見かけるコードにあるものです。

例として下記の様なSingletonはよく見かけるものだと思います。

<?hh // strict

namespace Acme;

class PhpSingleton {

private static ?PhpSingleton $singleton;

private function __construct() { }

public static function getInstance(): PhpSingleton {
return self::$singleton ?? self::$singleton = new self();
}
}

この場合、constructを通じてインスタンス生成が行われないため、

Hackでは、 private static PhpSingleton $singleton; と記述するとエラーとなるため、

nullableとする必要があります。

これをHackのMemoizeを使うと下記の様に記述できます。

<?hh // strict

namespace Acme;

class HackSingleton {

<<__Memoize>>
public function getInstance(): HackSingleton {
return new self();
}
}

Hackではメソッドに <<__Memoize>> とAttribute(アノテーション)を記述することで

戻り値を保管しておくことができるため、メモ化を利用することができます。

いわゆるシングルトンとしてインスタンス自体を保管することも可能、という機能です。

当然HHVMのメモリに保持されるため、

大量の値を保管すればメモリが多く使用されます。

またプロセス間の共有は行われません。


Memoize + 引数

次の様なケースは、実際の開発でも多く利用することがあります。

<?hh // strict

namespace Acme;

class HackMemoize {

private ImmVector<string> $iv = ImmVector{
'Vector',
'Map',
'Set'
};

<<__Memoize>>
public function find(int $index): string {
if($this->iv->containsKey($index)) {
return $this->iv->at($index);
}
return '';
}
}

Immutableなコレクションから必要に応じて値を取得する、など

フレームワークなどでも利用される、設定値の取得などに利用することができます。

次の様なコードの場合、一度メモ化された場合は値を変更することはできません。

<?hh // strict

namespace Acme;

class HackMemoize {

private Vector<string> $iv = Vector{
'Vector',
'Map',
'Set'
};

<<__Memoize>>
public function find(int $index): string {
if($this->iv->containsKey($index)) {
return $this->iv->at($index);
}
return '';
}

public function append(string $value): void {
$this->iv->add($value);
}
}

<?hh // strict

use type Acme\HackMemoize;

require __DIR__ .'/vendor/hh_autoload.php';

<<__Entrypoint>>
function main(): void {
$hm = new HackMemoize();
var_dump(
$hm->find(1),
$hm->find(1),
$hm->find(0),
$hm->find(2),
$hm->find(3),
);
// Vectorに値を追加しても一度メモ化されると変更はできません
$hm->append('Go');
var_dump(
$hm->find(1),
$hm->find(1),
$hm->find(0),
$hm->find(2),
$hm->find(3), // 空文字が返却されます(Goは返却されない)
);
}

Memoizeが記述されたメソッドの引数ですが、

全ての引数に対して有効ではなく、いくつか制約があります。

制約についてみていきましょう。


引数に利用できないもの

残念ながら 可変長の引数、参照渡しの引数、

コールバック、ジェネレーターなどは利用できません。


引数に利用できるもの

引数には、下記のものが利用できます。

bool、int、float、string、

Nullable(e.g., ?int)、配列とコレクション

またはIMemoizeParamインターフェースを実装したクラスとなります。


IMemoizeParam

次のクラスは、Memoizeが記述されたメソッドの引数に利用することはできません

<?hh // strict

namespace Acme;

final class Memo {

public function __construct(
private int $identifier
) { }

public function getIdentifier(): int {
return $this->identifier;
}
}

これを利用するためには、IMemoizeParamを実装しなければなりません。

このインターフェースで実装しなければいけなメソッドは、

public function getInstanceKey(): string;

となります。

これに対応したメソッドを用意すれば、利用可能となります。

<?hh // strict

namespace Acme;

use function strval;

final class Memo implements IMemoizeParam{

public function __construct(
private int $identifier
) { }

public function getInstanceKey(): string {
return strval($this->identifier);
}

public function getIdentifier(): int {
return $this->identifier;
}
}

用途によってはいろんな利用が考えられると思います。

シングルトンなどはこれだけでも十分に機能させることができますので、

様々な場面で導入してみましょう。