Palantir.net
Presented by Larry Garfield (@Crell)
implements Huggable
Humans got smarter by making tools
do our work for us.
Your job is...
To write code
To write correct (error-free) code
Make your tools find the errors for you
I can tell it's wrong just by looking at it
My tools can tell it's wrong just by looking at it
function showForm($user) {
require_once('form_tools.php');
if ($user->isAdmin()) {
$isAdmin = true;
}
$out = "<form>\n<input name='name'>";
if ($isAdmin) {
$out .= "<select name='role'>\n";
$out .= "<option>Admin</option>\n";
$out .= "<option>Peon</option>\n";
$out .= "</option>";
}
$out .= "</form>";
return $out;
}
PHP Notice: Undefined variable: isAdmin in samples.php on line 15
function showForm($user) {
require_once('form_tools.php');
$isAdmin = (bool)($user->isAdmin());
$out = "<form>\n<input name='name'>";
if ($isAdmin) {
$out .= "<select name='role'>";
$out .= "<option>Admin</option>\n";
$out .= "<option>Peon</option>\n";
$out .= "</option>";
}
$out .= "</form>";
return $out;
}
Approximate your code
function sum_square($a, $b) {
return ($a * $a) + ($b * $b);
}
Number {
POSITIVE,
NEGATIVE,
ZERO,
UNKNOWN
}
POSITIVE * POSITIVE = POSITIVE; POSITIVE + POSITIVE = POSITIVE;
POSITIVE * NEGATIVE = NEGATIVE; NEGATIVE + NEGATIVE = NEGATIVE;
NEGATIVE * NEGATIVE = POSITIVE; POSITIVE + NEGATIVE = UNKNOWN;
POSITIVE * ZERO = ZERO; POSITIVE + ZERO = POSITIVE;
NEGATIVE * ZERO = ZERO; NEGATIVE + ZERO = NEGATIVE;
UNKNOWN * ZERO = ZERO; POSITIVE + UNKNOWN = UNKNOWN;
UNKNOWN * POSITIVE = UNKNOWN; NEGATIVE + UNKNOWN = UNKNOWN;
UNKNOWN * NEGATIVE = UNKNOWN; UNKNOWN + ZERO = UNKNOWN;
The result will always be POSITIVE or ZERO, never NEGATIVE.
function bigger($x, $y) {
return ($x > $y) ? $x : 0;
}
function compute($a, $b, $c) {
return ($a + $b) / ($b * $c);
}
compute($a, $b, bigger($x, $y));
What do we know?
function bigger($x, $y) {
return ($x > $y) ? "the first" : "the second";
}
function compute($a, $b, $c) {
return ($a + $b) / ($b * $c);
}
compute($a, $b, bigger($x, $y));
What do we know?
function bigger(number $x, number $y) {
return ($x > $y) ? $x : 'the second';
}
function compute(number $a, number $b, number $c) returns a number {
return ($a + $b) / ($b * $c);
}
May or may not work
function bigger(number $x, number $y) returns a string {
return ($x > $y) ? "the first" : "the second";
}
function compute(number $a, number $b, number $c) returns a number {
return ($a + $b) / ($b * $c);
}
Obviously wrong!
function bigger(number $x, number $y) returns a number {
return ($x > $y) ? 1 : 2;
}
function compute(number $a, number $b, number $c) returns a number {
return ($a + $b) / ($b * $c);
}
Cannot fail!
Tools can tell you that,
if you help them
A type system is a tractable syntactic method for proving the absence of certain program behaviors by classifying phrases according to the kinds of values they compute.
—Benjamin Pierce, Types and Programming Languages
Semantic metadata about code we can analyze to find bugs
function compute_shipping($src, $user) {
$dest = $user['addres'];
$distance = distance_between($src, $dest);
return $distance * .5; // 50 cents/km
}
function distance_between($src, $dest) {
// ...
}
compute_shipping('2211 N. Elston Ave, Chicago, IL 60614', $user);
HTTP Referer header, anyone?
function compute_shipping($src, $user) {
$dest = $user['address'];
$distance = distance_between($src, $dest);
return $distance * .5; // 50 cents/km
}
compute_shipping('2211 N. Elston Ave, Chicago, IL 60614', $user);
function compute_shipping($src, $user) {
$dest = $user['address'];
$distance = distance_between($src, $dest);
return $distance * .5; // 50 cents/km
}
$user = 123;
compute_shipping('2211 N. Elston Ave, Chicago, IL 60614', $user);
function compute_shipping($src, array $user) {
$dest = $user['address'];
$distance = distance_between($src, $dest);
return $distance * .5; // 50 cents/km
}
$user = 123;
compute_shipping('2211 N. Elston Ave, Chicago, IL 60614', $user);
function compute_shipping($src, array $user) {
$dest = $user['address'];
$distance = distance_between($src, $dest);
return $distance * .5; // 50 cents/km
}
$user = ['id' => 123];
compute_shipping('2211 N. Elston Ave, Chicago, IL 60614', $user);
class User {
public $id;
public $address;
public function __construct($id, $address) {
$this->id = $id;
$this->address = $address;
}
}
function compute_shipping($src, User $user) {
$dest = $user->address;
$distance = distance_between($src, $dest);
return $distance * .5; // 50 cents/km
}
$user = new User(/*...*/);
compute_shipping('2211 N. Elston Ave, Chicago, IL 60614', $user);
function compute_shipping($src, User $user) {
$dest = $user->address;
$distance = distance_between($src, $dest);
return $distance * .5; // 50 cents/km
}
$user = new User(123, [
'street' => '456 Main',
'city' => 'Chicago',
'state' => 'IL',
'zip' => 60614,
]);
compute_shipping('2211 N. Elston Ave, Chicago, IL 60614', $user);
Does this still work? Up to distance_between()
class User {
public $id;
public $address;
public function __construct($id, Address $address) {
$this->id = $id;
$this->address = $address;
}
}
class Address {
public $street;
public $city;
public $state;
public $zip;
}
function distance_between(Address $src, Address $dest) { /* ... */ }
function compute_shipping($src, User $user) {
$dest = $user->address;
$distance = distance_between($src, $dest);
return $distance * .5; // 50 cents/km
}
$u_address = new Address();
$u_address->street = '456 Main St.';
$u_address->city = 'Chicago';
$u_address->state = 'IL';
$u_address->zip = 60614;
$user = new User(123, $u_address);
$src = new Address(); // ...
compute_shipping($src, $user);
class User {
protected $id;
protected $address;
public function __construct($id, Address $address) {
$this->id = $id;
$this->address = $address;
}
public function getAddress() : Address {
return $this->address;
}
}
function compute_shipping($src, User $user) {
$dest = $user->getAddress();
$distance = distance_between($src, $dest);
return $distance * .5; // 50 cents/km
}
$u_address = new Address(); // ...
$user = new User(123, $u_address);
$src = new Address(); // ...
compute_shipping($src, $user);
<aside>
$r = new UserRepository();
print $r->findById(123)->getAddress()->getStreet() . PHP_EOL;
Method called on non-object
No NULL means:
class UserRepository {
// ...
public function findById($id) : User {
if (/* can't find the user */) {
throw new InvalidArgumentException('No such User: ' . $id);
}
return $user;
}
}
interface AddressInterface {
public function getStreet();
public function getCity();
public function getState();
public function getZip();
}
class EmptyAddress implements AddressInterface {
public function getStreet() { return ''; }
public function getCity() { return ''; }
public function getState() { return ''; }
public function getZip() { return ''; }
}
class Address implements AddressInterface {
// ...
}
Returning false on error is a
big 'screw you' to your users
</aside>
class User {
protected $id;
protected $address;
public function __construct($id, AddressInterface $address) {
$this->id = $id;
$this->address = $address;
}
public function getAddress() : AddressInterface {
return $this->address;
}
}
function compute_shipping(AddressInterface $src, User $user) {
$dest = $user->getAddress();
$distance = distance_between($src, $dest);
return $distance * .5; // 50 cents/km
}
function distance_between(AddressInterface $src, AddressInterface $dest) {
// ...
}
$u_address = new Address(); // ...
$user = new User(123, $u_address);
$src = new Address(); // ...
compute_shipping($src, $user);
function compute_shipping(AddressInterface $src, AddressInterface $dest) {
$distance = distance_between($src, $dest);
return $distance * .5; // 50 cents/km
}
function distance_between(AddressInterface $src, AddressInterface $dest) {
// ...
}
$u_address = new Address(); // ...
$user = new User(123, $u_address);
$src = new Address(); // ...
compute_shipping($src, $user->getAddress());
What does distance_between() return?
function compute_shipping(AddressInterface $src, AddressInterface $dest) : float {
$distance = distance_between($src, $dest);
return $distance * .5; // 50 cents/km
}
function distance_between(AddressInterface $src, AddressInterface $dest) : int {
// ...
}
$u_address = new Address(); // ...
$user = new User(123, $u_address);
$src = new Address(); // ...
compute_shipping($src, $user->getAddress());
Everyone here owes
Andrea Faulds and Anthony Ferrara a drink
interface AddressInterface {
public function getStreet();
public function getCity();
public function getState();
public function getZip();
}
Is getStreet() a string or object?
interface AddressInterface {
public function getStreet() : string;
public function getCity() : string;
public function getState() : string;
public function getZip() : string;
}
class Address implements AddressInterface {
public function __construct(string $street, string $city, string $state, string $zip) {
// ...
}
// ...
}
$u_address = new Address('45 Hull St', 'Boston', 'MA', 02113);
$user = new User(123, $u_address);
$src = new Address('2211 N. Elston Ave', 'Chicago', 'IL', 60614);
compute_shipping($src, $user->getAddress());
Weak by default
* string -> int may E_NOTICE
Address.php
declare(strict_types=1);
class Address implements AddressInterface {
public function __construct(string $street, string $city, string $state, string $zip) {
// ...
}
public function getZip() : string {
return $this->zip;
}
// ...
}
Affects calls and returns from this file
setup.php
declare(strict_types=1);
$u_address = new Address('45 Hull St', 'Boston', 'MA', 02113);
$user = new User(123, $u_address);
$src = new Address('2211 N. Elston Ave', 'Chicago', 'IL', 60614);
compute_shipping($src, $user->getAddress());
Affects calls and returns from this file
* int converts to float automatically
90% of the time
(Everywhere except Input)
Also, Documentation
Types find bugs
Types find many bugs
Type checkers have false positives
class ThingOne {
public function helpCat() { /* ... */ }
}
class ThingTwo {
public function helpCat() { /* ... */ }
}
class CatInTheHat {
public function welcome(ThingOne $thing) {
$thing->helpCat();
}
}
Type checking will reject valid programs!
Program testing can be used to show the presence of bugs, but never to show their absence.
— Edsger W. Dijkstra
Testing will accept invalid programs!
Testing: establishes upper bounds on correctness
Proof: establishes lower bounds on correctness
Typing is a form of automated proof
Type first, then test
We've outsourced testing to language syntax!
Senior Architect, Palantir.net
Let's Make Something Good Together.
Keep tabs on our work at @Palantir