Collections: Examples

The following are various examples on how to use Hack collections.

Simple Vector Usage

This simple example shows you how to create, query, add items to and remove items from a Vector.

<?hh

namespace Hack\UserDocumentation\Collections\Examples\Examples\Vec;

function use_vector(): void {
  $fruit_basket = Vector {'Banana', 'Apple'}; // initialize
  $fruit_basket[] = 'Orange'; // Add to the next available index, which is 2
  $fruit_basket[] = 'Banana'; // You can have duplicates
  var_dump($fruit_basket);
  try {
    // Can't use an explicit index that doesn't exist, even if it would be the
    // next available index.
    $fruit_basket[4] = 'Plum';
  } catch (\OutOfBoundsException $ex) {
    var_dump($ex->getMessage());
  }
  // Query the fruit at index 1
  var_dump($fruit_basket[1]);
  // Iterate over the entire fruit basket using foreach
  foreach ($fruit_basket as $fruit) {
    // This should only be a query. Do not add or remove from a Vector while
    // in a foreach
    var_dump($fruit);
  }
  // Iterate over some of the fruit basket using for and ints
  for ($i = 0; $i < $fruit_basket->count() - 2; $i++) {
    if ($i % 2 === 0) {
      // We can change the vector in a for loop by index
      $fruit_basket[$i] = 'Grape';
    }
  }
  var_dump($fruit_basket);
  // Remove an item
  $fruit_basket->removeKey(0);
  var_dump($fruit_basket);
}

use_vector();
Output
object(HH\Vector)#1 (4) {
  [0]=>
  string(6) "Banana"
  [1]=>
  string(5) "Apple"
  [2]=>
  string(6) "Orange"
  [3]=>
  string(6) "Banana"
}
string(30) "Integer key 4 is out of bounds"
string(5) "Apple"
string(6) "Banana"
string(5) "Apple"
string(6) "Orange"
string(6) "Banana"
object(HH\Vector)#1 (4) {
  [0]=>
  string(5) "Grape"
  [1]=>
  string(5) "Apple"
  [2]=>
  string(6) "Orange"
  [3]=>
  string(6) "Banana"
}
object(HH\Vector)#1 (3) {
  [0]=>
  string(5) "Apple"
  [1]=>
  string(6) "Orange"
  [2]=>
  string(6) "Banana"
}

Simple Map Usage

This simple example shows you how to create, query, add items to and remove items from a Map.

<?hh

namespace Hack\UserDocumentation\Collections\Examples\Examples\UsingMap;

function use_map(): void {
  $users = Map {1 => 'Joel', 2 => 'Fred'}; // initialize using keys and values
  try {
    // You must specify an explicit key when adding an item to a map
    // You will also get an Invalid Assignment type error when doing the below
    $users[] = 'Matthew'; // Add to the next available index, which is 2
  } catch (\InvalidArgumentException $ex) {
    var_dump($ex->getMessage());
  }
  // Instead add to a map this way
  $users[3] = 'Matthew';
  // Or this way
  $users[] = Pair {4, 'Rex'};
  var_dump($users);
  // Query the user at id 1
  var_dump($users[1]);
  // Iterate over the entire fruit basket using foreach
  foreach ($users as $id=>$name) {
    // This should only be a query. Do not add or remove from a Vector while
    // in a foreach
    echo 'The user name at id: ' . strval($id) . ' is ' . $name . PHP_EOL;
  }
  // Remove an item
  $users->removeKey(2);
  var_dump($users);
}

use_map();
Output
string(37) "Parameter must be an instance of Pair"
object(HH\Map)#1 (4) {
  [1]=>
  string(4) "Joel"
  [2]=>
  string(4) "Fred"
  [3]=>
  string(7) "Matthew"
  [4]=>
  string(3) "Rex"
}
string(4) "Joel"
The user name at id: 1 is Joel
The user name at id: 2 is Fred
The user name at id: 3 is Matthew
The user name at id: 4 is Rex
object(HH\Map)#1 (3) {
  [1]=>
  string(4) "Joel"
  [3]=>
  string(7) "Matthew"
  [4]=>
  string(3) "Rex"
}

