Web Development With HHVM and Hack 10: Shapes

Mike Abelar
3 min readMar 18, 2020

--

In the last tutorial, we covered tuples: https://medium.com/@mikeabelar/web-development-with-hhvm-and-hack-9-tuples-d2f14126a2eb. In this tutorial, we will cover shapes

Shapes vs. Tuples

Let us continue with the example in the last tutorial in which we wanted to represent a user with a tuple that contains the name and the age of the user. Say that we have many tuples representing users and we want to add a field for zip code. Therefore, we want our tuples to go from storing name and age to storing name, age, and zip code. Unfortunately, tuples do not support adding or removing fields. Another data structure, shapes, does support this.

What Are Shapes?

Shapes are similar to tuples in that they are a compact way to represent data of different types. The key differences are that shapes support adding fields and use key/value pairs similar to dictionaries.

To explore shapes, make a directory for this tutorial and inside the folder type:

touch .hhconfig

Then, create a file to explore shapes: shapes.hack . Inside the file, paste the following contents:

<<__EntryPoint>>
function main(): noreturn {
$user_one = shape('name' => "Joe", 'age' => 25);
print("name: " . $user_one['name'] . "\n");
print("age " . $user_one['age'] . "\n");
exit(0);
}

We can run the code with hhvm shapes.hack . This outputs:

name: Joe
age 25

Observe the differences between this code and the nearly identical code we used with tuples. The first notable difference is the keyword shape rather than tuple . Our next observation is that the fields of the shape are named. Instead of just using “Joe” for the tuple, we specify the name of the user using 'name' => "Joe" . This means that the key is name and the value is "Joe" . We then separate the key-value pair with a comma before moving to the next key-value pair, age .

To access and then print the various fields in the shape, we access the fields similarly to how we accessed the values in a dictionary. So to get the name of the user_one shape, we use $user_one['name'] .

Note: All keys in shapes must be strings.

Adding and Editing Fields

To edit a field, we do the same as editing a dictionary value:

<<__EntryPoint>>
function main(): noreturn {
$user_one = shape('name' => "Joe", 'age' => 25);
print("name: " . $user_one['name'] . "\n");
$user_one['name'] = "Joe 2";
print("name: " . $user_one['name'] . "\n");
exit(0);
}

The code then prints:

name: Joe
name: Joe 2

To add a field, we just specify it using the same notation as above:

<<__EntryPoint>>
function main(): noreturn {
$user_one = shape('name' => "Joe", 'age' => 25);
print("name: " . $user_one['name'] . "\n");
// now we can add in a zip code
$user_one['zip code'] = 11111;
print("zip code: " . $user_one['zip code'] . "\n");
exit(0);
}

The code then prints:

name: Joe
zip code: 11111

Using Shapes In Functions

Like with tuples, we can also use shapes to define the return types of functions. Below is the example from the last tutorial of getting the age of a user given the name:

type User = shape("name" => string, "age" => int);function get_shape_of_user(string $name) : User {
$user_one = shape("name" => "Joe", "age" => 25);
$user_two = shape("name" => "Jack", "age" => 21);
$users = vec[$user_one, $user_two];
foreach ($users as $user) {
if ($user["name"] == $name) {
return $user;
}
}
return shape("name" => "Not found", "age" => -1);
}
<<__EntryPoint>>
function main(): noreturn {
$t = get_shape_of_user("Joe");
print($t["age"]);
exit(0);
}

The code prints 25 .

To define a shape that will be used frequently, in this case User , we follow the example:

type User = shape("name" => string, "age" => int);

We start out with the type keyword, which is different than the newtype keyword we used in tuples. Then, when defining the shape, we specify the keys, and for the values, we specify the types of the values. In this case, name is a string and age is an int.

Optional Fields

One neat feature when defining types using shapes is that the fields can be optional. A field is optional when it may or may not have a value. For instance, let’s follow the example above and say that for each shape, we may or may not have the age of the user. We can represent this as a type as follows:

type User = shape("name" => string, "age" => ?int);

Here, we say that the age field will be an int or not be present. We will cover optional values explicitly in a future tutorial.

In the next tutorial, we cover enums: https://medium.com/@mikeabelar/web-development-with-hhvm-and-hack-11-enums-2653782bf5d9

--

--