Shapes ¶
Table of Contents ¶
- Simple Shape Example
- Shapes Across Files
- Shapes and Generics
- Summary
Does this style of PHP code look familiar?
<?hh
function why_shapes(array $arr_as_struct): array {
if ($arr_as_struct['id'] === '573065673A34Z') {
$arr_as_struct['count']++;
}
else {
$arr_as_struct['url'] = "http://google.com";
$arr_as_struct['count']--;
}
return $arr_as_struct;
}
function main_why_shapes() {
$my_struct = array('id' => null, 'url' => null, 'count' => 0);
$my_struct['id'] = '573065673A34Y';
$my_struct['url'] = 'http://facebook.com';
var_dump($my_struct);
$my_struct = why_shapes($my_struct);
var_dump($my_struct);
}
main_why_shapes();
The above example will output:
array(3) { ["id"]=> string(13) "573065673A34Y" ["url"]=> string(19) "http://facebook.com" ["count"]=> int(0) } array(3) { ["id"]=> string(13) "573065673A34Y" ["url"]=> string(17) "http://google.com" ["count"]=> int(-1) }
Since PHP does not have the concept of a structs or records, arrays are many times used to mimic a struct or record-like entity . Arrays are also used as "argument bags" to hold a bunch of arguments that will be passed to a function or method. Shapes were created to bring some structure (no pun intended) and type-checking sanity to this use case.
Note:Classes with only public fields can also be used for the same representation.
Shapes have the following general syntax:
<?hh
type MyShape = shape('id1' => <type>, 'id2' => <type>);
function foo(MyShape $x): void {}
or
<?hh
newtype MyShape = shape('id1' => <type>, 'id2' => <type>);
function foo(MyShape $x): void {}
Note:Notice how shapes are declared with the HHVM keyword type or newtype. This means shapes follow the same behavior as a type alias or opaque type (e.g. underlying behavior can be accessed and changed across files for a type alias).
Simple Shape Example ¶
This example creates a shape that represents a point in the two-dimensional plane.
<?hh
type Point2D = shape('x' => int, 'y' => int);
function dotProduct(Point2D $a, Point2D $b): int {
var_dump($a);
var_dump($b);
return $a['x'] * $b['x'] + $a['y'] * $b['y'];
}
function main_sse(): void {
echo dotProduct(shape('x' => 3, 'y' => 3), shape('x' => 4, 'y' => 4));
}
main_sse();
The above example will output:
24
Notice how the "keys" of the shape are indicies to Point2D (e.g., 'x' and 'y').
Shapes Across Files ¶
Building on the example at the introduction of shapes, instead of using an array as a struct-like entity, a shape will be used instead. Also, this example spans across files.
shapesAcrossFiles1.php
<?hh
type RandomData = shape('id' => string, 'url' => string, 'count' => int);
function foo_saf(RandomData $rd): RandomData {
if ($rd['id'] === '573065673A34Z') {
$rd['count']++;
}
else {
$rd['url'] = "http://google.com";
$rd['count']--;
}
return $rd;
}
shapesAcrossFiles2.php
<?hh
function setup_saf() {
$rd = shape('id' => null, 'url' => null, 'count' => 0);
$rd['id'] = '573065673A34Y';
$rd['url'] = 'http://facebook.com';
var_dump($rd);
var_dump(foo_saf($rd));
// This should cause a Hack error
// But this will still run in HHVM
$rd['count'] = 'I should error';
var_dump(foo_saf($rd));
}
shapesAcrossFiles3.php
<?hh
require_once 'shapesAcrossFiles1.php';
require_once 'shapesAcrossFiles2.php';
function main_saf() {
setup_saf();
}
main_saf();
Running main_saf() should produce a Hack error in setup_saf() since $rd['count'] was set to a string and the shape RandomData expects count to be an int when foo() is called.
The above example will output:
shapesAcrossFiles2.php:14:20,22: Invalid argument shapesAcrossFiles1.php:3:69,71: This is an int shapesAcrossFiles2.php:13:18,33: It is incompatible with a string
However, HHVM will run this code correctly, most likely with unexpected results.
The above example will output:
array(3) { ["id"]=> string(13) "573065673A34Y" ["url"]=> string(19) "http://facebook.com" ["count"]=> int(0) } array(3) { ["id"]=> string(13) "573065673A34Y" ["url"]=> string(17) "http://google.com" ["count"]=> int(-1) } array(3) { ["id"]=> string(13) "573065673A34Y" ["url"]=> string(17) "http://google.com" ["count"]=> string(14) "I should error" }
Shapes and Generics ¶
<?hh
type Point<T> = shape ('x' => T, 'y' => T);
function gen_shape_add<T>(Point<T> $pt1, Point<T> $pt2): Point<T> {
$sumx = $pt1['x'] + $pt2['x'];
$sumy = $pt1['y'] + $pt2['y'];
return shape ('x' => $sumx, 'y' => $sumy);
}
function main_gs() {
// Float based shape
$pt1 = shape('x' => 1.0, 'y' => 2.0);
$pt2 = shape('x' => 3.0, 'y' => 4.0);
var_dump(gen_shape_add($pt1, $pt2));
// Int based shape
$pt3 = shape('x' => 1, 'y' => 2);
$pt4 = shape('x' => 3, 'y' => 4);
var_dump(gen_shape_add($pt3, $pt4));
}
main_gs();
The above example will output:
array(2) { [0]=> float(4) [1]=> float(6) } array(2) { [0]=> int(4) [1]=> int(6) }
Summary ¶
Shapes are useful to create type-checkable, struct-like entities so that plain arrays are not used for this purpose. PHP arrays are already overloaded enough. Shapes have a syntax that provides similar semantics as a type alias or opaque type, depending on whether type or newtype is used to declare the shape.
No comments:
Post a Comment