LoginSignup
2
0

More than 3 years have passed since last update.

2020年版 HackのAttributeを使いこなせ!

Posted at

Predefined Attributes

Hackではあらかじめ用意されているAttributeがいくつかあります。
これは現在のリリースされたPHP8ではまだ用意されていない、所謂built-inのものになります。
ネイティブ実装されているもので、
動作も非常に早くなっています。

開発時にPredefined Attributesを知っているのといないのではかなり使い勝手が変わってきますので、
是非覚えておきましょう。

__AcceptDisposable, __ReturnDisposable

Hackにはdestructはなく、代わりにusingと__disposeを利用するようになっていますが、
__dispose(\IDisposable)が実装されているオブジェクトを引数などに利用する場合に、
このattributeを利用します。

class TextFile implements \IDisposable {
  private ?int $fileHandle = null;
  private bool $openFlag = false;

  public function __construct(
    private string $fileName, 
    private string $openMode
  ) {
    $this->fileHandle = 55;
    $this->openFlag = true;
  }

  public function close(): void {
    if ($this->openFlag === false) {
      return;
    }
    $this->fileHandle = null;
    $this->openFlag = false;
    echo "Closed file $this->fileName\n";
  }

  public function __toString(): string {
    return 'fileName: '.
      $this->fileName.
      ', openMode: '.
      $this->openMode.
      ', fileHandle: '.
      (($this->fileHandle === null) ? "null" : $this->fileHandle).
      ', openFlag: '.
      (($this->openFlag) ? "True" : "False");
  }

  public function __dispose(): void {
    echo "Inside __dispose\n";
    $this->close();
  }

  <<__ReturnDisposable>>
  public static function open_TextFile(
    string $fileName,
    string $openMode,
  ): TextFile {
    return new TextFile($fileName, $openMode);
  }

  public function is_same_TextFile(
    <<__AcceptDisposable>>TextFile $t
  ): bool {
    return $this->fileHandle === $t->fileHandle;
  }
}

サンプルまんまですが、基本的な使い方はこの通りです。
__ReturnDisposableは見ての通り、\IDisposableを実装した型を戻り値として記述する際に
必須になります。
__AcceptDisposableも同様に引数で与える時に利用します。

これがあることでusingを利用しなければならないことをtypecheckerが教えてくれます。
usingは下記の記述方法となります。

  using ($f1 = new TextFile("file1.txt", "rw")) {
    // ... work with the file
  } // __dispose is called here

__ConsistentConstruct

これは以前にも紹介したものです。

ConsistentConstruct

Hackでファクトリパターンなどを利用する場合に必須となります。
これを知らなければ実装できません・・

__Deprecated

これは見てわかる通り非推奨になったことを示すAttributeです。
実行自体はさせてくれますが、警告が出ますので変更しなければならない、
ということがわかるようになります。
クラスには利用できません。

class Hoge {

  <<__Deprecated("つかえません")>>
  public function fuga(): void {
    echo 1;
  }
}

hh_clientを実行すると下記のものが出力されます。

Typing[4128] The method fuga is deprecated: つかえません [1]
-> Definition is here [2]

public/index.hack:12:8
    10 | 
    11 |   $dc = new Hoge();
[1] 12 |   $dc->fuga();
    13 |   exit();
    14 | }
       :
    17 |   
    18 |   <<__Deprecated("つかえません")>>
[2] 19 |   public function fuga(): void {
    20 |     echo 1;
    21 |   }

__DynamicallyCallable, __DynamicallyConstructible

PHPでは文字列に関数名、もしくはクラス名を与えて、
動的にコールしたりインスタンス生成ができますが、
Hackではこうした機能はありません。
どうしても使いたい場合はこのAttributeを使うことで利用できるようになります。
が、設定で無効にすることもできますので、
環境によって使える使えない、という問題になります。
なるべく使わない方がいいでしょう。

動的コールを使いたい場合は、classnameとConsistentConstructを
組み合わせるのがお勧めです。

__Enforceable

Hackではオブジェクトの評価などに is(phpでいうinstanceof)やasを利用しますが、
抽象定数の型評価をする場合にそのままでは利用できません。
利用したい場合は対象の抽象定数に記述します。

abstract class A {
  abstract const type Tnoenf;

  <<__Enforceable>>
  abstract const type Tenf;

