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

# Predefined Attributes

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

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

## \_\_AcceptDisposable, \_\_ReturnDisposable

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

```hack
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は下記の記述方法となります。

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

## \_\_ConsistentConstruct

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

[ConsistentConstruct](https://qiita.com/ytake/items/f9c827019961700b5b2f#__consistentconstruct)

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

## \_\_Deprecated

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

```hack
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<T>とConsistentConstructを
組み合わせるのがお勧めです。

## \_\_Enforceable

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

```hack
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から警告が出力されます。

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

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

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

```hack
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に型を明示する場合に利用します。

```hack
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に警告されません。

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

values_are_equal(1, 'test');
```

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

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

## \_\_EntryPoint

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

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

## \_\_LateInit

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

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

```hack
class Foo {}

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

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

## \_\_Memoize

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

[HackのMemoizeとは](https://qiita.com/ytake/items/be07d9013237e7dfde72)

## \_\_Newable

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

```hack
<<__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とは](https://qiita.com/ytake/items/7c6ddcbfc8334d05f7ec#sealed%E3%81%A8%E3%81%AF)

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

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