Pages

Wednesday, March 26, 2014

Shapes

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 $aPoint2D $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 

Shapes can be used with generics as shown here:
<?hh
 
type Point
<T> = shape ('x' => T'y' => T);
 
function 
gen_shape_add<T>(Point<T$pt1Point<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