I've been catching up with the discussion of grafts and traits. Here
at P'unk Avenue we do a lot of Symfony development, which frequently
involves kludges to achieve horizontal reuse - ugly, clever,
imaginative, combinations of the three but the first of the three is
pretty much always present. PHP needs "real" horizontal reuse, no
question.
My first reaction to traits was positive - it's simple and simple is good.
However I'm very concerned about the implications of not allowing state.
In the real world people are going to want to do horizontal reuse that
involves state. So they will work around it.
It's true that if they are extending a class they have control over,
they can do it fairly elegantly...
In the parent class:
public $traitData = null;
public function __construct()
{
$traitData = array();
}
Now, in a trait method, they can do:
public function mymethod()
{
$this->traitData['age'] = 38;
}
The PHP 5.3 cycle breaker can presumably clean up any references made
in traitData when all noncyclical references to the object itself are
gone.
However this is not without problems. There will be collisions between
key names in traitData when mixing traits, and there will be no way to
detect those conflicts and automatically resolve them. It's presumably
less efficient than an implementation of state in the core. And most
importantly, it only works if the parent class can be modified by the
programmer trying to extend it. One of the stated goals of horizontal
reuse is to accommodate adding multiple non-mutually-exclusive
extensions, written by you and not written by you, to parent classes
you didn't write (of course, without the "written by you and not
written by you... multiple non-mutually-exclusive" part, you could
just use a subclass).
So what happens when the programmer wants to write a trait to extend
classes they didn't write, and they need to save state?
Well, their first workaround attempt might be this:
$foo->bar = "I'm an object!";
$hash[$foo]['age'] = 5;
echo($hash[$foo]['age']);
That is, use the object as a key in a hash.
Fortunately, PHP doesn't allow objects as keys in hashes. That's good,
because this otherwise clever technique would leave stray references
in the hash which would never get cleaned up, ever. Cycle breaking or
no cycle breaking. We're right back in "why can't I manipulate a lot
of objects in a 'for' loop without hating life" land.
Okay, so what's our hypothetical user's next workaround going to be?
In some cases, they're just stuck. In others, the parent class does
offer some sort of unique identifier. Common examples being Propel and
Doctrine objects, which have getId() methods. So the programmer does
this (simplest possible implementation to show the idea):
public function myTraitMethod()
{
global $hash; // *
$hash[$this->getId()]['age'] = 38;
}
This works. But it leaks memory forever. When the object goes away,
there is no way to break the connection to the data in $hash. Which
might not be as trivial as 'age'. More likely it'll be crammed with
objects with references to objects with... you get the idea.
To clean these references up you'd need a destructor. Except the
traits RFC makes no mention of destructors. So you'd need a parent
class destructor that explicitly invoked trait destructors. Except you
went this road in the first place because you didn't have control over
the parent class to make changes like that. Etc.
Since PHP is moving in the direction of better garbage collection (and
thank heaven for that), it's a bad idea to make a design decision that
will lead developers in the direction of breaking that garbage
collection system.
The natural thought at this point is to endorse grafts. But the grafts
proposal has more complex implementation requirements and there
doesn't seen to be an existing patch to implement it. The big
stumbling point is that in a naive implementation, $this would refer
to the graft object, not its parent. But the programmer expects to be
able to pass $this around to other code which will expect the parent
object, etc.
My feeling is that traits should permit state and allow the renaming
of member variables just as they allow the renaming of methods. If
that's an implementation problem, it might be better to transparently
move the trait member variables to a hash and interpret references to
those variable names accordingly within the trait methods. The member
variable hash becomes $this->__traitName_0, $this->__traitName_1, etc.
for each consecutive use of the same trait in the same class (if we
really need that feature).
(*) Yes, $hash shouldn't be a global, it should be a static member of
a globalDataManager class or something. Makes no difference for
purposes of this discussion.
--
Tom Boutell
P'unk Avenue
215 755 1330
punkave.com
window.punkave.com
Hi Tom,
PHP needs "real" horizontal reuse, no question.
Looking forward to get this discussion running once again after 5.3 is
released :)
My first reaction to traits was positive - it's simple and simple is
good.
However I'm very concerned about the implications of not allowing
state.
In the real world people are going to want to do horizontal reuse that
involves state. So they will work around it.
I share you're concerns about state.
Even though, I would like to keep Traits as simple as possible.
From my perspective, it would be possible to introduce a full-grained
language concept for traits like proposed in the literature [1]. But,
it is quite complex.
On the other hand, this language constructs would be only used rarely,
and in this rare situations, it would be probably beneficial to have
them at hand. Merging attributes/properties of traits, aliasing and so
on.
For the sake of simplicity, we might discuss a very basic solution.
It would be possible to introduce trait-local state only.
Thus, all properties defined in a trait are only available within
methods of the same trait, no other operations are able to access
these properties.
How ever, there are also corner cases. (Behavior might be different in
case of name clashes for dynamically created properties within a
composition, changing behavior depending on the order of method
invocations on a single object)
My feeling is that traits should permit state and allow the renaming
of member variables just as they allow the renaming of methods.
Just to be clear, there is no such thing like renaming. Renaming is
just not possible with a dynamic language like PHP. ($this->$bar =
'baz'; $this->$foo();... )
Best regards
Stefan
[1] http://www.iam.unibe.ch/~scg/Archive/Papers/Berg07eStatefulTraits.pdf
--
Stefan Marr
Programming Technology Lab
Vrije Universiteit Brussel
Pleinlaan 2 / B-1050 Brussels / Belgium
http://prog.vub.ac.be/~smarr
For the sake of simplicity, we might discuss a very basic solution.
It would be possible to introduce trait-local state only.
Thus, all properties defined in a trait are only available within methods of the same trait,
no other operations are able to access these properties.
Yes, I think this is exactly right. Programmers who want to expose the
state can do that by writing setters and getters. A trivial
inconvenience compared to the pain of not being able to have multiple
enhancements of the same third party class from multiple sources -
something that comes up almost immediately when you start talking
about plug-ins for frameworks.
How ever, there are also corner cases. (Behavior might be different in case
of name clashes for dynamically created properties within a composition,
changing behavior depending on the order of method invocations on a single
object)
I'm not sure I grasp how these can happen in this scenario.
Just to be clear, there is no such thing like renaming. Renaming is just not
possible with a dynamic language like PHP. ($this->$bar = 'baz';
$this->$foo();... )
OK, but with strictly-trait-local state this is not an issue.
A shame this can't be in 5.3 but we will get there. (:
--
Tom Boutell
P'unk Avenue
215 755 1330
punkave.com
window.punkave.com
Hi Tom,
How ever, there are also corner cases. (Behavior might be different
in case
of name clashes for dynamically created properties within a
composition,
changing behavior depending on the order of method invocations on a
single
object)I'm not sure I grasp how these can happen in this scenario.
Well, it actually depends on the strictness of this idea. And its
particular implementation.
With the dynamic nature of PHP in mind, my first idea would be
something like this, which is not ideal:
class Base {
// private $a; <- intentionally left out because of
// some smart approach to use meta-programming
powers...
public function setA($value) {
$this->a = $value;
}
public function echoA() {
echo $this->a;
}
}
trait TraitUsingA {
// var $a; <- intentionally left out because of another smart
approach for using
// really clever meta-programming, not shown here
public function setTraitA($value) {
$this->a = $value;
}
public function echoTraitA() {
echo $this->a;
}
}
class MyClass {
use TraitUsingA;
}
$a = new MyClass();
$a->setTraitA('trait');
$a->setA('base');
$a->echoTraitA(); // echos 'trait'
$a->echoA(); // echos 'base'
$b = new MyClass();
$b->setA('base');
$b->setTraitA('trait');
$b->echoTraitA(); // echos 'trait'
$b->echoA(); // echos 'trait'
The question here is how to handle property accesses, in particular
accesses to unspecified properties.
I actually would expect to have a lookup mechanism which first looks
in the trait, and if the property is not found there, its going to the
object. I expect this behavior, because it is similar to what we have
with inheritance and properties defined by a superclass.
If a property is not jet defined, I would expect it to be created in
the most inner scope, like local variables.
On the other hand, I also would expect, as in inheritance, properties
to be found defined by super classes, or the class which defines the
composition.
But this only my intuitive solution, so this could of course be
specified different.
Best regards
Stefan
--
Stefan Marr
Programming Technology Lab
Vrije Universiteit Brussel
Pleinlaan 2 / B-1050 Brussels / Belgium
http://prog.vub.ac.be/~smarr
The question here is how to handle property accesses, in particular
accesses to unspecified properties.
I actually would expect to have a lookup mechanism which first looks in
the trait, and if the property is not found there, its going to the
object. I expect this behavior, because it is similar to what we have
with inheritance and properties defined by a superclass.If a property is not jet defined, I would expect it to be created in the
most inner scope, like local variables.
On the other hand, I also would expect, as in inheritance, properties to
be found defined by super classes, or the class which defines the
composition.But this only my intuitive solution, so this could of course be specified
different.Best regards
Stefan
Hi,
I see this in a much simpler fashion. Treat traits as a preprocessor step
semantically.
Internally, trait methods on multiple classes may point to the same method
to save resources, but in my opinion there's no need to complicate matter by
introducing new resolution rules, scopes, and so on.
The way I see it:
- Property/Method name conflicts on trait/class, within the class including
the trait raises Fatal Error. - Extending classes can override the property/method, unless it's a final
method, just like as if they override normal parent class property/method. - There's a single space for a class property and its trait properties, so
access works as if it was all class properties.
Feedback?
Regards,
Stan Vassilev
Hi Stan,
I see this in a much simpler fashion. Treat traits as a preprocessor
step semantically.Internally, trait methods on multiple classes may point to the same
method to save resources, but in my opinion there's no need to
complicate matter by introducing new resolution rules, scopes, and
so on.
The way I see it:
- Property/Method name conflicts on trait/class, within the class
including the trait raises Fatal Error.
Well, with methods this is ok, but properties are not Java fields
which are known at compile time, so it is not that easy. Even when
they are known beforehand things are more complicated.
State is usually even less composable than behavior/methods.
- There's a single space for a class property and its trait
properties, so access works as if it was all class properties.
trait Counter {
var $value;
public function inc() { $this->value++; }
...
}
trait Color {
var $hue;
var $saturation;
var $value;
public function getRGB() { /* ... */}
...
}
class MyClass {
use Counter, Color;
}
Ok, you could argue that you than will have to refactor your property
names in your state. But then you will break other traits. Even worth,
this trait is defined in a widely used framework and your breaking
other peoples code just by changing implementation details which
should not even be visible to your users.
I do not think that this simple model is practicable. Unfortunately,
it is implied by the current state-less proposal, if we do not forbid
any access to properties inside of trait methods (which sounds like a
stupid idea to me). :(
Regards
Stefan
Feedback?
Regards,
Stan Vassilev--
--
Stefan Marr
Programming Technology Lab
Vrije Universiteit Brussel
Pleinlaan 2 / B-1050 Brussels / Belgium
http://prog.vub.ac.be/~smarr
trait Counter {
var $value;
public function inc() { $this->value++; }
...
}trait Color {
var $hue;
var $saturation;
var $value;
public function getRGB() { /* ... */}
...
}class MyClass {
use Counter, Color;
}Ok, you could argue that you than will have to refactor your property
names in your state. But then you will break other traits. Even worth,
this trait is defined in a widely used framework and your breaking other
peoples code just by changing implementation details which should not
even be visible to your users.I do not think that this simple model is practicable. Unfortunately, it
is implied by the current state-less proposal, if we do not forbid any
access to properties inside of trait methods (which sounds like a stupid
idea to me). :(Regards
Stefan
Hi,
Since the expectancy of a trait is it'll be used in many other classes
horizontally, I wouldn't think people will start taking generic names in
traits as they do in normal classes.
I'd personally expect this naming convention more likely:
trait Counter { protected $counterValue; }
trait Observable { protected $observableListeners; }
And so on. The benefit is it's easy to implement, understand, and use.
However if this is not acceptable to many, and we could live with the
complication of it, the best idea for trait property access I suppose would
be this:
trait Observable {
protected $listeners;
}
class Any {
use Observable;
function accessTraitProperty() {
var_dump($this->Observable->listeners);
}
}
Hence namespacing trait state into an automatically created object of the
same name, and we're done.
On the other said, a all traits could have an automatic property "owner",
which gives it access to the class state/properties.
Regards,
Stan Vassilev
On Saturday 18 April 2009 12:57:19 pm Stefan Marr wrote:
Hi Stan,
I see this in a much simpler fashion. Treat traits as a preprocessor
step semantically.Internally, trait methods on multiple classes may point to the same
method to save resources, but in my opinion there's no need to
complicate matter by introducing new resolution rules, scopes, and
so on.
The way I see it:
- Property/Method name conflicts on trait/class, within the class
including the trait raises Fatal Error.Well, with methods this is ok, but properties are not Java fields
which are known at compile time, so it is not that easy. Even when
they are known beforehand things are more complicated.State is usually even less composable than behavior/methods.
- There's a single space for a class property and its trait
properties, so access works as if it was all class properties.trait Counter {
var $value;
public function inc() { $this->value++; }
...
}trait Color {
var $hue;
var $saturation;
var $value;
public function getRGB() { /* ... */}
...
}class MyClass {
use Counter, Color;
}Ok, you could argue that you than will have to refactor your property
names in your state. But then you will break other traits. Even worth,
this trait is defined in a widely used framework and your breaking
other peoples code just by changing implementation details which
should not even be visible to your users.I do not think that this simple model is practicable. Unfortunately,
it is implied by the current state-less proposal, if we do not forbid
any access to properties inside of trait methods (which sounds like a
stupid idea to me). :(Regards
StefanFeedback?
Earlier in the thread, someone suggested making properties entirely restricted
to their trait, not any using class. If you want access to a trait's
property, use an accessor and be done with it. That way it doesn't matter if
different traits use the same property names since they'll always be resolved
relative to the method that's using them. Is there an implementation reason
why that would not be a feasible solution? From a developer experience
perspective it seems like a reasonable approach.
Also, at the risk of scope creep I will ask if any sort of runtime knowledge
is being considered? That is, would there be a way to at runtime get a list
of all traits that a given class is using, or access them separately? Would
that be a possible way around the question of duplicate method names? (Vis,
reimplement the conflicting method and then specify which of the trait methods
you want to use by calling it, or possibly calling both of them. That would
allow for potentially interesting use cases, and force the resolution logic
onto the implementer rather than trying to come up with the perfect resolution
rules for all case.
Eg:
trait Foo1 {
public function foo() {}
}
trait Foo2 {
public function foo() {}
}
class Bar {
use Foo1, Foo2;
public function foo() { // Without this, we get a parse error
return $this->Foo1->foo();
// or
return $this->Foo2->foo();
// or
foreach ($this->traits as $trait) {
$return[] = $trait->foo();
}
return implode(',', $return);
// or whatever makes sense for your use case.
}
}
--
Larry Garfield
larry@garfieldtech.com
Hi Larry,
Also, at the risk of scope creep I will ask if any sort of runtime
knowledge
is being considered? That is, would there be a way to at runtime
get a list
of all traits that a given class is using, or access them separately?
well, the reflection API will be extended to be able to reflect about
traits, too.
But beside this, there is "no" notion of traits at runtime.
Would
that be a possible way around the question of duplicate method
names? (Vis,
reimplement the conflicting method and then specify which of the
trait methods
you want to use by calling it, or possibly calling both of them.
That would
allow for potentially interesting use cases, and force the
resolution logic
onto the implementer rather than trying to come up with the perfect
resolution
rules for all case.
That is already possible by means of aliasing as described in the RFC
[1].
You can provide different aliases for the conflicting methods and then
decide how to proceed in the method implemented in the composing class
by calling the aliases.
Best regards
Stefan
[1] http://wiki.php.net/rfc/horizontalreuse
--
Larry Garfield
larry@garfieldtech.com--
--
Stefan Marr
Programming Technology Lab
Vrije Universiteit Brussel
Pleinlaan 2 / B-1050 Brussels / Belgium
http://prog.vub.ac.be/~smarr