Thursday, February 11, 2010

Hobgoblins: Anonymous Functions in PHP 5.3

Among other things, PHP introduces anonymous functions (which they also call closures) in PHP 5.3.  This is interesting, because normal functions or methods in PHP are not first-class citizens and yet anonymous functions kinda are -- or at least look like they are.  And this is interesting, because I find first-class functions one of the really nice things about languages like Javascript and Python.

So, how does this work exactly in PHP?  Well, let's see if we can deduce how this works from some trial (& error).

The Basics

So, anonymous functions in PHP are most frequently used in callbacks.  As such, that's probably the first example you'll see:

Ah, so that's all well & good, but the interesting thing is that this anonymous function can also be assigned to a variable (and that's good, because it's filling in for what would normally be a variable):

What may come as a surprise (unless you've read the manual page):

Yes, it's a special internal class.  And no, you cannot instantiate a Closure yourself:

... or extend it (it's final).  But wouldn't it be cool if you could, because then you could maybe find out how they've made a callable object.  AFAIK, PHP doesn't support that otherwise, after all.  Now, if they had provided that feature, I think that would have been a lot more generally useful.

Closures and Scope

It is also possible to pass in variables from the current scope in to the anonymous function.  This provides a closure-like mechanism.  I say "closure-like" because unlike other languages (e.g. Javascript, Python, Ruby) PHP is not actually providing access to the parent scope; rather, it is injecting variables into the anonymous function's scope.  While that is different, that probably makes "sense" for PHP, since PHP provide no way to access parent scopes in any other contexts.

For (a very contrived) example:

But, for people who have experience closure elsewhere, this next example might come as a surprise:

So, the function did not change $value.  But the syntax does look like we're passing in the $value, and that seems to be the right way to think about it:

Compare with Javascript:

The Fine Print

So the actual behavior seems fairly consistent so far.  There certainly are a few types of variables that can't be imported.  For example, and despite some suggestion otherwise, it seems that you cannot reference $this from within an anonymous function defined within a class method:

And, no, you cannot pass it in to the closure either:

We're getting some interesting errors, though, no?

If you're thinking, ah, but what if the closure itself is defined as a member variable.  Ok, I think you're getting abusive, but let's give that a shot:

So, maybe there's a workaround for this, but suffice it to say that it doesn't work the way I would expect it to work.

Another Half-Baked Solution

One of my biggest gripes with PHP is how it adds half-baked features to the language.  This is true of Exceptions, the OO model, interfaces & type hinting in signatures, namespaces, etc.  Well, anonymous functions seem to be simply another example in this litany of offenses to computer science.  Now, the concept of first-class functions is a powerful one and leads to some extremely useful design patterns.  But PHP didn't actually make functions first-class, they just made a new type of "function" that is first-class (by virtue of being an instance of Closure class).

Anonymous functions are suppose to be functions.  So why are they fundamentally different from non-anonymous functions?

To me, this next block of code is just confusing:

To be fair, this anonymous function feature is butting up against the basic design of PHP.  PHP has special syntax for declaring variables (namely, a $ prefix), which makes the new anonymous functions syntactically incompatible with traditional functions in PHP.  In the case of object instance variables, the syntax is ambiguous but one finds out very quickly that PHP is going to err on the side of tradition when it comes to interpreting method invocations:

While syntax may just be a function of the completeness of the parser, to the programmer this is what gives a language a feeling of coherence or consistency.  We expect computer programming languages to be predictable and logical; for example, when values are equivalent, we expect to be able to substitute one value for another consistently.

Look at how consistently Javascript behaves:

Ok, PHP, your turn.