It’s time for yet another deep dive into our platform. This time we’re going to tell you about when and why HSL got classes with closures. It started when we revisited our MIME implementation. The MIME implementation was kind of basic, but served most people well. However, it could at the time only work on the top level MIME objects header and all MIME parts were simply addresses by IDs. Although the structure of MIME is highly nested with children and siblings and all MIME parts share the same features, there is a header and a body-part (which could be even more MIME parts). This data structure is ideal to be represented as objects in a tree structure; which got us started to sketch how we really wanted you to work with MIME objects.
In order to implement MIME objects properly we felt we had to implement objects deep into the language itself.
Anonymous functions were already added in an earlier release. Now we needed “classes” in order to bundle multiple functions around a local scope (implement closures by reference). The basic concept of closures is that a function object inherits a local scope, which follows the function object. We chose to add a new keyword (closure) which forces you to explicitly specified which variables should be included in the closure. This doesn’t impair the implementation but merely serves as a documentation of intention, which should keep the code less bug prune.
$variable = 0; $function = function () closure ($variable) { $variable += 1; } $function(); echo $variable; // 1
This concept allows you to build a simple MIME object.
function MIME() { $children = []; return [ "getParts" => function () closure ($children) { return $children; }, "appendPart" => function ($part) closure ($children) { $children[] = $part; }, ]; } $part1 = MIME(); $part2 = MIME(); $part1["appendPart"]($part2); echo $part1["getParts"]();
The next thing was the syntax. Calling anonymous functions on arrays were already supported. However the syntax was somewhat confusing and we wanted it to be perceived more like an object than an array with functions. Hence, we added the property (array) operator just like C has the obj->fun
syntax which is a shortcut for (*obj).fun
.
$part1->appendPart($part2); echo $part1->getParts();
This got us a long way, but we felt we were missing an important part, method chaining. In order for chaining to work, the object must return an instance to itself (let’s do that in our appendPart method). Our first though was, “that should be easy, can’t we just reference ourselves in the closure” like this:
function MIME() { $children = []; $self = [ "getParts" => function () closure ($children) { return $children; }, "appendPart" => function ($part) closure ($children, $self) { $children[] = $part; return $self; }, ]; return $self; } $part1 = MIME(); $part2 = MIME(); echo $part1->appendPart($part2)->getParts();
Unfortunately, that turned out causing issues with our reference counting. The object cannot reference itself, because if it does we start leaking memory for each object. To solve that a weak reference had to be added to the object, which required us to add some more functionally to the language. Two features, one to set the reference (define what self is) and one to get the reference (in order to return it). We choose the object keyword to declare an object which has the concept of a self. And the this keyword to get and return the reference to self.
function MIME() { $children = []; return object [ "getParts" => function () closure ($children) { return $children; }, "appendPart" => function ($part) closure ($children, $self) { $children[] = $part; return this; }, ]; } $part1 = MIME(); $part2 = MIME(); echo $part1->appendPart($part2)->getParts();
There you have it. Objects with closures. Since we implemented our built-in objects (such as MIME) using the same concept, you can actually extend them yourself.
function MIME() { $mime = builtin MIME(); $mime["setSubject"] = function ($subject) { this->addHeader("Subject", $subject); return this; }; return object $mime; } $part1 = MIME(); echo $part1->setSubject("Hello")->toString();
For more information see our documentation.