Using filter()

filter() allows you to create a collection that is a subset (which includes being empty) of the current collection by applying a condition on each item of the collection and if the condition is true for that element, it will be part of the new collection.

<?hh

namespace Hack\UserDocumentation\Collections\Examples\Examples\FilterM;

function filter_evens(Set<int> $numbers): Set<int> {
  return $numbers->filter($x ==> $x % 2 === 0);
  // We used a lambda above. This could have also been written as:
  //return $numbers->filter(function (int $x): bool {return $x % 2 === 0;});
}

function run(): void {
  $numbers = Set {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  var_dump(filter_evens($numbers));
}

run();
Output
object(HH\Set)#3 (6) {
  int(0)
  int(2)
  int(4)
  int(6)
  int(8)
  int(10)
}

Note that we are using a lambda to create the filtering operation. We could have also used a normal closure as well.

Using map()

map() allows you to create a collection that has the same elements as the collection on which map() is called, but with some operation applied to each element.

<?hh

namespace Hack\UserDocumentation\Collections\Examples\Examples\MapM;

function apply_prefix(Vector<string> $phrases): Vector<string> {
  return $phrases->map($x ==> 'Hello ' . $x);
  // We used a lambda above. This could have also been written as:
  //return $phrases->map(function (string $x): string {return 'Hello' . $x;});
}

function run(): void {
  $phrases = Vector {'Joel', 'Rex', 'Fred', 'Matthew', 'Tim', 'Jez'};
  var_dump(apply_prefix($phrases));
}

run();
Output
object(HH\Vector)#3 (6) {
  [0]=>
  string(10) "Hello Joel"
  [1]=>
  string(9) "Hello Rex"
  [2]=>
  string(10) "Hello Fred"
  [3]=>
  string(13) "Hello Matthew"
  [4]=>
  string(9) "Hello Tim"
  [5]=>
  string(9) "Hello Jez"
}

Note that we are using a lambda to create the mapping operation. We could have also used a normal closure as well.

Reference Semantics

Hack collections have reference semantics while arrays have value semantics.

<?hh

namespace Hack\UserDocumentation\Collections\Examples\Examples\Reference;

function vec_ref(Vector<int> $vec): Vector<int> {
  for ($i = 0; $i < $vec->count(); $i++) {
    // Changing the vector here actually changes the vector from the calling
    // site
    $vec[$i] += 2;
  }
  return $vec;
}

function arr_value(array<int> $arr): array<int> {
  for ($i = 0; $i < count($arr); $i++) {
    // Changing the array does not change the array at the calling site
    $arr[$i] += 2;
  }
  return $arr;
}

function run(): void {
  echo "-- VECTOR HAS REFERENCE SEMANTICS --\n\n";
  $vec = Vector {0, 1, 2, 3, 4};
  var_dump($vec);
  vec_ref($vec);
  var_dump($vec);
  echo "\n\n-- ARRAY HAS VALUE SEMANTICS --\n\n";
  $arr = array(0, 1, 2, 3, 4);
  var_dump($arr);
  arr_value($arr);
  var_dump($arr);
}

run();
Output
-- VECTOR HAS REFERENCE SEMANTICS --

object(HH\Vector)#1 (5) {
  [0]=>
  int(0)
  [1]=>
  int(1)
  [2]=>
  int(2)
  [3]=>
  int(3)
  [4]=>
  int(4)
}
object(HH\Vector)#1 (5) {
  [0]=>
  int(2)
  [1]=>
  int(3)
  [2]=>
  int(4)
  [3]=>
  int(5)
  [4]=>
  int(6)
}


-- ARRAY HAS VALUE SEMANTICS --

array(5) {
  [0]=>
  int(0)
  [1]=>
  int(1)
  [2]=>
  int(2)
  [3]=>
  int(3)
  [4]=>
  int(4)
}
array(5) {
  [0]=>
  int(0)
  [1]=>
  int(1)
  [2]=>
  int(2)
  [3]=>
  int(3)
  [4]=>
  int(4)
}

