[Andrea, please forward this to internals if you don't see my message appear 
there in a few hours; as you know, FB is working through issues sending email 
to internals, and though I think I know how to get it through, if it doesn't 
make it, please help :) Thanks!]

On Aug 4, 2014, at 12:49 PM, Andrea Faulds <a...@ajf.me> wrote:

> IMO, it’d also be good for Hack as it could use this instead of fun().

Yep! I don't think there's a single engineer or user of Hack that doesn't think 
that Hack's current solution to this is pretty awful :-P (For those unfamiliar, 
see http://docs.hhvm.com/manual/en/book.hackmagic.php, in particular 
fun/class_meth/inst_meth/meth_caller.)

So I love the idea of making a Hack syntax for this that doesn't suck and a PHP 
syntax at all. However, I think the current proposal misses an opportunity to 
fix an important issue with "strings as first-class functions" as it currently 
stands in PHP, which is how those strings deal with scope. The short version is 
that scope, e.g., for calling private methods, is going to be checked when you 
invoke the method via call_user_func/cufa, which is probably not what the 
programmer would expect -- you usually want a check when you *create* the 
string/array/whatever that you will eventually pass into cuf/cufa. It gets a 
ton more hairy than that -- 
https://github.com/facebook/hhvm/blob/master/hphp/test/quick/cuf.php is an HHVM 
test case that is a litany of all of the weird ways you can use cuf and how it 
applies to scope.

The RFC goes a long way to fixing this, but one important place it misses is 
with function references to private and protected methods. The crux of the 
issue is that allowing an unbound closure to escape can lead to very unexpected 
and unwanted results, as opposed to forcing the closure to be bound to a 
particular variable. Consider:

<?php
class C {
  private function priv() { /* ... */ }
  public function pub() {
    // ... do stuff ...
    // Return a closure for the caller to call priv at a later date:
    return &self::priv;
  }
}

While this example is somewhat contrived, I've certainly wanted to do something 
similar before. The intent of this code is for the closure returned by "pub" to 
only ever be called on what was $this inside "pub" -- but not only can you 
change that with bind() on the closure, "pub" itself cannot even pre-bind it to 
the intended $this. Or maybe it technically could by calling bind() on the 
result of &self::priv before returning it, but that's very cumbersome for what 
is the common case for what you want to do with "function pointer" to a 
non-static method.

FWIW the current kludge Hack does with its magic functions don't quite get this 
right either; inst_meth binds to a specific method and in principle should be 
able to call private/protected methods though I think it can't right now due to 
being on top of strings; meth_caller doesn't bind to a specific method and 
can't call private/protected methods, as it should be. However, importantly, 
the Hack typechecker *does* constrain a lot of this messiness, and so the 
underlying implementation isn't nearly as important for Hack right now. But 
straightening this out in the runtime would be a big win of having real first 
class function references, and I think your RFC is missing out on making the 
semantics here a whole lot more in line with what programmers are going to 
expect.

Orthogonal to the above issue, something else you may want to consider, since 
you're revisiting first class functions, is using Closure for this might not be 
what you want. (This might be partially what Stas was getting at as well?) If 
you're thinking about these as real first-class functions, the fact that they 
are objects, that you can add dynamic properties or re-bind() them is kind of 
weird. You can't do any of those things to the "real" function, so it's unclear 
if it makes sense to be able to do it to the callable reference. (That said, 
differing here gets into a whole lot of other issues around potentially being 
confusing when differing from the existing way of representing lambdas via 
Closure.)

Josh Watzman


--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to