Hackにおける配列とCollection - Hack Arrays / hsl 編

# Hack Arraysとは

HackにはPHPで利用される標準の配列に加えて、
vec、dict、keysetの三種類の配列が追加されています。

PHP配列や、HackのCollectionの両方を置き換えることができ、
かつPHPの配列とほぼ同様に利用できるため、手軽に利用できます。

Collectionはオブジェクトでしたが、このvec、dict、keysetは値扱いになっているため、
それ自体に関数などは用意されていません。
これらを操作する場合は、Collection同様にPHPと同じ配列操作の関数を利用します。
*isset、unsetは概要編でも触れた様に利用できません*

## vec
純粋配列を扱う配列です。
Vector、ImmVectorを置き換えることができるもので、
foreachなどで利用できます。

```hack:hackarray.php
<?hh
$v = vec[];
$vi = vec[1, 2, 3];
$vs = vec['a', 'b', 'c'];
```

出力は次の通り

```
vec(0) {
}
vec(3) {
  int(1)
  int(2)
  int(3)
}
vec(3) {
  string(1) "a"
  string(1) "b"
  string(1) "c"
}
```

PHPに慣れている方は違和感があるかもしれませんね。

vecかどうかを調べる場合は以下の方法があります。

```hack:hackarray.php
<?hh
$v = vec[];

// PHPでもおなじみのis_xx
\var_dump(is_vec($v));

// Hackのみで利用できるis演算子
\var_dump($v is vec<_>);
```

## dict
keyとvalueで構成される配列です。
Map、ImmMapを置き換えることができるもので、
これもforeachなどで利用できます。
配列キーはintかstringにしなければなりません。

```hack:hackarray.php
<?hh
$d = dict[];
$di = dict[1 => 'a', 2 => 'b', 3 => 'c'];
```

出力は次の通り

```
dict(0) {
}
dict(3) {
  [1]=>
  string(1) "a"
  [2]=>
  string(1) "b"
  [3]=>
  string(1) "c"
}
```

dictかどうかを調べる場合は以下の方法に。

```hack:hackarray.php
<?hh
$d = dict[];

// PHPでもおなじみのis_xx
\var_dump(is_dict($d));

// Hackのみで利用できるis演算子
\var_dump($d is dict<_, _>);
```

vecとそこまで大きく変わりません。

## keyset
keysetはvecと同様ですが、重複しない値を持つ純粋配列になります。
Set、ImmSetを置き換えることができます。
配列キーはintかstringにしなければなりません。

```hack:hackarray.php
<?hh

$ks = keyset[];
$ksi = keyset[2, 'a', 3, 1, 3];
```

入力された値をそのままの順番で、且つ重複を排除して保持しますので、
出力は次の通りです。

```
keyset(0) {
}
keyset(4) {
  int(2)
  string(1) "a"
  int(3)
  int(1)
}
```

vec, dictと同様にkeysetかどうかを調べる場合は以下の通り

```hack:hackarray.php
<?hh
$ks = keyset[];
var_dump(is_keyset($ks), $ks is keyset<_>);
```

## Hack Arrays hhi
このHackArrayの定義は、以下の通りに記述されています。

```hack:hhi
abstract final class dict<+Tk as arraykey, +Tv> implements Indexish<Tk, Tv>, XHPChild {}
abstract final class keyset<+T as arraykey> implements Indexish<T, T>, XHPChild {}
abstract final class vec<+T> implements Indexish<int, T>, XHPChild {}
```

arraykeyはこれもHack独自の型で、
用途としては、 `arraykey is special union type of int and string.` となります。
つまりstringとintを利用することができるよ、というものです。

## CollectionとHack Arrays
それぞれ置き換え可能ではありますが、
残念ながら `Vector === vec`、`Map === dict`といった比較等はできません。
比較はできませんが、CollectionからHack Arraysに変換することができます。

マニュアルに記載されている通りで、簡単です

```hack:hackarray.php
<?hh // strict

<<__Entrypoint>>
function main(): void {
  \var_dump(vec(Vector { 1, 2, 3, 1 }));
  \var_dump(dict(Map {'a' => \ord('a'), 'b' => \ord('b'), 'c' => \ord('c') }));
  \var_dump(keyset(Vector {1, 2, 3, 1 }));
  \var_dump(keyset(Set {1, 2, 3, 1 }));
}

// hhvm ./hackarray.php
```

`<<__Entrypoint>>`はAttribute(PHP等でいうアノテーションです)で、
hackのファイル実行時にこのAttributeが記載されている関数が実行されます。
goなどでもおなじみの `func main` などと同じものです。楽チン

もちろんdictやvecからMap、Vectorに変換することもできます。

```hh
<?hh // strict

<<__Entrypoint>>
function main(): void {
  \var_dump(new Vector(vec[1, 2, 3, 1]));
  \var_dump(new ImmMap(dict['a' => \ord('a'), 'b' => \ord('b'), 'c' => \ord('c')]));
  \var_dump(new Set(keyset[1, 2, 3, 1]));
}
```

通常のPHP配列も変換できます。

```hack:hackarray.php
<?hh // strict

<<__Entrypoint>>
function main(): void {
  $phpArrays = [1,2,3,'PHP Array'];
  \var_dump(vec($phpArrays));
  \var_dump(dict($phpArrays));
  \var_dump(keyset($phpArrays));
}
```

PHPの配列に、オブジェクトが混在している場合は、keysetでTypeErrorとなります。

