Using generics in Hack

3 minute read

Hack has introduced some new collection objects that will be a replacement for the array(). But what if you feel that something is missing? What if you have a problem that is easiest solved with another type of object? This is where we use generics.

Say that you want to build a chess game. You may find it useful to have a class representing the board.

class Matrix {
    private Map $matrix;
    public function __construct()
    {
        $this->matrix = new Map();
    }


    public function get(int $row, string $col): ?ChessPiece {
        if (!$this->matrix->containsKey($row)) {
            return null;
        }


        return $this->matrix->get($row)->get($col);
    }


    public function set(int $row, string $col, ChessPiece $value): void {
        if (!$this->matrix->containsKey($row)) {
            $this->matrix->set($row, new Map());
        }


        $this->matrix->get($row)->set($col, $value);
    }
}

This will probably work well for you. But when your manager ask you to build a Tic-tac-toe you have to rebuild your board like to be a Matrix[int][int]=Mark.

A better solution here is to use generics. You use generics as placeholders for types that are unknown at the time of writing. It is still statically typed because the types are defined when you are using an object of that class. Below you see an example of the matrix with generics.

class Matrix<Trow,Tcol,Tv> {


    private Map<Trow, Map> $matrix;


    public function __construct()
    {
        $this->matrix = new Map();
    }


    public function get(Trow $row, Tcol $col): ?Tv {
        if (!$this->matrix->containsKey($row)) {
            return null;
        }


        return $this->matrix->get($row)->get($col);
    }


    public function set(Trow $row, Tcol $col, Tv $value): void {
        if (!$this->matrix->containsKey($row)) {
            $this->matrix->set($row, new Map());
        }


        $this->matrix->get($row)->set($col, $value);
    }
}


$matrix = new Matrix();
$matrix->set(1,2,'hello');
$matrix->set(2,3,'world');
echo $matrix->get(1,2);

It is important to know the difference between generic and variable. A generic could be any value but can not change after it got its type. See this example with Vector<T>

function foobar() {
  $x = Vector {1, 2, 3, 4}; // T is associated with int
  $y = Vector {'a', 'b', 'c', 'd'}; // T is associated with string
}


foobar();

$x is of type Vector<int> and $y is of type Vector<string>. $x and $y are not of the same type.

It is good practice to name generic types with capital T in the beginning. You may use as many generics you want. Generic types can be used on both classes and functions.

Not supported features of generics

One documented unsupported of generics is to create a new instance of a generic type. This does not work.

class Foo&lt;T&gt; {
	private ?T $data;


	public function __construct(?T $data) {
		$this-&gt;data=$data;
	}


	public function getNewOrExisting():T {
		if ($this-&gt;data===null) {
			return new T();
		}
		return $this-&gt;data;
	}
}

Conclusion

Generics are great when you want to create a wrapper object for something or when you are making your own data types. I am about to write a blog post about how the type checker works and how it works with generics. Stay updated.

Categories:

Updated:

Leave a Comment