Hi folks,
With a 5.4 release right around the corner I'd like a moment of your time to
reconsider this issue [1].
I've read through the original conversation [2] and would summarize the
points as follows:
. several folks see value in the feature
. the feature would not be an impedance for people who did not wish to use
it
. there was only one objection which was largely subdued
The common use case of pairing traits with interfaces is the case where the
feature adds value.
Just curious why it died on the table if several folks saw value in it,
including Stephan who I gather is the primary architect of traits.
The dispute regarding the feature was the merit of a 'compile time' check vs
placing the burden on the developer to ensure all methods in a trait are
provided by the class in which it is used. In an earlier conversation about
traits Stefan said this:
"Yes, Traits are meant to be compile-time only, they are not used to
introduce typing relationships."
Which makes me even more comfortable about the merit of another compile time
check. As I argued in the original thread, the feature is merely an
embellishment atop the already supported use of the 'abstract' keyword as it
pertains to traits. And what if nothing else are features like PPP,
abstract, interfaces etc than syntactic sugar? Plus it will save folks some
typing :)
[1]
https://wiki.php.net/rfc/horizontalreuse#requiring_composing_class_to_implement_interface
[2] http://marc.info/?l=php-internals&m=129188077704898
thanks,
-nathan
Well, I have a few opinions on this that I think are worth sharing:
-
If this change is made, we no longer would have mixins, but would
have regular multiple-inheritance (With all the "issues" associated
with it). Note that I said mixins and not traits. That's because
since these were implemented with state (member properties), they are
not traits as implemented, but regular mixins. (Yet another gripe, but
that's slightly off topic). -
Traits and mixins are supposed to be the symmetric counterpart to
interfaces. An interface is a specification without implementation.
A trait/mixin is supposed to be implementation without specification.
And a class (including abstract) is implementation with specification.
So if you allowed type hinting of traits/mixins, wouldn't that just
make them nothing more than abstract classes (in that they can't be
used directly, but both provide the same info)?
There's a much bigger problem though. How would you solve the problem
where a class uses a trait that implements an interface, but you don't
pull one of the methods through (for example, you exclude it in the
use clause)? At first this sounds simple, just enforce it on the
class. But that means that the compiler couldn't enforce the
interface at definition time of the trait. So then that means that by
the trait implementing the interface is actually forcing any class
that implements it to be compliant and ensure that it implements the
interface. So at that point the trait stops being useful for
horizontal reuse, but becomes nothing more than a class with the
limitations that brings along. Traits/mixins were designed to avoid
this problem. So why should we re-introduce it?
Some of that is design choice, but I think we need to realize that
there are a lot of people who have thought about these problems before
in other areas of CS. What do other languages do? Can you name one
mainstream language that implements traits/mixins as a first class
citizen where they can implement an interface or can be type-hinted
against? In fact, the original paper the RFC references
(http://scg.unibe.ch/archive/papers/Duca06bTOPLASTraits.pdf)
explicitly recommends separating the specification (interface) from
the trait:
A quote from that paper:
We solved these problems by creating traits for the different collection properties and
combining them to build the required collection classes. To achieve maximum flexibility,
we separated the properties specifying the implementation of a collection from the
properties specifying the interface. This allowed us to freely combine different interfaces (e.g.,
“sorted-extensible interface” and “sorted-extensible-immutable interface”) with any of the
suitable implementations (e.g., “linked-list implementation” and “array-based implementation”).
So in all, I feel it would be a significant mistake to allow traits
(mixins) to implement interfaces or be type-hinted against. It goes
against the principles that they were designed with and destroys some
of their key benefits...
Anthony
Hi folks,
With a 5.4 release right around the corner I'd like a moment of your time to
reconsider this issue [1].I've read through the original conversation [2] and would summarize the
points as follows:. several folks see value in the feature
. the feature would not be an impedance for people who did not wish to use
it
. there was only one objection which was largely subduedThe common use case of pairing traits with interfaces is the case where the
feature adds value.Just curious why it died on the table if several folks saw value in it,
including Stephan who I gather is the primary architect of traits.The dispute regarding the feature was the merit of a 'compile time' check vs
placing the burden on the developer to ensure all methods in a trait are
provided by the class in which it is used. In an earlier conversation about
traits Stefan said this:"Yes, Traits are meant to be compile-time only, they are not used to
introduce typing relationships."Which makes me even more comfortable about the merit of another compile time
check. As I argued in the original thread, the feature is merely an
embellishment atop the already supported use of the 'abstract' keyword as it
pertains to traits. And what if nothing else are features like PPP,
abstract, interfaces etc than syntactic sugar? Plus it will save folks some
typing :)[1]
https://wiki.php.net/rfc/horizontalreuse#requiring_composing_class_to_implement_interface
[2] http://marc.info/?l=php-internals&m=129188077704898thanks,
-nathan
On Sat, Oct 22, 2011 at 8:25 AM, Anthony Ferrara ircmaxell@gmail.comwrote:
Well, I have a few opinions on this that I think are worth sharing:
Anthony, thanks for your reply.
- If this change is made, we no longer would have mixins, but would
have regular multiple-inheritance (With all the "issues" associated
with it). Note that I said mixins and not traits. That's because
since these were implemented with state (member properties), they are
not traits as implemented, but regular mixins. (Yet another gripe, but
that's slightly off topic).
While this may be scary to many it is in fact the motivation for the
feature. Multiple inheritance or some semblance thereof is something
user-space folks have been interested in for some time [1]. There is no
unanimous interest for it, nor is there unanimous favor for practically
anything in PHP, that's what makes it unique IMO, different strokes for
different folks.
- Traits and mixins are supposed to be the symmetric counterpart to
interfaces. An interface is a specification without implementation.
A trait/mixin is supposed to be implementation without specification.
And a class (including abstract) is implementation with specification.
So if you allowed type hinting of traits/mixins, wouldn't that just
make them nothing more than abstract classes (in that they can't be
used directly, but both provide the same info)?
True, but doesn't the use of the abstract keyword in a trait already mean
they support specification? There may be an academic concept of a trait,
but perhaps it has already been massaged for the PHP implementation.
There's a much bigger problem though. How would you solve the problem
where a class uses a trait that implements an interface, but you don't
pull one of the methods through (for example, you exclude it in the
use clause)? At first this sounds simple, just enforce it on the
class. But that means that the compiler couldn't enforce the
interface at definition time of the trait. So then that means that by
the trait implementing the interface is actually forcing any class
that implements it to be compliant and ensure that it implements the
interface. So at that point the trait stops being useful for
horizontal reuse, but becomes nothing more than a class with the
limitations that brings along. Traits/mixins were designed to avoid
this problem. So why should we re-introduce it?
I'm sorry I'm missing the point here, could you show a brief sample? I
think the forcing aspect is the point here though, and it is desired. A
trait that uses several methods from an interface, say Iterator or another
class like SplFileInfo. The trait should be able to say it needs a subset
of the methods in that interface or class today, and may at any time in the
future rely on any of the full set of methods defined in the interface or
class. Again, this functionality is already available in the current
implementation via the abstract keyword. It just seems silly to have to
maintain a list of methods by hand that could be done with a concise
notation instead. And it's even uglier if the trait relies on more than one
class interface, imagine code like this:
<?php
trait IteratorHelper {
/* Iterator */
abstract public function current()
;
abstract public function key()
;
abstract public function next()
;
abstract public function rewind()
;
abstract public function valid();
/* SplFileInfo */
abstract public function getATime();
abstract public function getBasename();
// etc..
}
?>
The author is wondering, do I bother to key in all the methods from
SplFileInfo now in case I ever use one of the methods I haven't in the
original implementation, or wait until I do use another method from
SplFileInfo and then update the list of abstract methods. And what if I
have an abstract method mentioned in the list, but remove calls to it from
within the trait and then forget to update the list, is that bad? Also note
the ambiguity introduced without comments indicating which foreign class /
interface these abstract (required) methods come from. The comments here,
and indeed the feature I suggest, would be more expressive to authors
because it would be explicit. Imagine encountering the code segment above
with the absence of the comments, you would be good to guess which classes /
interfaces the trait was intended to be used with. Instead something like:
<?php
trait IteratorHelper require Iterator, SplFileInfo {
//..
}
?>
is not only more concise, it is more expressive IMO in that it more clearly
indicates the author's intent for the use of said trait. It is also more
maintainable because there is no need to maintain a list of methods (for
which there is no explicit way to describe the interface / class they belong
to) when changes are made. There is also value for documentation tools.
With the current paradigm of just the abstract keywords using comments as I
suggested is not something a documentation tool can pick up, though I'm sure
it will be a matter of time until those tools cook up a silly annotation to
support it...
Some of that is design choice, but I think we need to realize that
there are a lot of people who have thought about these problems before
in other areas of CS. What do other languages do? Can you name one
mainstream language that implements traits/mixins as a first class
citizen where they can implement an interface or can be type-hinted
against? In fact, the original paper the RFC references
(http://scg.unibe.ch/archive/papers/Duca06bTOPLASTraits.pdf)
explicitly recommends separating the specification (interface) from
the trait:A quote from that paper:
We solved these problems by creating traits for the different collection
properties and
combining them to build the required collection classes. To achieve
maximum flexibility,
we separated the properties specifying the implementation of a collection
from the
properties specifying the interface. This allowed us to freely combine
different interfaces (e.g.,
“sorted-extensible interface” and “sorted-extensible-immutable
interface”) with any of the
suitable implementations (e.g., “linked-list implementation” and
“array-based implementation”).So in all, I feel it would be a significant mistake to allow traits
(mixins) to implement interfaces or be type-hinted against. It goes
against the principles that they were designed with and destroys some
of their key benefits...
A couple thoughts here.
-
I looked around in other language specs before re-hashing this topic and
from what I can tell these other languages don't have a concept of
interfaces as PHP does. One could say they do have classes proper and did
not support traits noting the classes which they were dependent upon in
their specification. Seems to me many of these other languages were
more wholly conceived at their inception and I doubt the user base had been
striving to implement multiple inheritance via a workaround for the past 5
years. Just google 'php multiple inheritance'. -
For better or for worse the decision to support specification within
traits has already been made in the form of the abstract keyword. I'm
suggesting taking it one step further for folks who are interested in some
semblance of multiple inheritance.
If it's a nightmare to implement internally maybe it's best left alone, but
if it is straightforward I say implement it and let folks take on 'all the
problems of multiple inheritance' if they wish to; everyone else can just
laugh at them if they wish to. Isn't that the PHP way?
[1] http://www.issociate.de/board/post/478936/Multiple_Inheritance.html
thanks,
-nathan
Well, I have a few opinions on this that I think are worth sharing:
- If this change is made, we no longer would have mixins, but would
have regular multiple-inheritance (With all the "issues" associated
with it). Note that I said mixins and not traits. That's because
since these were implemented with state (member properties), they are
not traits as implemented, but regular mixins. (Yet another gripe, but
that's slightly off topic).
I think you misunderstand the difference between traits and mixins:
http://code.google.com/p/es-lab/wiki/Traits
Scala has mixins, php has traits.
In short, the composition order does not matter with a trait (because there are conflict resolution rules).
With a mixin, the conflict resolution rule is the order of declaration. class foo mixwith bar1, bar2 {} // bar2 wins
Though traits were originally defined as 'stateless', it doesn't make them 'mixins' because they have state:
http://bergel.eu/download/papers/Berg07e-StatefulTraits.pdf
Javascript is probably the best example of 'mixins':
myobject.mixin(bar1,bar2, {otherMethod: function(){} })
Hi Anthony:
I will not comment on the original question of this thread, but felt obliged to point out some details where my understanding differs from what I understand from your explanation.
Well, I have a few opinions on this that I think are worth sharing:
- If this change is made, we no longer would have mixins, but would
have regular multiple-inheritance (With all the "issues" associated
with it). Note that I said mixins and not traits. That's because
since these were implemented with state (member properties), they are
not traits as implemented, but regular mixins. (Yet another gripe, but
that's slightly off topic).
Please refer to: https://wiki.php.net/rfc/horizontalreuse#handling_of_propertiesstate
"Traits do not provide any provisioning for handling state."
What you observe is that PHP is a very dynamic language when it comes to the definition of fields.
And that is what traits also take into account. We do the minimal possible thing, to keep the WTF factor low, but there is no "real" support for state.
And as someone else pointed out: the main difference is indeed the difference between linear application (mixins) and order-less composition (traits).
There's a much bigger problem though. How would you solve the problem
where a class uses a trait that implements an interface, but you don't
pull one of the methods through (for example, you exclude it in the
use clause)?
The relevant part of the RFC: https://wiki.php.net/rfc/horizontalreuse#conflict_resolution
We abandoned the idea of an explicit exclude operator long ago. (I think it was never in the PHP SVN.)
Thus, the situation you describe cannot occur in the sense that a method is missing from the class.
However, you can of course provide incompatible method implementations. (Which does not make traits any different from any other way to implement an interface.)
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
Stefan,
Thanks for the reply. I disagree on a few points that you made
however. See below for my reply.
Please refer to: https://wiki.php.net/rfc/horizontalreuse#handling_of_propertiesstate
"Traits do not provide any provisioning for handling state."
The original RFC was correct in that traits should not have any state.
However the implementation differs from the RFC significantly in that
respect.
What you observe is that PHP is a very dynamic language when it comes to the definition of fields.
And that is what traits also take into account. We do the minimal possible thing, to keep the WTF factor low, but there is no "real" support for state.
And as someone else pointed out: the main difference is indeed the difference between linear application (mixins) and order-less composition (traits).
Incorrect. Traits can explicitly define properties which actually
prevent the parent class from changing this. See these two examples:
http://codepad.viper-7.com/fqD91I and
http://codepad.viper-7.com/DFDpqM So there is absolutely 100% support
for state here. I could understand if it was a dynamic property or
the like, but this is really state.
The relevant part of the RFC: https://wiki.php.net/rfc/horizontalreuse#conflict_resolution
We abandoned the idea of an explicit exclude operator long ago. (I think it was never in the PHP SVN.)
Thus, the situation you describe cannot occur in the sense that a method is missing from the class.
However, you can of course provide incompatible method implementations. (Which does not make traits any different from any other way to implement an interface.)
The situation can absolutely happen simply with the conflict
resolution (as you say). In fact, that way is worse since the
signatures are different it means that you literally cannot use both
traits (with the situation I originally described, all it would take
is implementing a dummy method to complete the interface).
As I said before, traits are supposed to be the symmetric dual to
interfaces. Implementation without any contract. If we take a look
at the following table, you can see that one corner is currently
empty. And that corner is the one that's supposed to be filled by
traits. If we allow traits to "implement" an interface, that corner
remains empty...
|Implementation|Contract
Implementation| Interface|Class
Contract| Class________|_???????
Again, just my $0.02...
Anthony
Hi Anthony:
Please refer to: https://wiki.php.net/rfc/horizontalreuse#handling_of_propertiesstate
"Traits do not provide any provisioning for handling state."
The original RFC was correct in that traits should not have any state.
However the implementation differs from the RFC significantly in that
respect.
Please read the RFC. Thanks!
"Traits do not provide any provisioning for handling state."
"However, every behavior needs state to operate on, otherwise it could be just a static functional helper method. Thus, trait code will either need to use accessors, which is favorite way to go since it provides full traits semantics, or they use properties, which is possible but rather a convenience feature."
"Thus, the goal for a consistent language design is to raise awareness of the problem, promote the use of accessors, and break early in case the changes to a trait is potentially problematic for a class using it. This results in the following rules: [...]"
This is purely a matter of consistence with the dynamic nature of PHP.
We allow explicit naming of the properties, and that is all.
There is no handling of any semantics, there is no conflict resolution, there is no state merging, splitting or what ever you would need to have a powerful enough semantics for stateful traits.
Thus, I maintain the position that our traits do not provide any handling for state.
However, they break early when a conflict/incompatibility is assumed.
That is all, nothing more.
Traits can explicitly define properties which actually
prevent the parent class from changing this.
What is a parent class with regard to a trait? That concept does not exist.
See these two examples:
http://codepad.viper-7.com/fqD91I and
http://codepad.viper-7.com/DFDpqM
That is not a parent class, that is a using class or user, not a parent.
So there is absolutely 100% support
for state here. I could understand if it was a dynamic property or
the like, but this is really state.
As I outlined above, I disagree with that position. It does not mean that traits handle/have state!
That only means that we cater for the nature of PHP.
Perhaps the difference is academic, but it is quite important for me.
We cannot do without that breaking early property, because being conservative here seems the best option we have at the moment.
Bailing out in case of possible conflicts is the only way to avoid subtle bugs.
But the semantics of the properties are exactly as if they are defined in the class, which means that there is no notion of state what so ever associate with the trait. It is only realized in the final class, and the corresponding object.
The trait merely declares it.
The relevant part of the RFC: https://wiki.php.net/rfc/horizontalreuse#conflict_resolution
We abandoned the idea of an explicit exclude operator long ago. (I think it was never in the PHP SVN.)
Thus, the situation you describe cannot occur in the sense that a method is missing from the class.
However, you can of course provide incompatible method implementations. (Which does not make traits any different from any other way to implement an interface.)The situation can absolutely happen simply with the conflict
resolution (as you say).
I think, I do not understand what you are getting at.
The problem that cannot occur is that you get a runtime error because a method is completely missing from a class.
You can neither exclude nor rename a method. We do not have exclude, and we only got aliasing. Neither of these mechanisms will lead to a case where a method is completely missing from the eventual class.
Implementation without any contract.
Yes.
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 folks,
With a 5.4 release right around the corner I'd like a moment of your
time to reconsider this issue [1].Just curious why it died on the table if several folks saw value in
it, including Stephan who I gather is the primary architect of traits.The dispute regarding the feature was the merit of a 'compile time'
check vs placing the burden on the developer to ensure all methods in
a trait are provided by the class in which it is used. In an earlier
conversation about traits Stefan said this:"Yes, Traits are meant to be compile-time only, they are not used to
introduce typing relationships."Which makes me even more comfortable about the merit of another
compile time check. As I argued in the original thread, the feature
is merely an embellishment atop the already supported use of the
'abstract'
I think it's up to Stefan's experience & wisdom.
To really cover all use cases, it might be worth looking into using a require{} block as a way to list the requirements for the composing class (and get rid of the abstract syntax).
e.g.
trait Bar {
require {
function foo();
private $_prop;
implements Iterator;
}
function moveNext() {
$this->foo();
$this->_prop = 'hello';
$this->next();
}
}
class Foo implements Iterator {
use Bar; //ok
function foo() {}
private $_prop;
function next()
.... iterator methods
}
class FooFailure {
use Bar; // E_FATAL: composing requirements not met (implements Iterator, function foo())
private $_prop;
}
Traits is already an advanced concept, a conservative approach would be to remove the 'abstract' check and see how people end up using traits.
If there's enough use cases (not foo/bar examples) out there that need compile time checks, add the proper syntax for it.