Optional Types

Christopher Pitt
3 min readAug 28, 2014

--

In dynamically-typed languages (and even statically-typed languages which support nullable types) the problem of null references is a big one. Null references happen when you think you’re working with an object, but the variable you reference actually contains null, and so method calls generate an error.

To illustrate this problem, let’s imagine what happens when the following code is run:

while(true) {
if ($deferredProcess->complete()) {
print $deferredProcess->result;
break;
}

sleep(1);
}

This loop will run forever. That is until $deferredProcess->complete() returns true. At that point; the result will be printed and the loop will terminate. We add sleep(1) so the machine it’s running on doesn’t melt!

What if $deferredProcess isn’t the object we expect? What if it’s another object, which doesn’t have a complete() method? What if it’s null? In these circumstances, the script will terminate with a fatal error.

So, what often tends to happen (with some defensive programming) is that we add multiple checks. If the object is an object, and if the object has a completed() method, and if…

while(true) {
if (!is_object($deferredProcess)) {
break;
}

if (!method_exists($deferredProcess, "complete") {
break;
}

if ($deferredProcess->complete()) {
print $deferredProcess->result;
break;
}

sleep(1);
}

This bloats up the code we write, but it’s one of the few ways a dynamic language can be safely used. Let’s consider another example:

$user = $database
->table("user")
->where("id", "=", $id)
->first();

if (!$user) {
print "Error: User not found";
}

$address = $user->address;

if (!$address) {
print "Error: Address not found";
}

print "City: " . $address->city;

The more we chain potentially-null variables, the more we need to check (or hope) that the variables aren’t null.

Optional Values

One way around this problem is to box variables so that method calls are intercepted and avoided on null values. This looks something like:

$optional = new Optional($user); 
print $optional->address()->city()->value();

This is not a new idea. In fact, a chap named Johannes Schmitt implemented this kind of library and another chap named Igor Wiedler wrote about it. Scala supports this kind of type, natively. It’s a neat bit of glue between defensive programming and dynamic/nullable types.

I felt like implementing a slightly different interface for optional types, and came up with https://github.com/typedphp/php-optional. The examples for this are:

use TypedPHP\Optional\Optional;

class Foo
{
public function hello()
{
return new Bar();
}
}

class Bar
{
public function world()
{
return "hello world";
}
}

$optional = new Optional(new Foo());
$optional->hello()->world()->value(); // "hello world"

Chained calls and value resolution

use TypedPHP\Optional\None;

$none = new None();
$none->hello()->world()->value(); // null

Safe method calls to empty values

use TypedPHP\Optional\None;

$none = new None();
$none
->none(function() { print "none"; });
->value(function($value) { /* never happens */ });

use TypedPHP\Optional\Optional;

$optional = new Optional("hello world");
$optional
->none(function() { /* never happens */ });
->value(function($value) { print $value; });

Callbacks for responding in different situations

If a method call on an Optional type returns and empty value, the Optional type will be replaced with a None type, which makes this a very fluid process.

I have published this tiny library to Packagist, so you can use this in your code, similarly to:

❯ composer require "typedphp/optional:0.*"

./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Installing typedphp/optional (0.1.2)
Loading from cache

Writing lock file
Generating autoload files

I’d love to have some feedback on this simpler interface to optional types. If you have questions about it, or ideas to make it better, please comment here or speak to me on Twitter.

--

--