Lazy

Hack collections provide a Scala-like view with the lazy() method. Normally, when you create an instance of a collection, you are creating a strict version of the collection. So, when you create a collection that contains one million elements, memory is allocated for all one million of those elements immediately. However, with a view via lazy(), you can create a non-strict collection such that when you use a method like map() or filter() on the collection (i.e., a transformer method), the elements are only calculated when they are accessed.

This example shows you how to use lazy() on a rather large collection and the time for both a strict and non-strict version of the collection. Since we only need 5 of the elements in the end, the lazy view actually allows us to stop after we meet our required 5 without having to actually allocate all 1000000 elements up front.

<?hh

namespace Hack\UserDocumentation\Collections\Examples\Examples\LazyM;

$vector = new Vector(range(0, 1000000));

$s = microtime(true);
$non_lazy = $vector->filter($x ==> $x % 2 === 0)->take(5);
$e = microtime(true);

var_dump($non_lazy);
echo "Time non-lazy: " . strval($e - $s) . PHP_EOL;

// Using a lazy view of the vector can save us a bunch of time, possibly even
// cutting this call time by 90%.
$s = microtime(true);
$lazy = $vector->lazy()->filter($x ==> $x % 2 === 0)->take(5);
$e = microtime(true);

var_dump($lazy->toVector());
echo "Time lazy: " . strval($e - $s) . PHP_EOL;
Output
object(HH\Vector)#4 (5) {
  [0]=>
  int(0)
  [1]=>
  int(2)
  [2]=>
  int(4)
  [3]=>
  int(6)
  [4]=>
  int(8)
}
Time non-lazy: 0.27819204330444
object(HH\Vector)#9 (5) {
  [0]=>
  int(0)
  [1]=>
  int(2)
  [2]=>
  int(4)
  [3]=>
  int(6)
  [4]=>
  int(8)
}
Time lazy: 0.015102863311768

Indexish

Indexish is an interface that represents a collection that can be indexed by a key. Keys in collections can either be a string or int. Thus, you can use Indexish to represent Hack collections that have one of those two key types.

<?hh

namespace Hack\UserDocumentation\Collections\Examples\Examples\IndexishIface;

// Since this returns Indexish, we can return both a Vector and Map from this
// function. Could also return a Pair too (not a Set since you don't access a
// Set by key).
function return_indexish(bool $which): \Indexish<int, mixed> {
  return $which ? Vector {100, 200, 300} : Map {0 => 'A', 1 => 'B', 2 => 'C'};
}

function run(): void {
  var_dump(return_indexish(true));
  var_dump(return_indexish(false));
}

run();
Output
object(HH\Vector)#1 (3) {
  [0]=>
  int(100)
  [1]=>
  int(200)
  [2]=>
  int(300)
}
object(HH\Map)#1 (3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  string(1) "C"
}

Creating a New Collection

The current concrete collection classes are marked as final. That is, they cannot be directly extended and sub-classed. However, you can create new collection classes by using the interfaces provided. This example won't create a full-blown new collection since so many methods would have to be implemented from Iterable<T> with much more scrutiny and detail than what is presented here.

However, this example does show all the methods that would have to be implemented if you wanted to create a new set collection.

<?hh

namespace Hack\UserDocumentation\Collections\Examples\Examples\SimpleColl;

final class AbsurdSet<Tv> implements \MutableSet<Tv> {
  private \Set $cs;

  public function __construct(?Traversable<Tv> $values) {
    if (!$values) {
      $this->cs = Set {0};
    } else {
      invariant($values !== null, "won't happen");
      $this->cs = new Set($values);
      foreach ($values as $value) {
        // Yes, this is ridiculous
        if (is_int($value)) {
          $this->cs[] = $value + 2;
        }
      }
    }
  }

