Maybe in PHP
Doing functional programming in a language as PHP, which is almost completely used as an imperative or object oriented way, is not always easy. Good progresses have been made thanks to the introduction of callable type hints in PHP 5.4 and the diffusion of functional interfaces like the ones present in PSR-7.
Still, all “good” PHP code is still written using objects and classes and the object oriented perspective on the world strongly influences even the most functional oriented libraries.
In this post I would like to propose as an example how we could implement the Maybe
type
in PHP. We will see how some open source libraries do this, we will see an alternative solution
and we will raise concerns about some modelling issues.
SOMETIMES WE HAVE A VALUE, SOMETIMES WE DON’T
Sometimes in our application we need to treat values that may or may not exist at that
precise instant. You could think about an optional parameter, or about a value that needs
to be retrieved remotely and we don’t know if it has arrived yet. Usually in PHP we
instantiate such a variable with a default value, generally null
, which is used to
identity the case in which the value is not there yet. This approach is problematic,
because, before doing anything with the value, we need to check if it is not null
, and,
if it is, handle the null
case separately.
In strongly type languages like Haskell or Elm,
the possibility of the non-existence of a value is approached differently. It is
addressed at the type level with the Maybe
type, which is defined as follows:
Maybe a = Just a | Nothing
This means that a variable of type Maybe a
could be either of type Just a
, which is
just a wrapper for a value of type a
, or of type Nothing
, which means that no value
is there.
Now, if you have a variable foo
of type Maybe a
, you could use it in the following way
case foo of
Just value -> // here you have access to the value
Nothing -> // handle the case where you don't have a value
There are several things I really like about this approach and I’ll try to list them here briefly:
- when you have a
Maybe a
you are sure it is or aJust a
or aNothing
, there are no other possibilities; - when you have a
Maybe a
you are obliged to consider both cases (the compiler will check that in Haskell and Elm), you can not be lazy and forget to treat theNothing
case; - when you have a
Nothing
you don’t have any operation that allows you to retrieve something of typea
.
MAYBE IN PHP
Maybe
is quite a common structure and there are several libraries in PHP implementing it. I would
like now to analyze the implementation of some of them to check if they comply with the points I
listed above. I would like to point out that I am not in any way suggesting that these libraries
are poorly written; I would just like to point out some limitations which, in my opinion, are
inherently present in an object oriented approach.
Let’s start with monad-php by Anthony Ferrara,
where the author tries to show that the concept of monad could be used also in PHP.
He defines the Maybe
class as follows (I’m stripping away non-relevant code):
class Maybe
{
protected $value;
public function __construct($value) {
$this->value = $value;
}
public function extract() {
return $this->value;
}
...
}
As you see, only one class is defined, and therefore there is no clear distinction between Just
and Nothing
and null
is used as the discriminating factor. Moreover, we can call the extract
method also on Nothing
objects, receiving null
as return value.
Let’s continue with Phunkie by Marcello Duarte,
a library with functional structures for PHP. In there we can find three classes that constitutes
an implementation of Maybe
:
abstract class Option
{
abstract public function get();
...
}
final class Some extends Option
{
private $t;
public function get() {
return $this->t;
}
...
}
final class None extends Option
{
public function get() {
throw new \RuntimeException("Illegal get() call on None");
}
...
}
Here it is not possible to instantiate directly an Option and there is a clear distinction between
Just
(here called Some
) and Nothing
(here None
). Still, there is no way to ensure that Option
is either Some
or None
, since a new class could be defined to extend Option
. Moreover, since None
extends Option
, it must define a method get
which should not be there; to denote that the method
should not be used, an exception is thrown introducing side effects in a generically pure codebase.
UPDATE 21/06/2017: Phunkie was updated to solve the issues I mention here; now it does not define get
on
Option
and therefore it doesn’t need to define it throwing an exception on None
. Moreover, using a
final
constructor on Option
, it ensures that an Option
could be only Just
or None
. The only small
pitfall of this is that it ensures it throwing an exception.
These two libraries we just saw are not limited to define a Maybe
type and address functional programming
in PHP more broadly. Let’s have a look now at two libraries which are focused on the definition of Maybe
in PHP.
Let’s start with php-maybe-monad by
Marcelo Camargo, where we could find the following implementation
of Maybe
:
interface MaybeInterface
{
public function fromJust();
...
}
class Just implements MaybeInterface
{
private $value;
public function fromJust() {
return $this->value;
}
...
}
class Nothing implements MaybeInterface
{
public function fromJust() {
throw new \RuntimeException('Cannot call fromJust() on Nothing');
}
...
}
As in the Phunkie
implementation, inheritance is used to describe the fact that Just
and Nothing
are subtypes of Maybe
and an exception is used to prevent the client to use the fromJust
method. The
main difference is given by the fact that Just
and Nothing
are not final
here and therefore
subclasses of them could be defined.
Let’s conclude our tour with php-fp-maybe by
Tom Harding, where we can find the
following definition of Maybe
:
abstract class Maybe
{
abstract public function fork($default);
...
}
final class Just extends Maybe
{
private $value = null;
public function fork($_) {
return $this->value;
}
...
}
final class Nothing extends Maybe
{
public function fork($default) {
return $default;
}
}
Here the approach is similar to the previous two libraries that we saw (especially to Phunkie
), with
an abstract class Maybe
extended by two final classes Just
and Nothing
. The main difference is that
the author decided not to expose a method get()
to extract the value from Just
because, as we saw
earlier, this would have caused him to implementing it also on Nothing
where the only solution then is
throwing an exception. Instead a method fork($default)
is defined, which requires a default value that
is used in the Nothing
case; this saves us from using the exception, but still it forces us to have a
useless $default
value even in the Just
implementation of fork
.
Rounding up, I think that all these libraries which we analyzed have some little design flaw, because in one
way or the other, they allow you to do more than what you should be supposed to do (for example, calling get
on Nothing
) or they just force you to do something you should not be supposed to do (requiring a $default
value for the Just
case). As I said before, I think that these flaws are inherently caused by an object
oriented approach; maybe I’ll dive deeper on this idea in a future post.
I concluded the introduction saying that we would also look to a possible alternative to solve some of the issues I raised until this point. Let’s have a look!
ANOTHER MAYBE IN PHP
I will first provide my proposal and then discuss it a bit with regarding to the issues I raised above.
interface Just
{
public function get();
}
interface Nothing {}
class Maybe
{
public static function nothing()
{
return new class() extends Maybe implements Nothing {};
}
public static function just($value)
{
return new class($value) extends Maybe implements Just
{
private $value;
public function __construct($value)
{
$this->value = $value;
}
public function get()
{
return $this->value;
}
};
}
}
You can notice that only Just
has the get
method to access its wrapped value. Nothing
instead has no methods
and the only thing we could make of it is using it as type hinting (which is exactly the
only thing we would like to be possible). This saves us from using null
as discriminating
value, throwing exceptions to notify the usage of a method which should not be called, or
requiring the usage of a default value in the signature of the method.
Still, we are not preventing the fact that a Maybe
could be defined that is not Just
or Nothing
. This is
caused by the fact that we can not make Maybe
as final
since we want the objects returned by Maybe::just
and Maybe::nothing
to type hint against Maybe
.
I would be really curious if it would be possible to overcome these limitations and obtain a data structure
that better resembles the functional Maybe
type.
CONCLUSION
We saw several implementations in PHP of a Maybe
data structure to see if we could replicate the following
properties of the Maybe
type:
- when you have a
Maybe a
you are sure it is or aJust a
or aNothing
, there are no other possibilities; - when you have a
Maybe a
you are obliged to consider both cases; - when you have a
Nothing
you don’t have any operation that allows you to retrieve something of typea
.
We saw that in an object oriented world like PHP it is pretty hard (if not impossible) to design a data structure that satisfies these three properties. The main problem to overcome derives directly from the usage of inheritance.
Inheritance naturally guides us or to expose methods where they shouldn’t be and leaves the possibility
to provide other implementations of Maybe
which are not Just
or Nothing
.
Maybe trying to replicate functional programming data structures in an object oriented world is not a very smart thing to do, but it still helps us at identifying the limitations of the mainstream paradigm.