Web Development With HHVM and Hack 10: Shapes
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