Hello, PHP developers,
I'm new to the list, but have been using PHP for a number of years, have
done a little hacking in the source code, and have an interest in the
development of the language.
Particularly recently I have been reading with a good deal of excitement
Stefan Marr's RFC on 'horizontal reuse' on the PHP Wiki:
http://wiki.php.net/rfc/horizontalreuse
The trait functionality looks like a great start and an innovative
language development, and I'm looking forward to trying it out soon
(when I can find some more time!), and particularly looking forward to
making good use of it when it makes it into a release.
While it's still in the pre-release stage, though, I would like to put
in a vote for the assignment syntax: I think it is a lot easier to read
and understand than the 'insteadof' syntax.
I would also like to propose some extensions to the functionality as
currently described, which I think could potentially add tremendous
power to the mechanism, with relatively little additional conceptual
complexity and implementation effort. I've written it up as a bit of a
proposal below.
I'd love to hear what you think.
I would be willing to play a part implementing it, too.
Cheers,
Ben.
=============================
Proposed extensions to traits
Background
Traits in PHP [1] enable improved code reuse. They can be simplistically
viewed as compiler-assisted copy-and-paste. Methods designed to be
reused can be defined in traits and then these traits can be used in
classes. The traits are 'flattened', so it is as if the trait methods
were defined directly in the class in which they are used. Traits can
access other methods and properties of the class, including those of
other traits. They also fit in with the method overriding system:
methods defined directly in a class override those in used traits, which
in turn override those in ancestor classes.
There are two limitations of traits in their current implementation for
which I would like to propose extensions. The first limitation is that
traits can very easily break, particularly when methods are omitted from
classes in which the rest of the trait is used, or shadowed by method
definitions in the class proper. The second limitation is that the trait
overriding semantics are impoverished and needlessly restrictive.
Breakability
Limitation
There are two main aspects of traits which make them easy to break:
incorrect method calls and unintentionally shared state.
Incorrect method calls spring from the way trait methods can be omitted
from classes where the rest of the trait is used, or shadowed by methods
defined in the class proper. In either of these scenarios, any call in a
trait to such a method may not call the method that was originally
intended--they may fail, or they may call a different method, with
unpredictable results. Of course, sometimes such a behaviour is
desirable, if writing a trait which communicates with the rest of the
class by means of method calls, yet provides a fallback methods in case
the class author does not wish to provide such methods. However, when it
is not intended, this could lead to incorrect and difficult-to-pinpoint
behaviour.
The other way traits can break is by unintentionally sharing state.
Traits may make use of the same data members (not recommended, but
possible), or the same accessors, when each should actually have their
own independent state. Again, this could lead to incorrect and
difficult-to-pinpoint behaviour.
Example
trait ErrorReporting {
public function error($message) {
$this->print($message);
}
private function print($message) {
fputs($this->output,$message."\n");
}
}
class Printer {
use ErrorReporting;
public $output=null;
public function print($document) {
/* Send the document to the printer--$this->output. /
/ ... /
if (there_was_an_error()) {
$this->error("printing failed");
}
/ ... */
}
}
This example is very contrived, and hopefully no programmer would be
silly enough to fall into this exact trap. However, it is easy to
imagine more subtle cases where this kind of thing could happen,
particularly as traits and classes are modified from their original
conception.
The ErrorReporting trait allows the programmer to report errors in a
consistent way by using the trait in many classes. It includes a print()
method that is used to print the error to the screen. However, this
method has been unintentionally shadowed by a print method in the class,
intended to print a document on a printer. No error or warning will be
generated, but the class will not work as intended; probably it will
infinitely recurse; a nasty problem to track down.
Furthermore, even if the incorrect method call didn't occur, there would
be data-sharing problems, as both the ErrorReporting trait and the
class' print() function make use of the $output data member,
unintentially sharing data.
Proposal
I suggest these problems should be solved from two angles. Firstly,
additional warnings should be triggered to alert the programmer to the
problems, and secondly, the traits mechanism should be extended to allow
more desirable behaviours to be programmed.
Warnings
To avoid silent unintended shadowing, I suggest issuing a warning when a
conflict between trait and class methods occurs. So this would trigger
a warning:
trait SaySomething {
public function sayIt() {
echo "Something\n";
}
}
class Sayer {
use SaySomething;
public function sayIt() {
echo "Hello world!\n";
}
}
Something such as this would be required to suppress it:
use SaySomething {
sayIt = null;
}
or perhaps with a more 'insteadof'-like syntax something like:
use SaySomething {
unset sayIt;
}
This indicates that no sayIt() method should be included from any trait
(and thus there is no conflict with any sayIt() method defined in
Sayer). We could also have:
use SaySomething {
sayIt = SaySomething::sayIt;
}
which would also suppress the warning, but indicates that we know what
we are doing--we desire the trait method to be included. This is only
useful in combination with my proposal for dealing with overriding below
('prev').
Extension
I suggest these two problems can be simply solved by introducing two
additional uses of the trait keyword: as a scoping keyword and an access
specifier.
As a scoping keyword, it would be used analogously to self. Method calls
such as $this->print() could be replaced with trait::print() when the
programmer desires to ensure that their trait method, and only their
trait method, is called--when there is no intention that overriding
should be possible. It would only be able to be used in a trait, and
could only be used to reference methods or properties defined in the
same trait, using their original name.
As an access specifier, it would be used instead of public, private,
etc. in trait definitions, to mean that the member (data or method) can
and can only be accessed using the mechanism above (trait::).
Implementation could be very simple. When flattening a trait into a
class, every trait method, and every trait property with trait level
access, could be included with a mangled name (e.g. making use of the
reserved __ prefix and/or characters which are illegal in code, e.g.
__trait-TraitName-methodName), and any occurrences of trait:: scoping in
any trait method body could be replaced with a call to the same kind of
mangled name (e.g. trait::print() becomes
$this->__trait-ErrorReporting-print()). Data members could be treated in
exactly the same way (e.g. trait::$output becomes
$this->__trait-ErrorReporting-output). Static members pose no additional
problems. When flattening a trait into another trait, the
mangling/transformation is slightly different, but not much harder.
Perhaps a little demangling code for backtraces and/or error messages
would be nice. This would be sufficient, though. The trait access
specifier is nothing more than an indication that a method should be
omitted with its unmangled name (essentially the same as an insteadof
directive, but without any method taking its place), or that a property
should be included with a mangled name, rather than going through the
existing property conflict checking mechanism.
I realise a proposal for non-breakable traits [2] has already been
declined. However, I believe my proposal here is simpler to understand
and implement. It doesn't introduce any new overriding or
property-sharing semantics, doesn't overload the public and private
keywords with confusing additional meanings, and fits better with PHP's
dynamic nature by not requiring a decision at compile time about whether
or not to mangle a name (most notably in method bodies).
Rewritten example
The original example would work as desired if rewritten thus:
trait ErrorReporting {
trait $output = STDOUT;
public function setErrorOutput($output) {
trait::$output = $output;
}
public function error($message) {
trait::print($message);
}
trait function print($message) {
fputs(trait::$output,$message."\n");
}
}
class Printer {
use ErrorReporting;
public $output=null;
public function print($document) {
/* Send the document to the printer--$this->output. /
/ ... /
if (there_was_an_error()) {
$this->error("printing failed");
}
/ ... */
}
}
Overriding
Limitation
At present, the overriding semantics of traits are that a method defined
in a class proper overrides a method defined in a used trait which in
turn overrides a method defined in an ancestor class.
However, to my knowledge, there is no way for a class method to call a
trait method by the same name.
Furthermore, it is my belief that completely disallowing trait methods
of the same name, rather than allowing an overriding behaviour between
traits, is needlessly restrictive.
Proposal
I would therefore like to propose an extension backwards-compatible with
the current trait implementation. I will, however, extend the assignment
syntax, rather than the 'insteadof' syntax, as I find that clearer, and
more amenable to this extension. Of course, though, other syntaxes could
be found.
There are four aspects to this extension: (1) Introducing a new scoping
keyword. (2) Allowing a method name to be used from multiple traits. (3)
Allowing a trait to be included multiple times.
(1) Introducing a new scoping keyword.
I suggest something such as 'prev', to refer to the previous definition
of the method. Similar to 'parent', and the same in the absence of
traits, this refers to the 'next higher definition in the trait
hierarchy'; the 'trait hierarchy' is pictured like the 'class hierarchy'
but including traits. So if 'prev' is used in a class method when a
trait method of the same name exists, it will refer to the trait method,
rather than referring to a method in a parent class. Alternatively, the
'parent' keyword meaning could be changed to have this meaning. My
apologies if this is already the case: I have not played with the
implementation in the trunk (though look forward to doing so at some
stage) so am basing my comments purely on the RFC.
(2) Allowing a method name to be used from multiple traits.
When multiple methods of the same name are defined they simply take
their place in the 'trait hierarchy' and can be accessed by means of
'prev' (see (1) above).
So we could write, for instance:
trait Hello {
public function sayIt() {
echo "Hello ";
}
}
trait World {
public function sayIt() {
prev::sayIt();
echo "world ";
}
}
class HelloWorld {
use Hello, World {
sayIt = Hello::sayIt, World::sayIt;
}
public function sayIt() {
prev::sayIt();
echo "!\n";
}
}
$o = new HelloWorld();
$o->sayIt();
// Outputs "Hello world !\n"
sayIt() in the class overrides sayIt() in World, which overrides sayIt()
in Hello, but all are included. The first two make use of 'prev' to
reference those higher up the hierarchy.
(3) Allowing a trait to be included multiple times.
This is only useful with the extension I proposed above to deal with
breakability, where traits can be somewhat isolated.
However, the trait is simply given multiple names by which to refer to
it when it is used, and then its methods are referenced using those
names. So, for instance:
trait PublicQueue {
trait $arr = array();
public function add($item) {
trait::$arr[]=$item;
}
protected function remove() {
return array_shift(trait::$arr);
}
}
class OperationManager {
use Q1=PublicQueue, Q2=PublicQueue {
localQueue = Q1::add;
remoteQueue = Q2::add;
localUnqueue = Q1::remove;
remoteUnqueue = Q2::remove;
add = null;
remove = null;
}
public function process() {
while ($i=$this->localUnqueue()) echo "local $i\n";
while ($i=$this->remoteUnqueue()) echo "remote $i\n";
}
}
$o = new OperationManager();
$o->localQueue("one");
$o->remoteQueue("two");
$o->localQueue("three");
$o->process();
// Outputs local one\nlocal three\nremote two\n
Implementation
I don't think any of this poses particular implementation difficulties.
Everything that is necessary is pretty obvious: name mangling of trait
methods needs to use the aliases (e.g. Q1 in the example) rather than
the original trait name; multiple methods need to be able to be included
in a single class, with an ordering; and 'prev' needs to start a method
search at the current class, but not actually make a call until the
method using 'prev' has been passed in the search (or else, have some
way of starting the method search at the relevant place within a class);
only when prev is used is anything but the first method with a given
name called.
Fuller example
A more 'real-world' example is definitely in order now. One possible
scenario where all this kind of functionality could be useful is in
defining active record classes, where objects can 'retrieve themselves'
from a database, and 'update themselves' in the database. There are many
different semantics for records which could be added incrementally with
overriding traits, to construct classes with many different behaviours
and combinations of behaviours. With many, many database record types, a
lot of code reuse could be avoided. Just a taste:
abstract class ActiveRecord {
protected $new;
protected $id;
protected $other_values;
protected function __construct($id,$values,$new) {
$this->id=$id;
$this->other_values=$values;
$this->new=$new;
}
public function save() {
if ($this->new) {
if (!create_in_the_database()) return false;
if ($this->id===null) $this->id=last_insert_id();
} else {
if (!update_in_the_database()) return false;
}
return true;
}
public static function new() {
return new static(null,static::$default_values,true);
}
public static function get($id) {
return new static($id,get_from_the_database(),false);
}
}
trait LoggingOperations {
public function save() {
if ($this->new) {
log("Creating ".get_called_class());
} else {
log("Updating ".get_called_class()." ID ".$this->id);
}
if (!prev::save()) {
log("Failed");
return false;
}
log("Succeeded");
return true;
}
}
trait EnsuringNoConcurrentChanges {
trait $original_values = array();
protected function setOriginalValues($values) {
trait::$original_values = $values;
}
public static function get($id) {
$record = prev::get($id);
$record->setOriginalValues($record->other_values);
return $record;
}
public function save() {
$current_values=select_from_database();
if ($this->new&&$current_values) return false;
if (!$this->new&&!$current_values) return false;
if ($current_values!=trait::$original_values) return false;
return prev::save();
}
}
trait UsingHashesForIDs {
public function save() {
if ($this->id===null) $this->id=random_hash();
return prev::save();
}
}
class SessionRecord extends ActiveRecord {
protected static $default_values=array(
'user'=>'',
'time'=>''
);
use UsingHashesForIDs;
}
class Client extends ActiveRecord {
protected static $default_values=array(
'user'=>'',
'name'=>'',
'address'=>''
);
use EnsuringNoConcurrentChanges, LoggingOperations {
save = EnsuringNoConcurrentChanges::save,
LoggingOperations::save;
}
}
Obviously, other combinations are possible, too. Admittedly, with things
this simple, a single base class with configurable options would
probably be sufficient, but with additional complexity, traits that can
override each other definitely offer a distinct advantage with a great
amount of possible code reuse. There is a huge amount of power available
here, I think, which is unlocked by these proposed extensions.
Final note regarding grafts
My proposals here are not a replacement for grafts [1]. Grafts can be
viewed as compiler-assisted use of the delegate design pattern, and come
with all the benefits of that pattern. As such, they allow greater
isolation than traits and greater ability to use helper classes by
passing themselves via $this to other methods (which can then rely on
access to all the grafted class' public methods, without knowledge of
the enclosing class or which of the grafted class' methods it forwards
to).
Regarding the problem of maintaining encapsulation when doing such
things as returning $this from a graft method. I suggest perhaps the
'PHP way' of solving this would be to provide a get_called_object()
method, so one can return get_called_object() rather than return $this.
It could be problematic when grafts nest, but perhaps with some worked
examples and experience, would be found to be sufficient. In my opinion,
having $this behaving different ways in different contexts isn't just
difficult, but really isn't feasible--the programmer's intentions can't
be guessed adequately to make that kind of thing work.
References
I would also like to propose some extensions to the functionality as
currently described, which I think could potentially add tremendous
power to the mechanism, with relatively little additional conceptual
complexity and implementation effort. I've written it up as a bit of a
proposal below.I'd love to hear what you think.
-
New ideas are always welcome
-
Don't write long e-mails to a mailing list, write an RFC
http://wiki.php.net/rfc?do=register -
Don't overuse the word 'simple'
IMHO, if something is simple it should take 1-2 days to create a patch. That doesn't seem to be the case with what you're proposing.
But feel free to prove me wrong :)
Creating a patch will help getting feedback about what you're proposing
http://ca3.php.net/reST/php-src/README.MAILINGLIST_RULES
Hi, Jonathan,
- New ideas are always welcome
That's great to hear!
- Don't write long e-mails to a mailing list, write an RFC
http://wiki.php.net/rfc?do=register
Sure. Once I've digested and responded to Stefan's replies, I'll work on
putting something up there.
Perhaps this advice would be worth adding to the mailing list rules
page, as nothing about post length is mentioned there at all; it only
talks about attachments.
- Don't overuse the word 'simple'
IMHO, if something is simple it should take 1-2 days to create a
patch. That doesn't seem to be the case with what you're proposing.But feel free to prove me wrong :)
Yeah, well, I meant simple conceptually, and relatively speaking, not
that it wouldn't take time and effort. I don't think I'll be proving you
wrong! But as I said, I am willing to get my hands dirty with this.
Creating a patch will help getting feedback about what you're proposing
http://ca3.php.net/reST/php-src/README.MAILINGLIST_RULES
I hope I haven't broken any of the mailing list rules, but my apologies
if I have, and please point out specifically where I've gone wrong.
As far as a patch goes, I don't think that is appropriate at this stage.
I don't want to spend a lot of time creating a patch only to have it
rejected if this could be avoided by a little discussion. My time is too
valuable to me to waste like that, and with something as controversial
as this, there's a real danger of that happening.
Cheers,
Ben.
Creating a patch will help getting feedback about what you're
proposing http://ca3.php.net/reST/php-src/README.MAILINGLIST_RULESI hope I haven't broken any of the mailing list rules, but my
apologies if I have, and please point out specifically where I've gone
wrong.
No rules broken in my opinion but in general consider:
http://www.faqs.org/rfcs/rfc1855.html
Over 100 lines is considered "long"
The proposal (473 lines of plain text) would be easier to read on the web with some markup.
As far as a patch goes, I don't think that is appropriate at this
stage.
I don't want to spend a lot of time creating a patch only to have it
rejected if this could be avoided by a little discussion. My time is
too valuable to me to waste like that, and with something as
controversial as this, there's a real danger of that happening.
Np, since you mentioned hacking the source I thought you had done some tinkering.
- Don't write long e-mails to a mailing list, write an RFC
http://wiki.php.net/rfc?do=register
OK. I tried to do this. I got an account (username:isfs), but it seems
it is nothing more than an unprivileged account--I don't seem to be able
to edit or add pages at all. What do I need to do to be granted some?
Thanks,
Ben.
- Don't write long e-mails to a mailing list, write an RFC
http://wiki.php.net/rfc?do=registerOK. I tried to do this. I got an account (username:isfs), but it seems
it is nothing more than an unprivileged account--I don't seem to be able
to edit or add pages at all. What do I need to do to be granted some?
Greetings Ben,
You now have rights to the wiki rfc namespace.
Regards,
Philip
You now have rights to the wiki rfc namespace.
Thanks a lot, Philip.
I have now made an RFC based on the most recent discussions:
http://wiki.php.net/rfc/traitsmodifications
I think this is a more solid proposal than my original one, and I hope
we can continue to discuss it and agree to the extent that it's worth
starting an implementation.
Please read it and comment whenever you can find some time, guys!
Cheers,
Ben.
On Thu, Feb 10, 2011 at 6:25 PM, Ben Schmidt
mail_ben_schmidt@yahoo.com.auwrote:
You now have rights to the wiki rfc namespace.
Thanks a lot, Philip.
I have now made an RFC based on the most recent discussions:
http://wiki.php.net/rfc/traitsmodifications
I think this is a more solid proposal than my original one, and I hope
we can continue to discuss it and agree to the extent that it's worth
starting an implementation.Please read it and comment whenever you can find some time, guys!
As for your first example:
trait T {
public function foo() {
echo http://www.php.net/echo "T";
}}class C {
use T;
public function foo() {
echo http://www.php.net/echo "C";
}}
I think it would sometimes be desirable to allow this, for instance when a
trait has been updated in a framework to adapt to what has become common
practice in classes that uses it in the wild.
( I assume you already get error if function signature is different like in
inheritance? )
So to allow both cases, what about letting people use the final keyword on
functions to signal functions that can not be re declared without alias. Or
better, add a new keyword since final should mean final.
My 2 Euro cents,
André
I have now made an RFC based on the most recent discussions:
I think it would sometimes be desirable to allow this, for instance
when a trait has been updated in a framework to adapt to what has
become common practice in classes that uses it in the wild.
( I assume you already get error if function signature is different
like in inheritance? )So to allow both cases, what about letting people use the final
keyword on functions to signal functions that cannot be re declared
without alias. Or better, add a new keyword since final should mean
final.
I find the implementation in trunk convenient, traits aren't meant to replace inheritance & ~polymorphism.
The 'final' keyword currently means nothing to the class:
trait Foo {
final static function test() {
return 'Test';
}
}
class A {
use Foo;
static function test() {
return 'Test2';
}
}
echo A::test(); // returns 'Test2' in trunk
That might seem odd but it's not inheritance.
There is no error if the class method signature is different from a trait.
I'm comfortable with 'the class always wins', not really sure if should I be thinking differently...
It makes a trait somewhat fragile but that's part of the design vs. grafts.
That might seem odd but it's not inheritance.
Yeah. And that's my main concern with it. It seems like inheritance (and
is described like inheritance in the RFC at present--which is a
documentation issue that will need to be addressed in the manual
eventually), but it isn't. I feel it should be one or the other: either
have full inheritance semantics, or have full conflict-resolution
semantics like when trait methods conflict, at least by default. I think
the latter is better.
I'm really warming to the idea of using 'default' as I proposed in my
last email, though, as essentially doing what André suggested 'in
reverse'--i.e. a 'non-final' marker.
Ben.
On Thu, Feb 10, 2011 at 6:25 PM, Ben Schmidt
mail_ben_schmidt@yahoo.com.auwrote:You now have rights to the wiki rfc namespace.
Thanks a lot, Philip.
I have now made an RFC based on the most recent discussions:
http://wiki.php.net/rfc/traitsmodifications
I think this is a more solid proposal than my original one, and I hope
we can continue to discuss it and agree to the extent that it's worth
starting an implementation.Please read it and comment whenever you can find some time, guys!
As for your first example:
trait T {
public function foo() {
echohttp://www.php.net/echo "T";
}}class C {
use T;
public function foo() {
echohttp://www.php.net/echo "C";
}}I think it would sometimes be desirable to allow this, for instance when a
trait has been updated in a framework to adapt to what has become common
practice in classes that uses it in the wild.
( I assume you already get error if function signature is different like in
inheritance? )So to allow both cases, what about letting people use the final keyword on
functions to signal functions that can not be re declared without alias. Or
better, add a new keyword since final should mean final.
I don't mind that idea all that much, but perhaps doing the reverse
makes more sense: allowing a trait author to add a keyword to methods
which they intend and expect class authors to shadow. This means the
status quo is leaning towards stability, not breakages. Perhaps we could
reuse the 'default' keyword for this? That would indicate that the trait
has provided a default implementation of this method, but expects the
class author may well provide a more specialised implementation. Best
practice would then dictate that a responsible trait author should only
provide a default method for methods that were previously abstract
methods of the trait, so things wouldn't break, but traits can still
adapt to common practice, as you say.
So then this would generate an error, which could be resolved with an
insteadof in the use block:
trait T {
public function foo() {
echo "T";
}
}
class C {
use T;
public function foo() {
echo "C";
}
}
But this would not:
trait T {
default public function foo() {
echo "T";
}
}
class C {
use T;
public function foo() {
echo "C";
}
}
How does that sound?
Ben.
Some thoughts:
a) Class method conflict with trait
Class implementation always wins I feel is the right way to think about
traits.
But 'abstract' already has special meaning, so maybe a keyword like 'final'
could also do something special.
b) Support for 'non-breakable traits'
- Add trait-local scope
Really like the idea and how this could make traits less fragile.
e.g.
trait Foo {
trait $bar;
public $bar;
}
Could change the syntax to:
trait Foo {
var $bar = 'visibleByTrait'; // variable private to the trait --
zend_mangle_property_name('#Foo {NULL} bar')
public $bar = 'visibleByClass'; // variable public to the class it gets
composed in
}
class Test {
use Foo;
}
$t = new Test;
echo $t->bar; // 'visibleByClass'
Seems like it could allow traits to have their own independent state
(private properties), internally ~ zend_mangle_property_name('#' trait name
{NULL} prop name).
Small note: is there a plan to drop the T_VAR
token?
Any objections, concerns, more thoughts on the concept?
c) Extend 'use' syntax
Didn't quite understand, will read again.
Have you thought about how this could affect the reflection api and
documentation?
Hi Ben:
While it's still in the pre-release stage, though, I would like to put
in a vote for the assignment syntax: I think it is a lot easier to read
and understand than the 'insteadof' syntax.
The reason to go with insteadof is that the assignment syntax hides changes that might cause problems.
Thus, when you change any of the traits participating in a composition in a way that a conflict would be introduced, the assignment syntax is hiding this. With the insteadof syntax you are actually forced to reevaluate whether your code still makes sense and include the offending change explicitly.
There are two limitations of traits in their current implementation for
which I would like to propose extensions. The first limitation is that
traits can very easily break, particularly when methods are omitted from
classes in which the rest of the trait is used
Ehm, not sure I follow what you are getting at.
One thing to emphasize here is the explicit design choice to use the insteadof.
This does not only have the mentioned benefit of making problematic changes in the traits hierarchy explicit, but the second reason to go with this design was the wish to have a 'exclude' operator without actually having a full-exclude operator. Thus, there is no way that you can leave out arbitrary methods from a trait.
That means, you cannot build compositions which suddenly miss certain expected method implementations.
Well, of course, you can always build bugs into the code, but that is not different from the standard situation. You can always call arbitrary method names that just do not exist. Traits do not protect you from that. But the current design, protects you from explicitly allowing you to shot yourself in the foot.
, or shadowed by method
definitions in the class proper. The second limitation is that the trait
overriding semantics are impoverished and needlessly restrictive.
Hm, don't understand you here either. I needed to look up what impoverished actually means, you are not insulting me here, right? Just kidding ;)
Incorrect method calls spring from the way trait methods can be omitted
from classes where the rest of the trait is used, or shadowed by methods
defined in the class proper. In either of these scenarios, any call in a
trait to such a method may not call the method that was originally
intended--they may fail, or they may call a different method, with
unpredictable results. Of course, sometimes such a behaviour is
desirable, if writing a trait which communicates with the rest of the
class by means of method calls, yet provides a fallback methods in case
the class author does not wish to provide such methods. However, when it
is not intended, this could lead to incorrect and difficult-to-pinpoint
behaviour.
Ok, now I see what you are getting at.
And my answer to that is: If you require your trait to guarantee a certain non-trivial invariants, then well, what you are actually doing is something which should become a class with all its benefits.
Traits are not intended to replace classes as a mechanism to structure code, but they should supplement it.
However, I am open to votes on these things since I do not have any reason to believe that my standpoint is especially useful or superior, thats just how I understand what traits are useful for.
trait ErrorReporting {
public function error($message) {
$this->print($message);
}
private function print($message) {
fputs($this->output,$message."\n");
}
}class Printer {
use ErrorReporting;
public $output=null;
public function print($document) {
/* Send the document to the printer--$this->output. /
/ ... /
if (there_was_an_error()) {
$this->error("printing failed");
}
/ ... */
}
}
Ok, to summarize the example, you get a recursion because there is a naming problem, i.e., incompatibility between the used trait and the composing class.
No error or warning will be
generated, but the class will not work as intended; probably it will
infinitely recurse; a nasty problem to track down.
Furthermore, even if the incorrect method call didn't occur, there would
be data-sharing problems, as both the ErrorReporting trait and the
class' print() function make use of the $output data member,
unintentially sharing data.
Well, short answer: it is compiler-assisted copy-and-past, why isn't that trait just providing the glue that gets your class the necessary functionality to use a proper ErrorReporting class?
Sorry, I know not a really helpful answer, but I hope that examples shows how I see traits.
They are for reusing code in situations where the other concepts just break down. Not meant to replace those.
Proposal
I suggest these problems should be solved from two angles. Firstly,
additional warnings should be triggered to alert the programmer to the
problems, and secondly, the traits mechanism should be extended to allow
more desirable behaviours to be programmed.Warnings
To avoid silent unintended shadowing, I suggest issuing a warning when a
conflict between trait and class methods occurs. So this would trigger
a warning:trait SaySomething {
public function sayIt() {
echo "Something\n";
}
}
class Sayer {
use SaySomething;
public function sayIt() {
echo "Hello world!\n";
}
}
Ok, well, we could see the actual class body as another trait, and require it to follow the same composition rules, and that way make require the programmer to use insteadof in such a case.
In return that would mean, that traits are not part of the inheritance chain at all anymore.
Thus, first is the inheritance chain used to build up the implementation of a class, and afterwards all the traits are composed, while the original class is seen as another trait.
That idea has certainly something to it.
use SaySomething {
sayIt = null;
}
use SaySomething {
unset sayIt;
}
use SaySomething {
sayIt = SaySomething::sayIt;
}
I would reject that based on the arguments from the beginning of the email.
Ok, thats enough for this email.
I will come back to the extension in a later email.
Thanks for the proposals
Stefan
--
Stefan Marr
Software Languages Lab
Vrije Universiteit Brussel
Pleinlaan 2 / B-1050 Brussels / Belgium
http://soft.vub.ac.be/~smarr
Phone: +32 2 629 2974
Fax: +32 2 629 3525
Hi, Stefan,
Thanks for considering my ideas so carefully and for your detailed
replies.
The reason to go with insteadof is that the assignment syntax hides
changes that might cause problems.Thus, when you change any of the traits participating in a composition
in a way that a conflict would be introduced, the assignment syntax is
hiding this. With the insteadof syntax you are actually forced to
reevaluate whether your code still makes sense and include the
offending change explicitly.
OK. That makes sense. I'll rework any surviving parts of my proposal to
be based on the insteadof syntax.
This does not only have the mentioned benefit of making problematic
changes in the traits hierarchy explicit, but the second reason to go
with this design was the wish to have a 'exclude' operator without
actually having a full-exclude operator. Thus, there is no way that
you can leave out arbitrary methods from a trait.
This seems wrong to me. Doesn't it go against the stated principle that
the class author should have full control of traits? How is it full
control if you can't exclude a method? What is the reasoning behind this
wish not to have a full exclude operator?
Hm, don't understand you here either. I needed to look up what
impoverished actually means, you are not insulting me here, right?
Just kidding ;)
Sorry. I forget that a lot of you people aren't native English speakers
and sometimes funny words like that slip in! I think all you people who
communicate so fluently in additional languages are amazing.
No error or warning will be
generated, but the class will not work as intended; probably it will
infinitely recurse; a nasty problem to track down.
Furthermore, even if the incorrect method call didn't occur, there would
be data-sharing problems, as both the ErrorReporting trait and the
class' print() function make use of the $output data member,
unintentially sharing data.Well, short answer: it is compiler-assisted copy-and-past, why isn't
that trait just providing the glue that gets your class the necessary
functionality to use a proper ErrorReporting class?Sorry, I know not a really helpful answer, but I hope that examples
shows how I see traits.They are for reusing code in situations where the other concepts just
break down. Not meant to replace those.
I agree. I did say it was a poor example. I think the kinds of
behavioural problems it demonstrates, though, could be found in
situations where traits are truly appropriate.
No need to argue over examples, though. Plenty of other things to argue
about. :-)
Warnings
To avoid silent unintended shadowing, I suggest issuing a warning when a
conflict between trait and class methods occurs. So this would trigger
a warning:trait SaySomething {
public function sayIt() {
echo "Something\n";
}
}
class Sayer {
use SaySomething;
public function sayIt() {
echo "Hello world!\n";
}
}Ok, well, we could see the actual class body as another trait, and
require it to follow the same composition rules, and that way make
require the programmer to use insteadof in such a case.In return that would mean, that traits are not part of the inheritance
chain at all anymore.Thus, first is the inheritance chain used to build up the
implementation of a class, and afterwards all the traits are composed,
while the original class is seen as another trait.That idea has certainly something to it.
Yes, I think that would be good.
Having read your other emails, as well as having allowed my proposal
itself to clarify in my mind, I'm beginning to think it might be
clearest and cleverest to avoid inheritance altogether with traits. Of
course, this is in contrast to my original proposal, which was to
increase traits' participation in inheritance.
In this case, parent:: will always do what you expect then, and there's
no need for prev::.
Now to the other emails!
Ben.
Hi Ben:
Here the second part, on your extension proposal.
Extension
I suggest these two problems can be simply solved by introducing two
additional uses of the trait keyword: as a scoping keyword and an access
specifier.As a scoping keyword, it would be used analogously to self. Method calls
such as $this->print() could be replaced with trait::print() when the
programmer desires to ensure that their trait method, and only their
trait method, is called--when there is no intention that overriding
should be possible. It would only be able to be used in a trait, and
could only be used to reference methods or properties defined in the
same trait, using their original name.As an access specifier, it would be used instead of public, private,
etc. in trait definitions, to mean that the member (data or method) can
and can only be accessed using the mechanism above (trait::).
Ok, that would actually get us around all the meta-programming problems.
When you say that the 'trait'-access modifier always requires the access via a specific keyword (trait::) then mangling the name should be possible.
On the other hand, what would iterating over the object properties show?
Multiple properties with the same name, like with inherited private properties I suppose?
And an occurrence of trait:: would mean, do a $this-> but mangle the name first with the trait's name the original definition was in. Should be possible, but would certainly impact the Zend Engine a bit more than what we have now.
Certainly an interesting approach.
Has someone else an opinion on that?
Overriding
Limitation
At present, the overriding semantics of traits are that a method defined
in a class proper overrides a method defined in a used trait which in
turn overrides a method defined in an ancestor class.
Bye the way, where comes that terminology from: class proper? We are talking about the body of a class, right?
Well, but I will stop here, and try to cover the rest in the next mail...
Best regards
Stefan
--
Stefan Marr
Software Languages Lab
Vrije Universiteit Brussel
Pleinlaan 2 / B-1050 Brussels / Belgium
http://soft.vub.ac.be/~smarr
Phone: +32 2 629 2974
Fax: +32 2 629 3525
Hi again, Stefan,
Continuing the conversation.
Extension
I suggest these two problems can be simply solved by introducing two
additional uses of the trait keyword: as a scoping keyword and an access
specifier.As a scoping keyword, it would be used analogously to self. Method calls
such as $this->print() could be replaced with trait::print() when the
programmer desires to ensure that their trait method, and only their
trait method, is called--when there is no intention that overriding
should be possible. It would only be able to be used in a trait, and
could only be used to reference methods or properties defined in the
same trait, using their original name.As an access specifier, it would be used instead of public, private,
etc. in trait definitions, to mean that the member (data or method) can
and can only be accessed using the mechanism above (trait::).Ok, that would actually get us around all the meta-programming
problems.When you say that the 'trait'-access modifier always requires the
access via a specific keyword (trait::) then mangling the name should
be possible.On the other hand, what would iterating over the object properties
show?Multiple properties with the same name, like with inherited private
properties I suppose?
Well, I hadn't thought about it before. :-)
But yes, I think that makes perfect sense.
And an occurrence of trait:: would mean, do a $this-> but mangle the
name first with the trait's name the original definition was in.
Should be possible, but would certainly impact the Zend Engine a bit
more than what we have now.
One complication I hadn't thought of before is whether it should be
possible to access trait:: methods and properties in different objects.
And if it should be, what syntax to use. $that->trait::method() seems
somewhat ugly to me. That would also suggest we should use
$this->trait::method() for the same-object case.
Certainly an interesting approach.
Has someone else an opinion on that?
I think actually this is the most important part of my proposal, so if
it could be accepted, I would be very pleased. Obviously it does need a
bit more thought and discussion yet, though.
Overriding
Limitation
At present, the overriding semantics of traits are that a method defined
in a class proper overrides a method defined in a used trait which in
turn overrides a method defined in an ancestor class.Bye the way, where comes that terminology from: class proper? We are
talking about the body of a class, right?
Yes. 'Class proper' = 'class itself'. We seem to use 'proper' in English
as an adjective after a noun with a Latin-like sense of 'own/itself'. I
guess we probably got it from French, and surprisingly it hasn't died
out.
Cheers,
Ben.
Hi Ben:
Proposal
I would therefore like to propose an extension backwards-compatible with
the current trait implementation. I will, however, extend the assignment
syntax, rather than the 'insteadof' syntax, as I find that clearer, and
more amenable to this extension. Of course, though, other syntaxes could
be found.There are four aspects to this extension: (1) Introducing a new scoping
keyword. (2) Allowing a method name to be used from multiple traits. (3)
Allowing a trait to be included multiple times.(1) Introducing a new scoping keyword.
I suggest something such as 'prev', to refer to the previous definition
of the method. Similar to 'parent', and the same in the absence of
traits, this refers to the 'next higher definition in the trait
hierarchy'; the 'trait hierarchy' is pictured like the 'class hierarchy'
but including traits. So if 'prev' is used in a class method when a
trait method of the same name exists, it will refer to the trait method,
rather than referring to a method in a parent class. Alternatively, the
'parent' keyword meaning could be changed to have this meaning. My
apologies if this is already the case: I have not played with the
implementation in the trunk (though look forward to doing so at some
stage) so am basing my comments purely on the RFC.(2) Allowing a method name to be used from multiple traits.
When multiple methods of the same name are defined they simply take
their place in the 'trait hierarchy' and can be accessed by means of
'prev' (see (1) above).So we could write, for instance:
trait Hello {
public function sayIt() {
echo "Hello ";
}
}
trait World {
public function sayIt() {
prev::sayIt();
echo "world ";
}
}
class HelloWorld {
use Hello, World {
sayIt = Hello::sayIt, World::sayIt;
}
public function sayIt() {
prev::sayIt();
echo "!\n";
}
}
$o = new HelloWorld();
$o->sayIt();
// Outputs "Hello world !\n"sayIt() in the class overrides sayIt() in World, which overrides sayIt()
in Hello, but all are included. The first two make use of 'prev' to
reference those higher up the hierarchy.
Hm, after writing the part of my mail below, and coming back here,
why can't you use aliases for that kind of problem?
Instead of the prev:: stuff, you could just introduce an alias for the method you are 'hiding'. That should provide you with identical properties. And it would avoid 'magic' and weakening the flattening. Furthermore, traits are very explicit in what they do bring into your class at the moment, that would get lost with the approach required for 'prev::'.
(3) Allowing a trait to be included multiple times.
I still do not know what to say to the previous proposal, the 'prev' stuff.
But, this one gets a reject, because you actually want to use classes here.
I do not see the benefit of using a trait here instead of a class for your queues.
Or you might want to elaborate why this design is better than one that uses proper objects.
abstract class ActiveRecord {
protected $new;
protected $id;
protected $other_values;
protected function __construct($id,$values,$new) {
$this->id=$id;
$this->other_values=$values;
$this->new=$new;
}
public function save() {
if ($this->new) {
if (!create_in_the_database()) return false;
if ($this->id===null) $this->id=last_insert_id();
} else {
if (!update_in_the_database()) return false;
}
return true;
}
public static function new() {
return new static(null,static::$default_values,true);
}
public static function get($id) {
return new static($id,get_from_the_database(),false);
}
}
trait LoggingOperations {
public function save() {
if ($this->new) {
log("Creating ".get_called_class());
} else {
log("Updating ".get_called_class()." ID ".$this->id);
}
if (!prev::save()) {
log("Failed");
return false;
}
log("Succeeded");
return true;
}
}
trait EnsuringNoConcurrentChanges {
trait $original_values = array();
protected function setOriginalValues($values) {
trait::$original_values = $values;
}
public static function get($id) {
$record = prev::get($id);
$record->setOriginalValues($record->other_values);
return $record;
}
public function save() {
$current_values=select_from_database();
if ($this->new&&$current_values) return false;
if (!$this->new&&!$current_values) return false;
if ($current_values!=trait::$original_values) return false;
return prev::save();
}
}
trait UsingHashesForIDs {
public function save() {
if ($this->id===null) $this->id=random_hash();
return prev::save();
}
}
class SessionRecord extends ActiveRecord {
protected static $default_values=array(
'user'=>'',
'time'=>''
);
use UsingHashesForIDs;
}
class Client extends ActiveRecord {
protected static $default_values=array(
'user'=>'',
'name'=>'',
'address'=>''
);
use EnsuringNoConcurrentChanges, LoggingOperations {
save = EnsuringNoConcurrentChanges::save,
LoggingOperations::save;
}
}
Ok, that example is, well, not actually helping you.
You can do all the things here without your extensions, I believe.
Your problem with the save methods is solved by aliases and parent:: (parent:: only has semantics with respect to inheritance, so it will do the right thing.
Your Client will have a save method that calls the aliased versions of the saves in the order you want.
And in get() you can also just use parent::, no?
The only problem with aliases is that you do not get self-recursion, but you do not use that here.
Final note regarding grafts
Grafts are dead. There is no implementation, and I do see traits and grafts as an either the one or the other, but not both. We do not need to make PHP more complex...
All in all, I would like to have all the proposals recorded somewhere, in a way easily findable when you look up traits RFCs. Should be possible to group them in a namespace on the wiki, right?
Thanks again and sorry for 'rejecting' most of your ideas, but thats not final, I am still open for arguments.
By breaking up the topics into subthreads, it hopefully makes it easier for the community to comment on the different topics, too.
Best regards
Stefan
--
Stefan Marr
Software Languages Lab
Vrije Universiteit Brussel
Pleinlaan 2 / B-1050 Brussels / Belgium
http://soft.vub.ac.be/~smarr
Phone: +32 2 629 2974
Fax: +32 2 629 3525
Hi, Stefan,
I think if the other stuff goes ahead, we can probably scrap what I
originally proposed here. But perhaps something else would be helpful. I
won't comment very specifically on aspects of my original proposal, but
will just raise some new ideas for consideration and further thought.
It all hinges on aliasing: how it works and how it can be avoided when
necessary. Your comment on my example made this most clear.
abstract class ActiveRecord {
protected $new;
protected $id;
protected $other_values;
protected function __construct($id,$values,$new) {
$this->id=$id;
$this->other_values=$values;
$this->new=$new;
}
public function save() {
if ($this->new) {
if (!create_in_the_database()) return false;
if ($this->id===null) $this->id=last_insert_id();
} else {
if (!update_in_the_database()) return false;
}
return true;
}
public static function new() {
return new static(null,static::$default_values,true);
}
public static function get($id) {
return new static($id,get_from_the_database(),false);
}
}
trait LoggingOperations {
public function save() {
if ($this->new) {
log("Creating ".get_called_class());
} else {
log("Updating ".get_called_class()." ID ".$this->id);
}
if (!prev::save()) {
log("Failed");
return false;
}
log("Succeeded");
return true;
}
}
trait EnsuringNoConcurrentChanges {
trait $original_values = array();
protected function setOriginalValues($values) {
trait::$original_values = $values;
}
public static function get($id) {
$record = prev::get($id);
$record->setOriginalValues($record->other_values);
return $record;
}
public function save() {
$current_values=select_from_database();
if ($this->new&&$current_values) return false;
if (!$this->new&&!$current_values) return false;
if ($current_values!=trait::$original_values) return false;
return prev::save();
}
}
trait UsingHashesForIDs {
public function save() {
if ($this->id===null) $this->id=random_hash();
return prev::save();
}
}
class SessionRecord extends ActiveRecord {
protected static $default_values=array(
'user'=>'',
'time'=>''
);
use UsingHashesForIDs;
}
class Client extends ActiveRecord {
protected static $default_values=array(
'user'=>'',
'name'=>'',
'address'=>''
);
use EnsuringNoConcurrentChanges, LoggingOperations {
save = EnsuringNoConcurrentChanges::save,
LoggingOperations::save;
}
}Ok, that example is, well, not actually helping you.
You can do all the things here without your extensions, I believe.
Before commenting speciically I'd like to point out that whether
something can be done shouldn't be the only thing considered. Other
important aspects are how easily it can be done, how tidily it can be
done, how much code duplication it requires, and so on. Sometimes, a
construct by merely providing elegance, without any additional power, is
worthwhile. In fact, you can model any program as a Turing machine, so
we actually need very little; but there aren't many popular languages
like that!
In this case, though, I think there are a couple of shortcomings in the
current trait behaviour that mean this can't quite be done reliably.
One thing of note is the use of the trait-scoped property, making the
trait more robust. That was the subject of an earlier email, and one of
the parts of the proposal you viewed most favourably, so I'm happy about
that. I won't dwell on it here.
Your problem with the save methods is solved by aliases and parent::
(parent:: only has semantics with respect to inheritance, so it will
do the right thing.Your Client will have a save method that calls the aliased versions of
the saves in the order you want.And in get() you can also just use parent::, no?
Aliases come a long way, and to be honest, I am still coming to terms
with how powerful they are, particularly in combination with traits'
ability to have abstract methods.
But there is at least one missing link.
You can't reliably use parent:: in a trait method, because that
jeopardises the reusability of the trait. In some composing classes, you
might want it to call a method that isn't from the superclass. In this
minimal example, parent:: happens to work; but that shouldn't be relied
upon when writing a trait, and in the generalised case, where there are
more traits and more composition combinations, it would fall apart.
This means at the very least, you would have to use a method declared in
the trait, and then write forwarding methods. In this case, you have
basically returned to use of the delegate pattern--traits have lost
their benefit. It could look something like this:
use EnsuringNoConcurrentChanges, LoggingOperations {
LoggingOperations::save insteadof EnsuringNoConcurrentChanges::save;
EnsuringNoConcurrentChanges::save as saveForLoggingOperations;
}
private function saveForEnsuringNoConcurrentChanges() {
return parent::save();
}
My original proposal tried to solve this by introducing a prev:: scoping
keyword which worked like parent:: but on a smaller scale, so could take
its place in appropriate methods. I don't deny, though, that having
parent:: and prev:: filling such similar, but possibly confusingly
distinct, functions was not ideal.
Perhaps I was aiming at the wrong target, though. Perhaps what we need
to do to solve this problem is extend the aliasing functionality.
Suppose inside the 'use' block we can have not just exclusion
directives, and aliasing directives for trait methods, but also aliasing
directives for other methods. This may well solve the problem. It
removes the burden from the programmer to write a lot of glue code. I
think we end up with a lot of messy names, though, if we have to use
things like saveForLoggingOperations to ensure traits are linked
together properly. To mitigate this, I suggest we also allow SomeTrait::
to be used after 'as' with the meaning that the aliasing is to an
abstract trait-level access method; this is slightly better. Also, by
making use of the ability to change method visibility, methods in traits
designed to be strung together like this could have trait-level
visibility and be 'publicised' in the 'use' directive, without having to
list a whole lot of clashing methods (as other trait-level access
methods would simply not clash, as they wouldn't be visible). So it
could look more like this:
use EnsuringNoConcurrentChanges, LoggingOperations {
LoggingOperations::save as public;
EnsuringNoConcurrentChanges::save as LoggingOperations::prev_save;
parent::save as EnsuringNoConcurrentChanges::prev_save;
}
This is significantly more elegant, IMHO.
In fact, this is so nice, could I suggest it would be nice to allow
other delegation-like forwarding to be done like this? You could have
'use' without a trait even, just like this:
use {
$queue->add as addToQueue;
}
Since the properties' object wouldn't be available at compile time, this
extra ability would probably have to be implemented by basically
generating an addToQueue method, and it wouldn't work with arguments
passed by reference. It would basically be a shorthand for
public function addToQueue() {
return call_user_func(array($queue,'add'),func_get_args());
}
but much more elegant.
I think this achieves everything I was aiming for with my original
proposal, and in a nicer way.
What do you think?
If nothing else, the discussion is definitely worthwhile. I hope we can
get this revised extension, or a later revision, into PHP, so we truly
reap the benefits of the great discussion.
Final note regarding grafts
Grafts are dead. There is no implementation, and I do see traits and
grafts as an either the one or the other, but not both. We do not need
to make PHP more complex...
Could that be noted in the RFC, please?
It would be good to note the same in this one, too:
http://wiki.php.net/rfc/nonbreakabletraits
I only found out that it was declined by chance when I happened to see
where it was listed in the 'parent page':
If I hadn't decided to have a look at that page for another reason, I
wouldn't have known that a resolution had been reached.
All in all, I would like to have all the proposals recorded somewhere,
in a way easily findable when you look up traits RFCs. Should be
possible to group them in a namespace on the wiki, right?
I will pull together all this new stuff into a new RFC on the wiki,
which can serve as a more useful ongoing reference point at least for
this discussion. Should I put the original proposal in an RFC too, or
should we just let that die and I put the revised one up?
And if I need to do anything to ease the gathering of these RFCs into a
namespace, just let me know.
Thanks again and sorry for 'rejecting' most of your ideas, but thats
not final, I am still open for arguments.
No, by all means! The discussion is making the proposal better, and that
won't happen if you don't reject the bad ideas! Only if you rejected
something without good reason would there be any need to apologise. :-)
By breaking up the topics into subthreads, it hopefully makes it
easier for the community to comment on the different topics, too.
I think having it online shortly as an RFC might help too.
Thanks again,
Ben.
In fact, this is so nice, could I suggest it would be nice to allow
other delegation-like forwarding to be done like this? You could have
'use' without a trait even, just like this:use {
$queue->add as addToQueue;
}Since the properties' object wouldn't be available at compile time, this
extra ability would probably have to be implemented by basically
generating an addToQueue method, and it wouldn't work with arguments
passed by reference. It would basically be a shorthand forpublic function addToQueue() {
return call_user_func(array($queue,'add'),func_get_args());
}but much more elegant.
Of course should have been
public function addToQueue() {
return call_user_func(array($this->queue,'add'),func_get_args());
}
Ben.