  public function values(): \Vector<Tv> {
    return $this->cs->values();
  }
  public function keys(): \Vector<mixed> {
    return $this->cs->keys();
  }
  public function map<Tu>((function(Tv): Tu) $fn): \Set<Tu> {
    return $this->cs->map($fn);
  }
  public function mapWithKey<Tu>(
    (function(mixed, Tv): Tu) $fn): \Set<Tu> {
    return $this->cs->mapWithKey($fn);
  }
  public function filter((function(Tv): bool) $fn): \Set<Tv> {
    return $this->cs->filter($fn);
  }
  public function filterWithKey(
    (function(mixed, Tv): bool) $fn): \Set<Tv> {
    return $this->cs->filterWithKey($fn);
  }
  public function zip<Tu>(
    Traversable<Tu> $traversable): \Set<Pair<Tv, Tu>> {
    return $this->cs->zip($traversable);
  }
  public function take(int $n): \Set<Tv> {
    return $this->cs->take($n);
  }
  public function takeWhile((function(Tv): bool) $fn): \Set<Tv> {
    return $this->cs->takeWhile($fn);
  }
  public function skip(int $n): \Set<Tv> {
    return $this->cs->skip($n);
  }
  public function skipWhile((function(Tv): bool) $fn): \Set<Tv> {
    return $this->cs->skipWhile($fn);
  }
  public function slice(int $start, int $len): \Set<Tv> {
    return $this->cs->slice($start, $len);
  }
  public function concat<Tu super Tv>(
    Traversable<Tu> $traversable): \Vector<Tu> {
    return $this->cs->concat($traversable);
  }
  public function firstValue(): ?Tv {
    return $this->cs->firstValue();
  }
  public function firstKey(): mixed {
     return $this->cs->firstKey();
  }
  public function lastValue(): ?Tv {
     return $this->cs->lastValue();
  }
  public function lastKey(): mixed {
     return $this->cs->lastKey();
  }
  public function isEmpty(): bool {
     return $this->cs->isEmpty();
  }
  public function count(): int {
     return $this->cs->count();
  }
  public function contains<Tu super Tv>(Tu $value): bool {
    return $this->cs->contains($value);
  }
  public function add(Tv $value): AbsurdSet<Tv> {
    return new AbsurdSet($this->cs->add($value));
  }
  public function addAll(?Traversable<Tv> $values): AbsurdSet<Tv> {
    return new AbsurdSet($this->cs->addAll($values));
  }
  public function clear(): \Set<Tv> {
    return $this->cs->clear();
  }
  public function getIterator(): \KeyedIterator<mixed, Tv> {
    return $this->cs->getIterator();
  }
  public function items(): \Iterable<Tv> {
    return $this->cs->items();
  }
  public function lazy(): \KeyedIterable<mixed, Tv> {
    return $this->cs->lazy();
  }
  public function remove(Tv $value): AbsurdSet<Tv> {
    return new AbsurdSet($this->cs->remove($value));
  }
  public function toArray(): array<Tv, Tv> {
    return $this->cs->toArray();
  }
  public function toImmMap(): \ImmMap<mixed, Tv> {
    return $this->cs->toImmMap();
  }
  public function toImmSet(): \ImmSet<Tv> {
    return $this->cs->toImmSet();
  }
  public function toImmVector(): \ImmVector<Tv> {
    return $this->cs->toImmVector();
  }
  public function toMap(): \Map<mixed, Tv> {
    return $this->cs->toMap();
  }
  public function toSet(): \Set<Tv> {
    return $this->cs->toSet();
  }
  public function toVector(): \Vector<Tv> {
    return $this->cs->toVector();
  }
  public function toKeysArray(): array<Tv> {
    return $this->cs->toKeysArray();
  }
  public function toValuesArray(): array<Tv> {
    return $this->cs->toValuesArray();
  }
}

$aset = new AbsurdSet(array(2, 3));
var_dump($aset);
Output
object(Hack\UserDocumentation\Collections\Examples\Examples\SimpleColl\AbsurdSet)#1 (1) {
  ["cs":"Hack\UserDocumentation\Collections\Examples\Examples\SimpleColl\AbsurdSet":private]=>
  object(HH\Set)#2 (4) {
    int(2)
    int(3)
    int(4)
    int(5)
  }
}