HackのCollection?
PHPでは配列や連想配列を使って開発することが多くありますが、
厳密に型を指定することができません。
HackではPHPと異なる機能を利用して、
静的型付け言語のような厳格なコードを記述できるようになっています。
まずは配列操作の違いから見ていきましょう。
PHP配列操作
不確実な値の入力を防ぐために、下記のようなクラスなりメソッドなりを用意することもあると思いますが、
この配列に対して型を指定することはできたないため、
下記例のall
で取得された配列にはどんな値でも加えることができます。
<?php
declare(strict_types=1);
class ArraySet
{
protected $collect = [];
public function add(string $key, string $value): void
{
$this->collect[$key] = $value;
}
public function all(): array
{
return $this->collect;
}
}
$a = new ArraySet();
$a->add('PHP', 'Arrays');
var_dump($a->all());
Hack配列操作
Hackではこれに対して、配列に対して型を指定することができます。
<?hh // strict
class ArraySet {
protected darray<string, string> $collect = [];
public function add(string $key, string $value): void {
$this->collect[$key] = $value;
}
public function all(): darray<string, string> {
return $this->collect;
}
}
ちなみに // strict
とは、
PHPの declare(strict_types=1);
と同じような意味を持ちますが、
Typecheckerによる型検査が一番厳格なモードで、
最近のHackではこの指定を利用するのがスタンダードです。
ただのarrayとせずに、keyとvalueからなるdarray(辞書)として、
制約を与えています。
このため、次コードを記述すると、Typechecker で引っかかるようになります。
public function all(): darray<string, string> {
$this->collect[] = 2;
$this->collect[1] = 2;
$this->collect['Hack'] = 0001;
return $this->collect;
}
Typeエラーとしては以下になり、
実行できなくなるため不確実な値が加わることを防ぐことができます。
[Hack] Typing[4110] Invalid assignment [4110]
ArraySet.php(5, 28): This is a string
ArraySet.php(14, 30): It is incompatible with an int
Typecheckerで検知されるため、
コードレビューなどでこうした指摘をすることがなくなるわけです
Collection
さらにHackではCollectionを使って厳格に型を指定し、
Mutable、またはImmutableを使うことでPHPの配列が持つ課題を解決しています。
上記のArraySetのようなクラスは
Mapなどに置き換えてアプリケーション内で使うことができます。
<?hh // strict
class HackMap {
protected Map<string, string> $collect = Map{};
public function add(string $id, string $value): void {
$this->collect->add(Pair{$id, $value});
}
public function all(): Map<string, string> {
return $this->collect;
}
}
$this->collect->add()
以外に、
$this->collect[$id] = $value
とPHPと同様に記述することができますが、
Mapのaddメソッドは、Map<string, string>
が返却されますので、
用途によって使い分けることができます。
Immutableとする場合はImmMapを利用します。
<?hh // strict
class HackImmMap {
protected ImmMap<string, string> $collect = ImmMap{
'Hack' => 'Collection'
};
}
Genericsと共に
Generics自体の解説は別で記述しますが、
Mapで扱う型に合わせてクラスを作らずともGenericsを活用することもできます。
keyとvalueで同じ型を使う場合は以下のように書くこともできます。
<?hh // strict
class HackGenericsMap<T> {
protected Map<T, T> $collect = Map{};
public function add(T $id, T $value): void {
$this->collect->add(Pair{$id, $value});
}
}
インスタンス生成時に型を指定することで利用できます。
<?hh
$stringMap = new HackGenericsMap<string>();
$stringMap->add('testing', 'hello');
$intMap = new HackGenericsMap<int>();
$intMap->add(1, 2);
PHPの例にあったallメソッドのようなメソッドがあった場合でも、
型が指定されているためintなどが入りこむことが無くなります。
Mapの他、用途に合わせて下記のものを利用します。
- 純粋配列のVector
- ユニークで順序を持った値を表す Set
Collectionは配列を拡張したものになっていますので、
PHPの配列操作の関数の多くがそのまま利用できます。
どんなものが使えるかは、ドキュメントに記載されています。
Collections: Semantics
isset、unsetを使わない
Collectionは配列と同じように利用することができますが、
残念ながらunsetやisset、といったPHPでおなじみの関数は利用できません。
issetの代わりに その1
issetを記述すると、Typecheckerにarray_key_existsを利用しなさいと指摘されます。
このため、Hackではarray_key_existsを使うことになります。
issetの代わりに contains
HH\Map::contains
Mapでキーについて検査するときはこのメソッドを利用します。
<?hh // strict
class HackMap {
protected Map<string, string> $collect = Map{};
public function mapExample(): void {
$r = $this->collect->contains('Hack');
}
}
存在しないキーを与えた場合でもboolで返却されますので、安心です。
containsを利用した後に値を取得する場合、
getやatでアクセスできますが、用途に合わせて使い分ける必要があります。
Mapのgetは
public function get(Tk $k): ?Tv;
となるため、
値がnullかそうではないかを記述しなければなりません(Typecheckerで引っかかる)
<?hh // strict
class HackMap {
protected Map<string, string> $collect = Map{};
public function contains(): void {
$key = 'Hack';
if($this->collect->contains($key)) {
$r = $this->collect->get($key);
if ($r is string) {
echo $r;
}
}
}
}
($hoge is string) はHackのみの記述方法
nullではないことをTypecheckerに示すコードを書く必要があるわけです。
こうした検査が特に必要なければ、atを利用します。
public function at(Tk $k): Tv;
確実に値が返却されることを示す場合はこちらを利用します。
ただしkeyが確実に存在するものでなければnullとなってしまい、
実行時にTypeErrorがスローされるため注意しなければなりません。
Vector、Setもほぼ同じメソッドが利用できます。
unsetの代わりに
unsetの代わりにremoveなどのメソッドがCollectionに用意されていますので、
そちらを利用します。
条件による要素の削除
removeを利用する場合に、ある条件で特定のキーだけ削除したい場合は
filterなどを利用します。
class HackMap {
protected Map<string, string> $collect = Map{};
public function filter(): Map<string, string> {
return $this->collect->filterWithKey(($key, $_) ==> $key === 'a');
}
}
Colelctionにはこれ以外にもたくさんのメソッドがありますが、
配列よりも確実な操作ができるためPHPに比べて実装コードがシンプルになる場合が多いです。
初めてHackを使う場合は、HHVMによるPHPサポートが間もなく終了しますので、
配列をCollectionに置き換えてみるところから触れてみるのも良いのではないでしょうか。
次回はvecやdictを使ってhslによる配列操作です。