```hack:hackarray.php
<?hh // strict

<<__Entrypoint>>
function main(): void {
  $phpArrays = [1,2,3,'PHP Array', new stdClass()];
  \var_dump(keyset($phpArrays));
}
```

上記の様にした場合は、
`It is incompatible with an object of type stdClass` となります。
Collection同様にTypecheckerで見つけることができますので、
実際の開発でも利用することが多いものの一つです。

# hsl / Hack Standard Library
[hhvm/hsl](https://github.com/hhvm/hsl)
vecやdict、keysetを操作するには、関数を利用することになりますが、
一般的に利用されるものはこのライブラリで用意されています。

## via Composer
ライブラリは基本的にComposerを経由してインストールしますが、
Hackで利用する場合は、
[[2018] HHVM/Hackの始め方 導入編](https://qiita.com/ytake/items/2aee4fdee13e7244adfe) で紹介している様に、
[hhvm-autoload](https://github.com/hhvm/hhvm-autoload) をインストールしなければなりません。

適当なプロジェクトを作成したら、composer.jsonを作成してライブラリのインストールをしていきます。
まずはcomposer.jsonを作ります。これはcomposerのコマンドで作成できます

```bash
$ composer ini
```

適当に入力しましょう。
次にhhvmを指定して `hhvm/hhvm-autoload` をインストールします。
PHP環境では解決できない機能が含まれているためインストールできません。

```bash
$ hhvm $(which composer) require hhvm/hhvm-autoload
```

まっさらなプロジェクトにインストールすると、 `hh_autoload.json` を作成してくれます。
次にやっとhslのインストールです。

```bash
$ hhvm $(which composer) require hhvm/hsl
```

*残念ながら古いバージョンのHHVM環境のままの方はインストールできませんので、
最新のLTSバージョン以上のバージョンにアップデートしましょう。

Hackを利用するため、 `.hhconfig` ファイルを作成します。
プロジェクトルートに設置すればOKですので、
composer.jsonやhh_autoload.jsonと同じディレクトリに設置します。
記述内容は以下の通りです

```text:.hhconfig
assume_php = false
ignored_paths = [ "vendor/.+/tests/.+" ]
safe_array = true
safe_vector_array = true
unsafe_rx = false
```

最後に適当なphpファイルを作成して(hhファイルでもどちらでも良いです)、
hslを利用できる様にvendor/hh_autoload.phpをrequireします。
**vendor/autoload.phpではありません*

次の内容そのままでいいでしょう。

```hack:sample.php
<?hh // strict

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

<<__Entrypoint>>
function main(): void {
  
}

```

これでHackのコードを書く準備ができました。
PhpStormなどでは補完を利用することができないため、
忘れずにVisual Studio Code(+ Hack for Visual Studio Code)かNuclideを準備しましょう。
これでコードの補完も安心です。

hslすべての関数の利用方法を解説するのは大変なので、
ほんの一握りだけ紹介します。

HH\Lib\DictやHH\Lib\Vecといった名前空間に属する関数は、
名前空間に合わせたHack Arrayが返却されます。

例としてここでは適当なdictを生成するものを使います。

```hack
function hh_random_dict(): dict<int, int> {
  $d = dict(range(11,20));
  shuffle(&$d);
  return $d;
}
```

## Dict\filter

コールバック関数を使用して、Hack Arrayのvalueでフィルタリングする関数です

```hack

use namespace HH\Lib\Dict;
require __DIR__ .'/vendor/hh_autoload.php';

<<__Entrypoint>>
function main(): void {
  var_dump(Dict\filter(hh_random_dict(), ($v) ==> $v >= 15));
}
```

asyncで利用する場合は、以下の様になります

```hack
var_dump(\HH\Asio\join(
  Dict\filter_async(hh_random_dict(), async ($v) ==> ($v >= 15))
));
```

## Dict\filter_with_key

コールバック関数を使用して、Hack Arrayのkeyでフィルタリングする関数です

```hack

use namespace HH\Lib\Dict;
require __DIR__ .'/vendor/hh_autoload.php';

<<__Entrypoint>>
function main(): void {
  var_dump(Dict\filter_with_key(hh_random_dict(), ($k, $_) ==> $k >= 2));
}
```

## Dict\from_entries

tupleで構成された配列からdict配列を生成する関数です。

```hack
<?hh 
  var_dump(Dict\from_entries(vec[
    tuple(1, 'q'),
    tuple(2, 'w'),
    tuple(3, 'e'),
    tuple(4, 'r'),
  ]));
```

## Dict\merge

dictやvecなどを一つのdictにマージする関数です。

```hack
<?hh // strict

use namespace HH\Lib\{Vec,Dict,C};
require __DIR__ .'/vendor/hh_autoload.php';

<<__Entrypoint>>
function main(): void {
  var_dump(Dict\merge(dict[1 => 2], dict[2 => 3], vec['testing']));
}
```

## Dict\map

指定したHack配列の要素にコールバック関数を適用する関数です

```hack
<?hh // strict

use namespace HH\Lib\{Vec,Dict,C};
require __DIR__ .'/vendor/hh_autoload.php';

<<__Entrypoint>>
function main(): void {
  var_dump(Dict\map(hh_random_dict(), ($v) ==> $v * 100));
}
```

vecやkeysetなどに合わせて関数も多く用意されています。
ほんの一部のみ紹介しましたが、どれもPHPの配列操作関数と大きな違いはありません。
(記法の違い程度です lambdaなど)

hslには文字列操作の関数なども含まれていますので、
開発時に利用してみましょう。