Hack at a Glance — A Language a Day, Advent Calendar 2019 Day 9/24

About this ‘A Language a Day’ Advent Calendar 2019

Welcome to Day 9 of this year’s A Language a Day Advent Calendar. Today’s topic is introduction to the Hack programming language.

Facts about the language

Some facts about Hack:

  • Hack is an extension of PHP
  • Hack uses the HHVM virtual machine (HH = HipHop)
  • Hack provides both dynamic and static typing
  • Introduced in 2014
  • Created by Facebook
  • Website: hacklang.org

Installing and running Hack

You need to install the HHVM machine. Refer to the installation instructions on its website for the details. Here is a snippet you can run on Mac:

$ brew tap hhvm/hhvm
$ brew install hhvm

(Run the second command twice or manually create the /usr/local/Frameworks directory if you did not have one and got an error during the installation.)

There are two main ways of running a Hack program. First, you just do it from command line:

$ hhvm program.hack

In the second method, you start a webserver and see the results in a browser. Run the following command in a directory with your .hack files, and then navigate to http://localhost:80/program.hack to see the output (remember that Hack is a dialect of PHP, that’s why web is its native land).

$ hhvm -m server -p 8080

Static checker

Hack comes with the hh_client tool, which a fast typechecker daemon. It is possible to embed it to the IDE so that it displays the errors while you are typing. To run the tool, you need to have at least an empty configuration file .hhconfig in your project directory.

Alternatively, run the tool as a daemon that will monitor the syntax as soon as you save the file. Having this, you will always have a fresh error list in the terminal:

$ watch hh_client

Hello, World!

The minimal program that works and prints the message is a one-liner:

echo "Welcome to Hack!\n";

You can also make it a bit more official by surrounding it with a function and explicitly telling where the program must start:

<<__EntryPoint>>
function main(): noreturn {
    echo "Welcome to Hack!\n";
    exit(0);
}

The important point here is the <<__EntryPoint>> attribute. It is followed by a function that will be called first. You can give a different name to this function; it should not necessarily be called main.

Variables

Variables in Hack are prefixed with the dollar sign. There are special variables: $_ for loops and $this for working with objects.

When a variable appears inside a double-quoted string, its value is substituted. Compare the following two printing instructions:

$name = 'John';         # name
echo "Hello, $name!\n"; // Hello, John
echo 'Hello, $name!\n'; /* No substitution */

The first echo call prints the correct greeting with a name, while the second one prints a bare text string with a dollar in it; even the \n is not substituted and is printed as is. Notice the comments: it can be in either bash or C style.

To create a constant, use the const keyword:

const height = 190;
echo height;

Aggregate data types

Arrays, dictionaries, and sets, you all create them with one of the array creation operators: vec[], dict[], or keyset[]. Here are a few examples:

$array = vec[1, 2, 3];
$array[0] = 42;
echo $array[0], "\n";

$dict = dict[]; # empty dictionary
$dict["a"] = "alpha"; 
$dict["b"] = "beta";
echo $dict["a"], "\n";

A keyset is an ordered collection of things, all of which are of the same type and are not repeated. Here is a bit more complicated example that prints all the values in a keyset:

$keyset = keyset["red", "green", "blue", "red"];
foreach ($keyset as $x) {
    echo "$x\n"; // second "red" is not there
}

This program prints red, green, and blue. Notice how you organise a foreach loop. First comes the variable over which you iterate, then the loop variable: $keyset as $x. This is opposite to what you may expect thinking of a more traditional convention if you are not familiar with PHP.

Functions

You create a function using the function keyword. The parameters of a function can have an optional type specifier as shown in the next example. The return type is also an optional thing. Here, it is explicitly said the function does not return anything:

function greet(string $name): void {
    echo "Hello, $name!\n";
}

greet('John');
// greet(42); // error

If you uncomment the last line, you’ll get a runtime error, as the function does not expect a number. On the other hand, you cannot overload a function using the same name.

A Factorial example

A recursive function that computes a factorial can be defined like this:

function factorial(int $n): int {
    if ($n < 2) return 1;
    else return $n * factorial($n - 1);
}

echo factorial(5), "\n"; // 120

Lambdas

You can create an anonymous function by either omitting the function name or even the function keyword itself. The following two code snippets do the same:

$f = function(int $x): int {
    return $x * $x;
};

echo $f(2), "\n"; // 4
echo $f(3), "\n"; // 9

The same result is achieved here:

$f = (int $x): int ==> $x * $x;

echo $f(2), "\n"; // 4
echo $f(3), "\n"; // 9

Namespaces

Namespaces in Hack are the nested names, where the parts are separated by backslashes. In the following program, you can see how you access the names from a different namespace.

namespace MyNameSpace\TestCode {
    function hello() {
        \echo("Hello, World!\n");
    }
}

namespace { // global namespace
    <<__EntryPoint>>
    function main(): noreturn {
        MyNameSpace\TestCode\hello();
        \exit(0);
    }
}

In the Hack programs and documentation, you may see function calls prefixed with a single backslash. This is nothing more than a reference to the global namespace.