  public function f(mixed $m): void {
    $m as this::Tenf; // OK

    $m as this::Tnoenf; // Hack error
  }
}

class B1 extends A {
  const type Tnoenf = (function (): void); // ok
  const type Tenf = (function (): void); // error
}

class B2 extends A {
  const type Tnoenf = (function (): void); // ok
  const type Tenf = int; // ok
}

上記の例でわかるように、抽象定数を評価したい場合に下記のメソッドを記述すると
typecheckerから警告が出力されます。

  public function f(mixed $m): void {
    $m as this::Tenf;
    $m as this::Tnoenf;
  }

ただしこのAttributeを記述すると関数型(無名関数)などには利用できません。
抽象クラスを継承して、具体的な型を指定した場合は問題なく利用できます。

またこの__EnforceableとreifyキーワードがつけられたGenericsを利用すると
下記のようなフィルター処理を作ることもできます。

function filter<<<__Enforceable>> reify T>(
  vec<mixed> $list
): vec<T> {
  $ret = vec[];
  foreach ($list as $elem) {
    if ($elem is T) {
      $ret[] = $elem;
    }
  }
  return $ret;
}

<<__EntryPoint>>
function main(): void {
  filter<int>(vec[1, "hi", true]);
  // => vec[1]
  filter<string>(vec[1, "hi", true]);
  // => vec["hi"]
}

__Explicit

Genericsに型を明示する場合に利用します。

function values_are_equal<<<__Explicit>> T>(T $x, T $y): bool {
  return $x === $y;
}

function example_usage(int $x, int $y, string $s): void {
  values_are_equal<int>($x, $y);

  // Genericsパラメータを指定していないためエラーに
  values_are_equal($x, $s);
}

これを指定しない場合は、下記のように書いてもtypecheckerに警告されません。

function values_are_equal<T>(T $x, T $y): bool {
  return $x === $y;
}

values_are_equal(1, 'test');

利用時に下記のように記述すれば同じように警告が出ますが、
__Explicit を使ってGenericsの型記述を強制させておいた方がいいでしょう。

values_are_equal<int>(1, 'test');

__EntryPoint

このAttributeを記述した関数は特に何もしなくてもHackが実行してくれます。
ただし利用するにはトップレベルでなければならず、
他の言語にもあるmainと同じくエントリポイントになるものなので、
一つの関数だけで利用できます。
asyncであっても問題ありません。

<<__EntryPoint>>
function main(): void {
  \printf("Hello, World!\n");
}

__LateInit

Hackではプロパティはコンストラクタか、プロパティ定義時に初期化が必要になっています。
が、それ以外で初期化をする場合に、対象のプロパティに記述することで
初期化に関するエラーを発生させずに実行できます。

主にユニットテストなどで利用するといいでしょう!
確実に通過することが担保されない処理など(決まったメソッドを外から必ずコールしなければならないなど)に
使うことはあまりいいことではないので、
できるだけ使わないようにするのが良いです。

class Foo {}

class Bar {
  // 初期化されていないので、 __LateInitがなければエラーに
  <<__LateInit>> private Foo $f;

  public function trustMeThisIsCalledEarly(): void {
    $this->f = new Foo();
  }
}

__Memoize

これは以前解説していますので、そちらを参照ください。

HackのMemoizeとは

__Newable

通常Genericsのパラメータを指定してもインスタンス生成をそのまましようとするとエラーになりますが、
これを記述すると、
Genericsのパラメータで指定したもので確実にインスタンス生成ができる場合
インスタンス生成してもエラーにならなくなります。

<<__ConsistentConstruct>>
abstract class A {
  public function __construct(int $x, int $y) {}
}

class B extends A {}

function f<<<__Newable>> reify T as A>(int $x, int $y): T {
  return new T($x,$y);
}

<<__EntryPoint>>
function main(): void {
  f<B>(3,4);             // success, equivalent to new B(3,4)
}

確実にインスタンス生成できるもの、となりますので
例のように__ConsistentConstructで指定する必要があります。
この__Newable + reifyを知っておくと便利です。

__Sealed

こちらも以前解説しています。

Sealedとは

個人的にはPHPのbuilt-inで是非欲しいAttributeです。

ほかにもいくつかありますが、今回は代表的なものだけを紹介しました。
Hackで開発する場合はどれも必要なものになりますので覚えておきましょう!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0