Classes

If you are familiar with traditional object-oriented programming, you will have no problems with understanding the code in Hack. Writing it, though, may be a bit less smooth at first.

Classes can have both private and public data fields and methods. You define a class with the class keyword, and access its members using the $this pointer together with the member selection operator ->. In the following code, notice, for example, how the $name field is accessed through $this->name, with only a single $ for the whole construct. The class’s constructor is named __construct. Examine the following program to get an idea of how to use simple classes.

class Person {
    public string $name;
    private int $age;

    public function __construct(string $name, int $age) {
        $this->name = $name;
        $this->age = $age;
    }

    public function inc_age() {
        $this->age++;
    }

    public function info() {
        return "I am $this->name, and I am $this->age years old.\n";
    }

    public function __toString() {
        return $this->info();
    }
}

<<__EntryPoint>>
function main() {
    $p = new Person("Alice", 20);
    $p->inc_age();
    echo $p->info();
    echo $p->name, "\n"; // public field
    echo $p; // calls __toString(), and then info()
}

The new keyword instantiates an object of the given class. To automatically convert an object to a string, you can define the __toString() method.

For simplicity, the constructors that only initialise data can be shortened like this:

class Person {
    public function __construct(
        public string $name,
        private int $age
    ) {}

    . . .
}

Static members

Use the static keyword to make a data field or a method static (i. e., belonging to the class, not its instances). To access such method, you need a double colon, as you can see in the following example. This program counts the number of created objects and saves the current number in the object. Thus, there is a common counter $total which have impact on all created objects.

class Counter {
    private static int $total = 0;
    private int $my_number;

    public function __construct() {
        $this->my_number = ++Counter::$total;
    }
   
    public function info() {
        echo "My number is $this->my_number.\n";
    }
 }

 <<__EntryPoint>>
 function main() {
     $a = new Counter();
     $b = new Counter();

     $a->info(); # My number is 1.
     $b->info(); # My number is 2.
     $a->info(); # My number is 1.
 }

Inheritance

Inheritance is possible via extending the classes, or via interfaces or by using traits. Let us program the zoo with a number of polymorphic animals.

A Polymorphic example

Here is our polymorphic zoo. The base class, Animal, is an abstract class and it defines the contents of its both children: Dog and Cat.

abstract class Animal {
    abstract public function __toString();
}

class Dog extends Animal {
    public function __toString() {
        return "Dog\n";
    }
}

class Cat extends Animal {
    public function __toString() {
        return "Cat\n";
    }
}

$zoo = vec[new Dog(), new Cat(), new Cat(), new Dog()];
foreach ($zoo as $x) {
    echo $x;
}

The base class demands the presence of the __toString() converter. This method is also marked abstract in the Animal class. The Cat and Dog classes defined their own behaviour for that method.

Although the $zoo array contains a mixture of different objects, Hack correctly dispatches the calls of __toString() when it meets echo $x.

Asynchronous programming

The Hack language offers some support for asynchronous programming. There is a dedicated section ‘Asynchronous operations’ in the official documentation. Let me reduce the discussion to a Sleep sort implementation. To run it, you need to configure your setup to include the corresponding libraries, but it is easier to run the code online (add the <?hh instruction at the top).

The Sleep Sort example

The general idea of the Sleep Sort implementation remains the same. You traverse the data array and create an asynchronous task that sleeps some time proportional to the given number and then prints it.

async function sort_me($n) {
    await SleepWaitHandle::create($n * 100000);
    echo "$n\n";
}

async function main() {
    $data = [10, 4, 2, 6, 2, 7, 1, 3];
    $jobs = Vector {};
    foreach ($data as $n) {
        $jobs[] = sort_me($n);
    }

    await GenArrayWaitHandle::create($jobs);
}

main()->join();

The task is implemented in the sort_me async function. It returns immediately after getting the job. To wait for all jobs to finish, we collect them in the $jobs vector.

Notice how you can add a new element to the end of the vector: $jobs[] = sort_me($n). This does the same work as $jobs->add(sort_me($n)). Each element in the $jobs vector is an object of the HH\AsyncFunctionWaitHandle type. When all the jobs are sent, we can wait for them to complete and join the main thread.

XHP

Behind this abbreviation, XHP, stands an extremely interesting mechanism that allows you to embed XML or proper HTML code directly to your Hack files. Here is an example:

$output = <title>Page title</title>;

Again, the above line is a valid Hack code. It further extends to the possibility to interpolate variables, for example:

function greeting($name) {
    echo <h1>Hello, {$name}!</h1>;
}

greeting('John');

If you want to try this feature, you have to deal with a painful installation process of getting the xhp-lib library work.

And let me stop here for today. I hope you got a few ideas of where Hack can be used at.

Get More

Learn more about the Hack programming language on:

On GitHub, you can find the source codes for this article about Hack.

Next: Day 10. Lua

One thought on “Hack at a Glance — A Language a Day, Advent Calendar 2019 Day 9/24”

Leave a Reply

Your email address will not be published. Required fields are marked *