Hi,
during last six months I've studied a language construct called Traits.
It is a construct to allow fine-grained code reuse and in my opinon
this would be a nice feature for PHP, which I did like to propose here.
The following RFC deals with the questions what Traits are, how they are
used, why they are usefull and how they do look like in PHP.
A patch implementing this new language construct is available, too.
Thank you for your attention and I'm looking forward to hear your comments
:)
Kind Regards
Stefan
Request for Comments: Traits for PHP
:HTML: http://www.stefan-marr.de/artikel/rfc-traits-for-php.html
... contents::
This RFC will discuss at first the motivation for Traits describing the
rationals
and presenting a short real world use case. The main part will describe the
concept of Traits in detail using the syntax for Traits implemented in a
patch
which is part of this proposal. In the end, the URL of the patch and
additional resources about Traits are given.
Introduction
============
*Traits* is a mechanism for code reuse in single inheritance languages such
as PHP. A Trait is intended to reduce some limitations of single inheritance
by enabeling a developer to reuse sets of methods freely in several
independent
classes living in different class hierarchies.
The semantics of the combination of Traits and classes is defined in a way,
which reduces complexity and avoids the typical problems associated with
multiple
inheritance and Mixins.
They are recognized for their potential in supporting better composition
and reuse, hence their integration in newer versions of languages
such as Perl 6, Squeak, Scala, Slate and Fortress.
Traits have also been ported to Java and C#.
Why do we need Traits?
----------------------
Code reuse is one of the main goals that object-oriented languages try to
achieve
with inheritance. Unfortunately, single inheritance often forces the
developer
to take a decision in favor for either code reuse *or* conceptual clean
class
hierarchies. To achieve code reuse, methods have either to be duplicated or
to be moved near the root of the class hierarchy, but this hampers
understandability and maintainability of code.
To circumvent this problems multiple inheritance and Mixins have been
invented.
But both of them are complex and hard to understand. PHP5
has been explicitly designed with the clean and successful model of Java in
mind: single inheritance, but multiple interfaces. This decision has been
taken
to avoid the known problems of for example C++.
Traits have been invented to avoid those problems, too. They enable designer
to build
conceptually clean class hierarchies without the need to consider code reuse
or
complexity problems, but focusing on the real problem domain and
maintainability
instead.
Traits: A Mechanism for Fine-grained Reuse
==========================================
A Trait is a unit of reuse much like a class, but only intended to group
functionality in a fine-grained and consistent way. It is not possible to
instantiate a Trait on its own. It is an addition to traditional inheritance
and enables horizontal composition of behavior.
The following code illustrates the current implementation of an extended
version of the PHP reflection API which provides detailed access to doc
comment
blocks.
ReflectionMethod and ReflectionFunction are classes from the reflection API
and
have to be extended with exactly the same code. In some situations it
would be possible to add a common base class, but in this case it is
impossible, because the extended classes are not under our control, i.e.,
they
are implemented in third party code or even in C, like it is the case here.
::
<?php
class ezcReflectionMethod extends ReflectionMethod {
/* ... */
function getReturnType() { /*1*/ }
function getReturnDescription() { /*2*/ }
/* ... */
}
class ezcReflectionFunction extends ReflectionFunction {
/* ... */
function getReturnType() { /*1*/ }
function getReturnDescription() { /*2*/ }
/* ... */
}
?>
With Traits it is possible to refactor this redundant code out.
::
<?php
trait ezcReflectionReturnInfo {
function getReturnType() { /*1*/ }
function getReturnDescription() { /*2*/ }
}
class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
/* ... */
}
class ezcReflectionFunction extends ReflectionFunction {
use ezcReflectionReturnInfo;
/* ... */
}
?>
This is just a small example of what Traits are useful for.
The next sections will discuss on more advanced techniques and describe how
the
current implementation of Traits for PHP works.
The Flattening Property
-----------------------
As already mentioned, multiple inheritance and Mixins are complex
mechanisms.
Traits are an alternative which have been designed to impose no
additional semantics on classes. Traits are only entities of the literal
code
written in your source files. There is no notion about Traits at runtime.
They are used to group methods and reuse code and are totally flattened
into the classes composed from them. It is almost like a language supported
and
failsafe copy'n'paste mechanism to build classes.
Precedence Order
""""""""""""""""
Flattening is achieved by applying some simple rules on the composition
mechanism. Instead of implementing a fancy and awkward algorithm to solve
problems, the entire control about the composition is left in the hand of
the
developer and fits nicely into the known inheritance model of PHP.
The following examples illustrate the semantics of Traits and their relation
to methods defined in classes.
::
<?php
class Base {
public function sayHello() {
echo 'Hello ';
}
}
trait SayWorld {
public function sayHello() {
parent::sayHello();
echo 'World!';
}
}
class MyHelloWorld extends Base {
use SayWorld;
}
$o = new MyHelloWorld();
$o->sayHello(); // echos Hello World!
?>
As shown in the above code, an inherited method from a base class is
overridden
by the method inserted into ``MyHelloWorld`` from the ``SayWorld`` Trait.
The behavior is the same for methods defined in the ``MyHelloWorld`` class.
The precedence order is that methods from the current class override Trait
methods,
which in return override methods from the base class.
::
<?php
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
class TheWorldIsNotEnough {
use HelloWorld;
public function sayHello() {
echo 'Hello Universe!';
}
}
$o = new TheWorldIsNotEnough();
$o->sayHello(); // echos Hello Universe!
?>
Multiple Traits Usage
"""""""""""""""""""""
To keep things simple in the beginning, there has only one Trait being used
at
a time, but obviously a class could use multiple Traits at the same time.
::
<?php
trait Hello {
public function sayHello() {
echo 'Hello ';
}
}
trait World {
public function sayWorld() {
echo ' World';
}
}
class MyHelloWorld {
use Hello;
use World;
public function sayExclamationMark() {
echo '!';
}
}
$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
// Results eventually in: Hello World!
Conflict Resolution
"""""""""""""""""""
But now a problem will occur, if different Traits provide methods with the
same name.
::
<?php
trait A {
public function smallTalk() {
echo 'a';
}
public function bigTalk() {
echo 'A';
}
}
trait B {
public function smallTalk() {
echo 'b';
}
public function bigTalk() {
echo 'B';
}
}
?>
Both classes have to be used in a class named ``Talker``. Multiple
inheritance
and Mixins define an algorithm to resolve this conflict. Traits don't.
Conflicts
aren't solved implicitly by any kind of precedence. Instead, to avoid
implicit
complexity, the developer has full control over class composition.
::
<?php
class Talker {
use A;
use B;
}
?>
In case of the above definition of ``Talker``, PHP will show a notice that
there
have been conflicts and name the methods ``smallTalk()`` and ``bigTalk()``
as the reason of this conflict. Therefore, neither of the given
implementations
will be available in the class.
Instead, the developer can exactly define which methods are used and how the
conflict is resolved.
::
<?php
class Talker {
use A { !smallTalk }
use B { !bigTalk }
}
?>
This definition will result in the exclusion of ``smallTalk()`` from the
Trait A
and ``bigTalk()`` from Trait B. Therefore, the resulting class Talker would
echo ``'b'`` for ``smallTalk()`` and ``'A'`` for ``bigTalk().``
But simple exclusion of methods is not the best choice for all situations.
::
<?php
class Talker {
use A { !smallTalk }
use B { !bigTalk, talk => bigTalk }
}
?>
Beside the exclusion an alias operation is available, too. This alias
operation, notated like a ``key => value`` for arrays even has a similar
semantics like the array notation. The definition ``talk => bigTalk``
lets the new name ``talk`` refer to the method body of ``bigTalk``
of the Trait B. The resulting ``Talker`` class will consist of following
three methods:
* ``bigTalk() { echo 'A'; }``
* ``smallTalk() { echo 'b'; }``
* ``talk() { echo 'B'; }``
Since the alias operation adds a new name to an existing method body, the
``bigTalk`` method still has to be excluded. Otherwise, PHP would print
a notice that two methods from Traits have a conflict and are excluded.
Aliasing is not renaming and references in methods to a given method name
aren't changed either. On the first look this may sound strange, but it
provides the opportunity to build Traits and even hierarchies of Traits
which
fit together very well.
Traits Composed from Traits
"""""""""""""""""""""""""""
Not explicitly mentioned jet, but implied by the flattening property is the
composition of Traits from Traits.
Since Traits are fully flattened away at compile time it is possible to use
Traits to compose Traits without any additional impact on the semantics.
The following code illustrates this::
<?php
trait Hello {
public function sayHello() {
echo 'Hello ';
}
}
trait World {
public function sayWorld() {
echo 'World!';
}
}
trait HelloWorld {
use Hello;
use World;
}
class MyHelloWorld {
use HelloWorld;
}
$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
// Results eventually in: Hello World!
?>
Traits itself can take part in arbitrary compositions, but Traits are not
part
of the inheritance tree i.e., it is not possible to inherit from a Trait to
avoid confusion and misuse of Traits.
Traits Semantics Summarized
---------------------------
1. Traits do not add runtime semantics, they only take part in the process
of
building a class.
2. Traits integrate into the precedence order of method overriding.
3. To avoid complexity, conflicts between Trait methods have to be solved
explicitly. Otherwise a notice is generated and the conflicting methods
are excluded.
4. Specific methods can be excluded from a composition to handle conflicts.
5. Aliases can be defined for methods to enable reuse of conflicting
methods.
6. Traits can be composed from Traits.
As a result of this semantics, at runtime, classes build using Traits are
not distinguishable
from classes not using Traits but traditional code duplication instead.
Semantics of ``parent`` and ``$this`` hasn't changed, too. Used in a Trait
method, they behave exactly the same as if the method has been defined in
the
class directly.
Visibility and Interfaces
-------------------------
Visibility modifiers have not been discussed so far. Since Traits are meant
as
units of reuse, modifiers should be changeable easily in the context of a
composed class. Therefore, the aliasing operation is able to change the
visibility modifier of a method, too.
::
<?php
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
class MyClass1 {
use HelloWorld { protected sayHello }
}
class MyClass2 {
use HelloWorld { private doHelloWorld => sayHello }
}
?>
The abstract and final modifiers are supported, too. Abstract methods in
Traits are commonly used to define requirements a Trait needs to have
implemented by a class. The static modifier is not supported, because it
would
change the methods semantics and references to ``$this`` would break.
Another important feature of PHP is the support of interfaces. A often used
metaphor to describe Traits is *Traits are interfaces with implementation*.
Traits can be utilized to provide the implementation for a specific
interface
and since an interface is a guarantee that some methods are available it
fits
in the concept of Traits which provides those methods very well.
To underpin this relationship, it is possible to declare that a Trait
implements an interface like this::
<?php
interface IHello {
public function sayHello();
}
trait SayHello implements IHello {
public function sayHello() {
echo 'Hello World!';
}
}
class MyHelloWorld {
use SayHello;
}
$o = new MyHelloWorld();
var_dump($o instanceof IHello); // bool(true)
If a Trait implements an interface, this definition is propagated to the
class
using the Trait. Therefore, it is possible to provide implementations for an
interface
and reuse them in different classes.
Proposal and Patch
==================
This Request for Comments proposes a new language feature for PHP named
Traits.
Traits are a nice approach to enhance the capabilities to design conceptual
consistent class hierarchies and avoid code duplication.
The patch against the PHP_5_2 branch is available at
http://toolslave.net/snapshots/traits/traits.patch
The written test cases are located here:
http://toolslave.net/snapshots/traits/traits-tests.zip
Additionally, the SVN repo used for developing this patch is located at
https://instantsvc.svn.sourceforge.net/svnroot/instantsvc/branches/php-exten
sion/traits-php/
Alternative Syntax Proposals
----------------------------
This section collects proposals for alternative Traits syntaxes.
Traits Use Definition in the Class Header
"""""""""""""""""""""""""""""""""""""""""
Instead of declaring the Trait composition in the class body, it could be
defined in the class prologue like this::
<?php
trait Hello {
public function sayHello() {}
}
class MyHelloWorld extends BaseClass
uses Hello (hello => sayHello, !sayHello)
{
public function foo() {}
}
?>
The drawback of this notation is the implied notation of Traits as some kind
of
a type changing construct. Since they do not influence the type as their
major
feature, this notion would be misleading. Furthermore, this notation seams
to
have readability problems. Complex compositions are not as clearly arranged
as
they are with the *In-Body* notation.
A patch implementing this notation is available at:
http://toolslave.net/snapshots/traits/traits-head-syntax.patch
More about Traits
-----------------
As already mentioned, Traits is not a totally new concept, but the semantics
used in this proposal has been fully defined at first in 2003. For
scientific
information and papers about Traits
http://www.iam.unibe.ch/~scg/Research/Traits/
is a good starting point. Since it isn't a purely academic concepts, there
are
already languages supporting Traits out there. Squeak, Perl6, Scala, Slate,
Fortress and even for C#/Rotor implementation are available.
A detailed technical report has been published at
http://www.iam.unibe.ch/~scg/Archive/Papers/Duca06bTOPLASTraits.pdf
It explains Traits and gives some formal proves about the soundness of
Traits, too.
Last but not least, in this Phd thesis
http://www.iam.unibe.ch/~scg/Archive/PhD/schaerli-phd.pdf
two case studies have been publish illustrating the benefits Traits are
providing.
Fantastic. Nice RFC, nice explanation, nice design - I love it.
Can I
+1
already? :)
David
Am 18.02.2008 um 20:27 schrieb php@stefan-marr.de <php@stefan-
marr.de>:
Hi,
during last six months I've studied a language construct called
Traits.
It is a construct to allow fine-grained code reuse and in my opinon
this would be a nice feature for PHP, which I did like to propose
here.
The following RFC deals with the questions what Traits are, how they
are
used, why they are usefull and how they do look like in PHP.
A patch implementing this new language construct is available, too.Thank you for your attention and I'm looking forward to hear your
comments
:)Kind Regards
StefanRequest for Comments: Traits for PHP
:HTML: http://www.stefan-marr.de/artikel/rfc-traits-for-php.html ... contents:: This RFC will discuss at first the motivation for Traits describing the rationals and presenting a short real world use case. The main part will describe the concept of Traits in detail using the syntax for Traits implemented in a patch which is part of this proposal. In the end, the URL of the patch and additional resources about Traits are given. Introduction ============ *Traits* is a mechanism for code reuse in single inheritance languages such as PHP. A Trait is intended to reduce some limitations of single inheritance by enabeling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies. The semantics of the combination of Traits and classes is defined in a way, which reduces complexity and avoids the typical problems associated with multiple inheritance and Mixins. They are recognized for their potential in supporting better composition and reuse, hence their integration in newer versions of languages such as Perl 6, Squeak, Scala, Slate and Fortress. Traits have also been ported to Java and C#. Why do we need Traits? ---------------------- Code reuse is one of the main goals that object-oriented languages try to achieve with inheritance. Unfortunately, single inheritance often forces the developer to take a decision in favor for either code reuse *or* conceptual clean class hierarchies. To achieve code reuse, methods have either to be duplicated or to be moved near the root of the class hierarchy, but this hampers understandability and maintainability of code. To circumvent this problems multiple inheritance and Mixins have been invented. But both of them are complex and hard to understand. PHP5 has been explicitly designed with the clean and successful model of Java in mind: single inheritance, but multiple interfaces. This decision has been taken to avoid the known problems of for example C++. Traits have been invented to avoid those problems, too. They enable designer to build conceptually clean class hierarchies without the need to consider code reuse or complexity problems, but focusing on the real problem domain and maintainability instead. Traits: A Mechanism for Fine-grained Reuse ========================================== A Trait is a unit of reuse much like a class, but only intended to group functionality in a fine-grained and consistent way. It is not possible to instantiate a Trait on its own. It is an addition to traditional inheritance and enables horizontal composition of behavior. The following code illustrates the current implementation of an extended version of the PHP reflection API which provides detailed access to doc comment blocks. ReflectionMethod and ReflectionFunction are classes from the reflection API and have to be extended with exactly the same code. In some situations it would be possible to add a common base class, but in this case it is impossible, because the extended classes are not under our control, i.e., they are implemented in third party code or even in C, like it is the case here. :: <?php class ezcReflectionMethod extends ReflectionMethod { /* ... */ function getReturnType() { /*1*/ } function getReturnDescription() { /*2*/ } /* ... */ } class ezcReflectionFunction extends ReflectionFunction { /* ... */ function getReturnType() { /*1*/ } function getReturnDescription() { /*2*/ } /* ... */ } ?> With Traits it is possible to refactor this redundant code out. :: <?php trait ezcReflectionReturnInfo { function getReturnType() { /*1*/ } function getReturnDescription() { /*2*/ } } class ezcReflectionMethod extends ReflectionMethod { use ezcReflectionReturnInfo; /* ... */ } class ezcReflectionFunction extends ReflectionFunction { use ezcReflectionReturnInfo; /* ... */ } ?> This is just a small example of what Traits are useful for. The next sections will discuss on more advanced techniques and describe how the current implementation of Traits for PHP works. The Flattening Property ----------------------- As already mentioned, multiple inheritance and Mixins are complex mechanisms. Traits are an alternative which have been designed to impose no additional semantics on classes. Traits are only entities of the literal code written in your source files. There is no notion about Traits at runtime. They are used to group methods and reuse code and are totally flattened into the classes composed from them. It is almost like a language supported and failsafe copy'n'paste mechanism to build classes. Precedence Order """""""""""""""" Flattening is achieved by applying some simple rules on the composition mechanism. Instead of implementing a fancy and awkward algorithm to solve problems, the entire control about the composition is left in the hand of the developer and fits nicely into the known inheritance model of PHP. The following examples illustrate the semantics of Traits and their relation to methods defined in classes. :: <?php class Base { public function sayHello() { echo 'Hello '; } } trait SayWorld { public function sayHello() { parent::sayHello(); echo 'World!'; } } class MyHelloWorld extends Base { use SayWorld; } $o = new MyHelloWorld(); $o->sayHello(); // echos Hello World! ?> As shown in the above code, an inherited method from a base class is overridden by the method inserted into ``MyHelloWorld`` from the ``SayWorld`` Trait. The behavior is the same for methods defined in the ``MyHelloWorld`` class. The precedence order is that methods from the current class override Trait methods, which in return override methods from the base class. :: <?php trait HelloWorld { public function sayHello() { echo 'Hello World!'; } } class TheWorldIsNotEnough { use HelloWorld; public function sayHello() { echo 'Hello Universe!'; } } $o = new TheWorldIsNotEnough(); $o->sayHello(); // echos Hello Universe! ?> Multiple Traits Usage """"""""""""""""""""" To keep things simple in the beginning, there has only one Trait being used at a time, but obviously a class could use multiple Traits at the same time. :: <?php trait Hello { public function sayHello() { echo 'Hello '; } } trait World { public function sayWorld() { echo ' World'; } } class MyHelloWorld { use Hello; use World; public function sayExclamationMark() { echo '!'; } } $o = new MyHelloWorld(); $o->sayHello(); $o->sayWorld(); $o->sayExclamationMark(); // Results eventually in: Hello World! Conflict Resolution """"""""""""""""""" But now a problem will occur, if different Traits provide methods with the same name. :: <?php trait A { public function smallTalk() { echo 'a'; } public function bigTalk() { echo 'A'; } } trait B { public function smallTalk() { echo 'b'; } public function bigTalk() { echo 'B'; } } ?> Both classes have to be used in a class named ``Talker``. Multiple inheritance and Mixins define an algorithm to resolve this conflict. Traits don't. Conflicts aren't solved implicitly by any kind of precedence. Instead, to avoid implicit complexity, the developer has full control over class composition. :: <?php class Talker { use A; use B; } ?> In case of the above definition of ``Talker``, PHP will show a notice that there have been conflicts and name the methods ``smallTalk()`` and ``bigTalk()`` as the reason of this conflict. Therefore, neither of the given implementations will be available in the class. Instead, the developer can exactly define which methods are used and how the conflict is resolved. :: <?php class Talker { use A { !smallTalk } use B { !bigTalk } } ?> This definition will result in the exclusion of ``smallTalk()`` from the Trait A and ``bigTalk()`` from Trait B. Therefore, the resulting class Talker would echo ``'b'`` for ``smallTalk()`` and ``'A'`` for ``bigTalk().`` But simple exclusion of methods is not the best choice for all situations. :: <?php class Talker { use A { !smallTalk } use B { !bigTalk, talk => bigTalk } } ?> Beside the exclusion an alias operation is available, too. This alias operation, notated like a ``key => value`` for arrays even has a similar semantics like the array notation. The definition ``talk => bigTalk`` lets the new name ``talk`` refer to the method body of ``bigTalk`` of the Trait B. The resulting ``Talker`` class will consist of following three methods: * ``bigTalk() { echo 'A'; }`` * ``smallTalk() { echo 'b'; }`` * ``talk() { echo 'B'; }`` Since the alias operation adds a new name to an existing method body, the ``bigTalk`` method still has to be excluded. Otherwise, PHP would print a notice that two methods from Traits have a conflict and are excluded. Aliasing is not renaming and references in methods to a given method name aren't changed either. On the first look this may sound strange, but it provides the opportunity to build Traits and even hierarchies of Traits which fit together very well. Traits Composed from Traits """"""""""""""""""""""""""" Not explicitly mentioned jet, but implied by the flattening property is the composition of Traits from Traits. Since Traits are fully flattened away at compile time it is possible to use Traits to compose Traits without any additional impact on the semantics. The following code illustrates this:: <?php trait Hello { public function sayHello() { echo 'Hello '; } } trait World { public function sayWorld() { echo 'World!'; } } trait HelloWorld { use Hello; use World; } class MyHelloWorld { use HelloWorld; } $o = new MyHelloWorld(); $o->sayHello(); $o->sayWorld(); // Results eventually in: Hello World! ?> Traits itself can take part in arbitrary compositions, but Traits are not part of the inheritance tree i.e., it is not possible to inherit from a Trait to avoid confusion and misuse of Traits. Traits Semantics Summarized --------------------------- 1. Traits do not add runtime semantics, they only take part in the process of building a class. 2. Traits integrate into the precedence order of method overriding. 3. To avoid complexity, conflicts between Trait methods have to be solved explicitly. Otherwise a notice is generated and the conflicting methods are excluded. 4. Specific methods can be excluded from a composition to handle conflicts. 5. Aliases can be defined for methods to enable reuse of conflicting methods. 6. Traits can be composed from Traits. As a result of this semantics, at runtime, classes build using Traits are not distinguishable from classes not using Traits but traditional code duplication instead. Semantics of ``parent`` and ``$this`` hasn't changed, too. Used in a Trait method, they behave exactly the same as if the method has been defined in the class directly. Visibility and Interfaces ------------------------- Visibility modifiers have not been discussed so far. Since Traits are meant as units of reuse, modifiers should be changeable easily in the context of a composed class. Therefore, the aliasing operation is able to change the visibility modifier of a method, too. :: <?php trait HelloWorld { public function sayHello() { echo 'Hello World!'; } } class MyClass1 { use HelloWorld { protected sayHello } } class MyClass2 { use HelloWorld { private doHelloWorld => sayHello } } ?> The abstract and final modifiers are supported, too. Abstract methods in Traits are commonly used to define requirements a Trait needs to have implemented by a class. The static modifier is not supported, because it would change the methods semantics and references to ``$this`` would break. Another important feature of PHP is the support of interfaces. A often used metaphor to describe Traits is *Traits are interfaces with implementation*. Traits can be utilized to provide the implementation for a specific interface and since an interface is a guarantee that some methods are available it fits in the concept of Traits which provides those methods very well. To underpin this relationship, it is possible to declare that a Trait implements an interface like this:: <?php interface IHello { public function sayHello(); } trait SayHello implements IHello { public function sayHello() { echo 'Hello World!'; } } class MyHelloWorld { use SayHello; } $o = new MyHelloWorld(); var_dump($o instanceof IHello); // bool(true) If a Trait implements an interface, this definition is propagated to the class using the Trait. Therefore, it is possible to provide implementations for an interface and reuse them in different classes. Proposal and Patch ================== This Request for Comments proposes a new language feature for PHP named Traits. Traits are a nice approach to enhance the capabilities to design conceptual consistent class hierarchies and avoid code duplication. The patch against the PHP_5_2 branch is available at http://toolslave.net/snapshots/traits/traits.patch The written test cases are located here: http://toolslave.net/snapshots/traits/traits-tests.zip Additionally, the SVN repo used for developing this patch is located at https://instantsvc.svn.sourceforge.net/svnroot/instantsvc/branches/php-exten sion/traits-php/ Alternative Syntax Proposals ---------------------------- This section collects proposals for alternative Traits syntaxes. Traits Use Definition in the Class Header """"""""""""""""""""""""""""""""""""""""" Instead of declaring the Trait composition in the class body, it could be defined in the class prologue like this:: <?php trait Hello { public function sayHello() {} } class MyHelloWorld extends BaseClass uses Hello (hello => sayHello, !sayHello) { public function foo() {} } ?> The drawback of this notation is the implied notation of Traits as some kind of a type changing construct. Since they do not influence the type as their major feature, this notion would be misleading. Furthermore, this notation seams to have readability problems. Complex compositions are not as clearly arranged as they are with the *In-Body* notation. A patch implementing this notation is available at: http://toolslave.net/snapshots/traits/traits-head-syntax.patch More about Traits ----------------- As already mentioned, Traits is not a totally new concept, but the semantics used in this proposal has been fully defined at first in 2003. For scientific information and papers about Traits http://www.iam.unibe.ch/~scg/Research/Traits/ is a good starting point. Since it isn't a purely academic concepts, there are already languages supporting Traits out there. Squeak, Perl6, Scala, Slate, Fortress and even for C#/Rotor implementation are available. A detailed technical report has been published at http://www.iam.unibe.ch/~scg/Archive/Papers/Duca06bTOPLASTraits.pdf It explains Traits and gives some formal proves about the soundness of Traits, too. Last but not least, in this Phd thesis http://www.iam.unibe.ch/~scg/Archive/PhD/schaerli-phd.pdf two case studies have been publish illustrating the benefits Traits are providing.
<?php
trait ezcReflectionReturnInfo {
function getReturnType() { /1/ }
function getReturnDescription() { /2/ }
}class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
So it's just like an include for a re-used body of 'class' code.
Hmmmm.
Why not just allow 'include' here instead?
:-)
Forgive me if I'm missing something subtle/complex here, but I wonder
if a Trait is really the "right answer"...
Yes, the ability to add/exclude specific functions from two Traits is
"gone" with a simple 'include'... But so is the complexity of yet
another language construct...
--
Some people have a "gift" link here.
Know what I want?
I want you to buy a CD from some indie artist.
http://cdbaby.com/from/lynch
Yeah, I get a buck. So?
<?php
trait ezcReflectionReturnInfo {
function getReturnType() { /1/ }
function getReturnDescription() { /2/ }
}class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;So it's just like an include for a re-used body of 'class' code.
Hmmmm.
Why not just allow 'include' here instead?
Oh come on..
:-)
Forgive me if I'm missing something subtle/complex here, but I wonder
if a Trait is really the "right answer"...Yes, the ability to add/exclude specific functions from two Traits is
"gone" with a simple 'include'... But so is the complexity of yet
another language construct...
Please if you do not understand a concept, do not bash it or degrade it.
Read the proposal, read about traits, read the thesis, read the patch,
then if you still don't understand, give up, and if you do understand,
you can complain.
Thanks,
--
Some people have a "gift" link here.
Know what I want?
I want you to buy a CD from some indie artist.
http://cdbaby.com/from/lynch
Yeah, I get a buck. So?--
--
David Coallier,
Founder & Software Architect,
Agora Production (http://agoraproduction.com)
51.42.06.70.18
How was his comment bashing? He asked a valid question.
-Chris
<?php
trait ezcReflectionReturnInfo {
function getReturnType() { /1/ }
function getReturnDescription() { /2/ }
}class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;So it's just like an include for a re-used body of 'class' code.
Hmmmm.
Why not just allow 'include' here instead?
Oh come on..
:-)
Forgive me if I'm missing something subtle/complex here, but I wonder
if a Trait is really the "right answer"...Yes, the ability to add/exclude specific functions from two Traits is
"gone" with a simple 'include'... But so is the complexity of yet
another language construct...Please if you do not understand a concept, do not bash it or degrade it.
Read the proposal, read about traits, read the thesis, read the patch,
then if you still don't understand, give up, and if you do understand,
you can complain.Thanks,
--
Some people have a "gift" link here.
Know what I want?
I want you to buy a CD from some indie artist.
http://cdbaby.com/from/lynch
Yeah, I get a buck. So?--
--
David Coallier,
Founder & Software Architect,
Agora Production (http://agoraproduction.com)
51.42.06.70.18
Hi David,
Read the proposal, read about traits, read the thesis, read the patch,
then if you still don't understand, give up, and if you do understand,
you can complain.
If you don't understand, ask. If you still don't understand ask again.
That's more the concept of RFC and open discussions.
The goal of a RFC is to get comments (yes, the C in RFC means comments).
Let discuss this request only now and don't begin pointless bitching
session, that's counter productive.
Cheers,
In case anyone is really excited about traits and traits
don't make it in soon, I'll point out that something similar
has been available in php for years. We've been implementing
traits based on the fact that "$this" has a different meaning
in php than in other languages. In php, "$this" is the most
recent object context. So... it's easy to create a trait
class and make calls to it from your non-trait class using
"::" syntax. "->" calls within the trait class automatically
go back to the non-trait class.
This makes the purists shudder and they added a STRICT
message for it. (If you're a purist please don't bother
replying with how evil I am. I already know this makes
your stomach turn.) It obviously isn't as clean
as a well defined trait feature (or doing a re-architecture
of your app to elimate the need), but if you need traits
today, this may work for you.
- Todd
<?php
trait ezcReflectionReturnInfo {
function getReturnType() { /1/ }
function getReturnDescription() { /2/ }
}class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;So it's just like an include for a re-used body of 'class' code.
Hmmmm.
Why not just allow 'include' here instead?
Oh come on..
:-)
Forgive me if I'm missing something subtle/complex here, but I
wonder
if a Trait is really the "right answer"...Yes, the ability to add/exclude specific functions from two Traits
is
"gone" with a simple 'include'... But so is the complexity of yet
another language construct...Please if you do not understand a concept, do not bash it or degrade
it.Read the proposal, read about traits, read the thesis, read the patch,
then if you still don't understand, give up, and if you do understand,
you can complain.
I am not complaining.
I think it's a FINE proposal.
I'm suggesting an alternative solution which is simpler and easier to
implement, with less confusion for new users.
In fact, new users are often confused because the CAN'T do an include
in the middle of a class -- A rule which, to some, seems arbitrary and
illogical.
--
Some people have a "gift" link here.
Know what I want?
I want you to buy a CD from some indie artist.
http://cdbaby.com/from/lynch
Yeah, I get a buck. So?
I am not complaining.
I think it's a FINE proposal.
I'm suggesting an alternative solution which is simpler and easier to
implement, with less confusion for new users.In fact, new users are often confused because the CAN'T do an include
in the middle of a class -- A rule which, to some, seems arbitrary and
illogical.
And once they understand (if they do) why they cannot use "include",
they want to know how to do it anyway.
Traits answer this quite nicely. All be it in a way which the idea of
include would solve if it was done at parse/compile time.
So, maybe something as simple as
class_include 'file.inc';
where the name has to be a constant (parse error otherwise).
But having said that traits LOOK nice. They look clever.
Is it over complicated? If you don't know OOP, then maybe. But hell if
everything was easy we would have monkeys doing it all for us.
Traits look good. Many userland developers would use them. Many more
would need to learn how to use them. Let's add them! It certainly
would increase the amount of code reuse which MI theoretically is
supposed to do.
And then lets add accessibility to class properties without magic functions.
Richard.
Richard Quadling
Zend Certified Engineer : http://zend.com/zce.php?c=ZEND002498&r=213474731
"Standing on the shoulders of some very clever giants!"
Richard Quadling schrieb:
In fact, new users are often confused because the CAN'T do an include
in the middle of a class -- A rule which, to some, seems arbitrary and
illogical.And once they understand (if they do) why they cannot use "include",
they want to know how to do it anyway.Traits answer this quite nicely. All be it in a way which the idea of
include would solve if it was done at parse/compile time.So, maybe something as simple as
class_include 'file.inc';
We even would not need a new keyword, we could use "include" for this.
The user would not really care if this is compile time or run time as
long as the simple (and most useful) case Just Works IMHO.
But having said that traits LOOK nice. They look clever.
Is it over complicated? If you don't know OOP, then maybe. But hell if
everything was easy we would have monkeys doing it all for us.
I'm trying to suppress my reservations about new language constructs and
was quite succesful: I started to like Traits. But the discussion about
the semantics are close to making me reconsider: If you (the core
developers and language designers) cannot agree on the semantics
(stateless vs. stateful traits, etc.) I wonder how the user is going to
feel about it.
Traits look good. Many userland developers would use them. Many more
The use case examples I've seen so far haven't been all that convincing
to me but that's probably because of a different coding style.
- Chris
So it's just like an include for a re-used body of 'class' code.
Hmmmm.
Why not just allow 'include' here instead?
Well, think this would be a Mixin mechanism like in Ruby.
Forgive me if I'm missing something subtle/complex here, but I wonder
if a Trait is really the "right answer"...Yes, the ability to add/exclude specific functions from two Traits is
"gone" with a simple 'include'... But so is the complexity of yet
another language construct...
The problem here is that we will need a way to handle conflicts i.e.
methods defined in more than one of the includes.
The mixin way is to apply one include at a time. So conflicts are
solved by overriding already defined methods. But this solution is not
perfect at all since there exist situations where you can not find an
order for the includes which provides you with the needed method
implementations. Additionally you will not notice when a method in an
included hierarchy will break your semantics by overriding other
methods unexpectedly.
The Traits way is to enable the developers to decide on such conflicts
and give them the power to re-use the methods exactly needed in this
special situation, instead of doing some "magic"/linearization to
solve conflicts.
Kind Regards
Stefan
Hello Stefan,
any dynamic aspect of a class has brought us to problems in inheritance
and required us to design the objct/compile model in a way that
inheritance often is done at run time. Imo traits are a way out of this.
In fact I'd love to issue a deprecated message as soon as class is found
outside of a main block.
marcus
Tuesday, February 19, 2008, 12:36:32 AM, you wrote:
So it's just like an include for a re-used body of 'class' code.
Hmmmm.
Why not just allow 'include' here instead?
Well, think this would be a Mixin mechanism like in Ruby.
Forgive me if I'm missing something subtle/complex here, but I wonder
if a Trait is really the "right answer"...Yes, the ability to add/exclude specific functions from two Traits is
"gone" with a simple 'include'... But so is the complexity of yet
another language construct...
The problem here is that we will need a way to handle conflicts i.e.
methods defined in more than one of the includes.
The mixin way is to apply one include at a time. So conflicts are
solved by overriding already defined methods. But this solution is not
perfect at all since there exist situations where you can not find an
order for the includes which provides you with the needed method
implementations. Additionally you will not notice when a method in an
included hierarchy will break your semantics by overriding other
methods unexpectedly.
The Traits way is to enable the developers to decide on such conflicts
and give them the power to re-use the methods exactly needed in this
special situation, instead of doing some "magic"/linearization to
solve conflicts.
Kind Regards
Stefan
Best regards,
Marcus
Marcus Boerger wrote:
Hello Stefan,
any dynamic aspect of a class has brought us to problems in inheritance
and required us to design the objct/compile model in a way that
inheritance often is done at run time. Imo traits are a way out of this.
In fact I'd love to issue a deprecated message as soon as class is found
outside of a main block.
That would mean deprecating autoload as well since autoloading a class
is effectively the same as defining it outside of the main block.
-Rasmus
Hello Rasmus,
not really. We can have a table that keeps track of which class was
declared in what file. Then we could actually break down files into class
definitions and only import the requested part. In a perfect world each
class would be in its own file. A file would only create a single class and
nothing more or contain the code to generate output, probably some short,
flat procedural code. Then each of these files would get compiled and
cached. And loaded from those output generating files...
marcus
Tuesday, February 19, 2008, 4:59:19 PM, you wrote:
Marcus Boerger wrote:
Hello Stefan,
any dynamic aspect of a class has brought us to problems in inheritance
and required us to design the objct/compile model in a way that
inheritance often is done at run time. Imo traits are a way out of this.
In fact I'd love to issue a deprecated message as soon as class is found
outside of a main block.
That would mean deprecating autoload as well since autoloading a class
is effectively the same as defining it outside of the main block.
-Rasmus
Best regards,
Marcus
In fact I'd love to issue a deprecated message as soon as class is
found
outside of a main block.
err .. deprecated?
as in you want to deprecate the possibility entirely?
or you just want to hin to the user that its a bad idea .. (not sure
if even E_STRICT
would be a good fit there).
regards,
Lukas
Hello Lukas,
it doesn't work with opcode cashes. So I want at least an E_STRICT
here.
And yes I mean what I wrote. That's why I wrote it.
marcus
Tuesday, February 19, 2008, 6:40:08 PM, you wrote:
In fact I'd love to issue a deprecated message as soon as class is
found
outside of a main block.
err .. deprecated?
as in you want to deprecate the possibility entirely?
or you just want to hin to the user that its a bad idea .. (not sure
if evenE_STRICT
would be a good fit there).
regards,
Lukas
Best regards,
Marcus
<?php
trait ezcReflectionReturnInfo {
function getReturnType() { /1/ }
function getReturnDescription() { /2/ }
}class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;So it's just like an include for a re-used body of 'class' code.
Hmmmm.
Why not just allow 'include' here instead?
:-)
Because include requires the code in question to live in another file, which I
don't always want.
It sounds interesting to me, and I can definitely see the value. I think I'd
suggest having multiple included traits override each other, however, so
that:
trait A {
function foo() { echo "A"; }
}
trait B {
function foo() { echo "B"; }
}
class C {
use A;
use B;
}
$c = new C();
$c->foo(); // prints "B" since that came second.
That said, the conventional OOP mechanism to get the same result would, I
think, look something like this:
interface iface {
function foo();
function bar();
}
class I implements iface {
function foo() { ... }
function bar() { ... }
}
class A implements iface {
protected $iface;
function __construct() {
$this->iface = new I();
}
function foo() { return $this->iface->foo(); }
function bar() { return $this->iface->bar(); }
}
The class/interface method takes a little more typing and an extra function
call on the stack, but otherwise what is the advantage of Traits over this
method? (Really, I'm genuinely curious.)
You also note that this mechanism has no runtime impact. That's unfortunate,
because I'd find the ability to add methods to an object at runtime
conditionally based on some other value far more useful in my work. :-)
Especially since, as above, there seems to be a way to implement this
functionality now as-is with a little more typing, yet runtime modification
is still impossible without eval() and similar scary stuff.
Vis, I'd find the following more useful in the code I write:
trait Baby {
function crawl() { ... }
}
trait Teen {
function complain() { ... }
}
class Person {
protected $age;
function __construct($age) {
$this->age = $age;
if ($this->age <= 3) {
$this->addTrait('Baby');
}
if ($this->age >=13 && $this->age <=19) {
$this->addTrait(Teen');
}
}
}
$p[1] = new Person(19);
$p[1]->complain();
$p[2] = new Person(1);
$p[2]->crawl();
foreach ($p as $person) {
if ($p instanceof Teen) {
$person->complain();
$person->parent->ground($person);
}
}
I don't know if that's technically not feasible or technically not a Trait
anymore and therefore off topic in this thread (if it is, that's fine, let me
know and I'll shut up about it <g>), but that strikes me as more useful than
just runtime composition.
--
Larry Garfield AIM: LOLG42
larry@garfieldtech.com ICQ: 6817012
"If nature has made any one thing less susceptible than all others of
exclusive property, it is the action of the thinking power called an idea,
which an individual may exclusively possess as long as he keeps it to
himself; but the moment it is divulged, it forces itself into the possession
of every one, and the receiver cannot dispossess himself of it." -- Thomas
Jefferson
Larry Garfield wrote:
You also note that this mechanism has no runtime impact. That's unfortunate,
because I'd find the ability to add methods to an object at runtime
conditionally based on some other value far more useful in my work. :-)
Especially since, as above, there seems to be a way to implement this
functionality now as-is with a little more typing, yet runtime modification
is still impossible without eval() and similar scary stuff.
The idea here is that we want to be able to cache opcodes, classes and
functions and optimize them out of the runtime context so the executor
can skip creating classes and functions on every single request. A lot
of the traffic on this list over the past couple of months seems to
ignore this basic premise. Features such as autoload and runtime object
manipulation incur a huge performance hit in the sense that they change
something that was free before and not only add the cost of the feature
itself, but it also means the object in question now can no longer be
cached and has to be created on every single request.
This doesn't mean we can't consider such features, but people need to
also consider the performance implications.
-Rasmus
Larry Garfield wrote:
You also note that this mechanism has no runtime impact. That's
unfortunate, because I'd find the ability to add methods to an object at
runtime conditionally based on some other value far more useful in my
work. :-) Especially since, as above, there seems to be a way to
implement this functionality now as-is with a little more typing, yet
runtime modification is still impossible without eval() and similar scary
stuff.The idea here is that we want to be able to cache opcodes, classes and
functions and optimize them out of the runtime context so the executor
can skip creating classes and functions on every single request. A lot
of the traffic on this list over the past couple of months seems to
ignore this basic premise. Features such as autoload and runtime object
manipulation incur a huge performance hit in the sense that they change
something that was free before and not only add the cost of the feature
itself, but it also means the object in question now can no longer be
cached and has to be created on every single request.This doesn't mean we can't consider such features, but people need to
also consider the performance implications.-Rasmus
Indeed, those are a concern. That's why I thought the closure proposal from
late December was so promising, because it appeared (to my non-C-coding eyes)
to not have any runtime compilation, just indirect calling.
It looks like Traits as described in the original post wouldn't have a runtime
cost, but might (depending on the cost of building the code references) have
a notable compile cost. Runtime would cost more, but could they be done in a
non-terrible way? (I don't know; that's why I'm asking.) Is the lookup
table for what functions exist within a class/object easy/fast to manipulate
or slow?
--
Larry Garfield AIM: LOLG42
larry@garfieldtech.com ICQ: 6817012
"If nature has made any one thing less susceptible than all others of
exclusive property, it is the action of the thinking power called an idea,
which an individual may exclusively possess as long as he keeps it to
himself; but the moment it is divulged, it forces itself into the possession
of every one, and the receiver cannot dispossess himself of it." -- Thomas
Jefferson
Rasmus Lerdorf schrieb:
The idea here is that we want to be able to cache opcodes, classes and
functions and optimize them out of the runtime context so the executor
can skip creating classes and functions on every single request.
Thanks to their Flattening Property, there should be no notion of traits
at runtime. So once the compiler has flattened a trait into a class it
is just normal oparrays APC needs to deal with. At least as far as I can
see.
--
Sebastian Bergmann http://sebastian-bergmann.de/
GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69
Sebastian Bergmann wrote:
Rasmus Lerdorf schrieb:
The idea here is that we want to be able to cache opcodes, classes and
functions and optimize them out of the runtime context so the executor
can skip creating classes and functions on every single request.Thanks to their Flattening Property, there should be no notion of traits
at runtime. So once the compiler has flattened a trait into a class it
is just normal oparrays APC needs to deal with. At least as far as I can
see.
This was in response to the suggestion that it should be extended to do
runtime conditional modification of the classes.
-Rasmus
Rasmus Lerdorf schrieb:
This was in response to the suggestion that it should be extended to do
runtime conditional modification of the classes.
Ah, sorry.
--
Sebastian Bergmann http://sebastian-bergmann.de/
GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69
Hello Rasmus,
Tuesday, February 19, 2008, 2:45:15 AM, you wrote:
Larry Garfield wrote:
You also note that this mechanism has no runtime impact. That's unfortunate,
because I'd find the ability to add methods to an object at runtime
conditionally based on some other value far more useful in my work. :-)
Especially since, as above, there seems to be a way to implement this
functionality now as-is with a little more typing, yet runtime modification
is still impossible without eval() and similar scary stuff.
The idea here is that we want to be able to cache opcodes, classes and
functions and optimize them out of the runtime context so the executor
can skip creating classes and functions on every single request. A lot
of the traffic on this list over the past couple of months seems to
ignore this basic premise. Features such as autoload and runtime object
manipulation incur a huge performance hit in the sense that they change
something that was free before and not only add the cost of the feature
itself, but it also means the object in question now can no longer be
cached and has to be created on every single request.
This doesn't mean we can't consider such features, but people need to
also consider the performance implications.
For that reason allowing traits in favor of include inside a class body is
much better.
Best regards,
Marcus
For that reason allowing traits in favor of include inside a class
body is
much better.
Once upon a time, there was a real difference between require and
include -- where require was compile-time and include was run-time.
PERHAPS it would be interesting to consider something not unlike the
old 'require' which is compile-time include, with some kind of name
that doesn't confuse users as much as require did back in the day.
--
Some people have a "gift" link here.
Know what I want?
I want you to buy a CD from some indie artist.
http://cdbaby.com/from/lynch
Yeah, I get a buck. So?
The idea here is that we want to be able to cache opcodes, classes and
functions and optimize them out of the runtime context so the executor
can skip creating classes and functions on every single request. A
lot
of the traffic on this list over the past couple of months seems to
ignore this basic premise. Features such as autoload and runtime
object
manipulation incur a huge performance hit in the sense that they
change
something that was free before and not only add the cost of the
feature
itself, but it also means the object in question now can no longer be
cached and has to be created on every single request.This doesn't mean we can't consider such features, but people need to
also consider the performance implications.
Many users need opcode caches and performance is crucial...
Many MORE don't, really :-)
Perhaps those who need caches can devote the resources to simply not
use dynamic constructs that will break their caching mechanism, for
many of the features, rather than simply not letting the features
exist for those who don't need caching that badly.
Whether dynamic method construction or trait addition is such a
feature is beyond me, of course, but it's worth considering whether
the feature will make it ALWAYS slower, or only slower if one is silly
enough to use it when one shouldn't.
--
Some people have a "gift" link here.
Know what I want?
I want you to buy a CD from some indie artist.
http://cdbaby.com/from/lynch
Yeah, I get a buck. So?
Richard Lynch wrote:
The idea here is that we want to be able to cache opcodes, classes and
functions and optimize them out of the runtime context so the executor
can skip creating classes and functions on every single request. A
lot
of the traffic on this list over the past couple of months seems to
ignore this basic premise. Features such as autoload and runtime
object
manipulation incur a huge performance hit in the sense that they
change
something that was free before and not only add the cost of the
feature
itself, but it also means the object in question now can no longer be
cached and has to be created on every single request.This doesn't mean we can't consider such features, but people need to
also consider the performance implications.Many users need opcode caches and performance is crucial...
Many MORE don't, really :-)
That's a bit like saying that most people don't need more money. While
probably true, it certainly doesn't hurt to have much lower page
latencies. Similarly, most people don't need to scale either, but that
doesn't mean we should suddenly start tearing down our perfect shared
nothing sandbox. If we design for good performance and scaling, the
trickledown effect means that even horribly-written applications will be
usable while the people who care about performance can get outstanding
performance out of it.
-Rasmus
Hi Larry,
It sounds interesting to me, and I can definitely see the value. I think I'd
suggest having multiple included traits override each other, however, so
that:trait A {
function foo() { echo "A"; }
function bar() { echo "A"; }
}
trait B {
function foo() { echo "B"; }
function bar() { echo "B"; }
}
class C {
use A;
use B;
}
$c = new C();
$c->foo(); // prints "B" since that came second.
$c->bar();
Lets suppose you will need here the "A" for some domain specific
reason. This is not possible with this mixin semantics, with traits it
is.
That said, the conventional OOP mechanism to get the same result would, I
think, look something like this:interface iface {
function foo();
function bar();
}class I implements iface {
function foo() { ... }
function bar() { ... }
}class A implements iface {
protected $iface;function __construct() {
$this->iface = new I();
}function foo() { return $this->iface->foo(); }
function bar() { return $this->iface->bar(); }
}The class/interface method takes a little more typing and an extra function
call on the stack, but otherwise what is the advantage of Traits over this
method? (Really, I'm genuinely curious.)
Yes, it is the typical delegation pattern. Often used to achieve code
reuse without the need to inherited and clutter up interfaces with
unnecessary stuff.
The advantage of traits here is the preservation of the encapsulation
property of your classes. In many situations you would need to
propagate some state to the delegate which is not a nice thing in all
situations. Instead it would be more elegant to have a method in the
class to preserve encapsulation. The method could be provided by copy
and past, or for consistency and conceptual reasons, by a trait :)
You also note that this mechanism has no runtime impact. That's unfortunate,
because I'd find the ability to add methods to an object at runtime
conditionally based on some other value far more useful in my work. :-)
Especially since, as above, there seems to be a way to implement this
functionality now as-is with a little more typing, yet runtime modification
is still impossible without eval() and similar scary stuff.Vis, I'd find the following more useful in the code I write:
trait Baby {
function crawl() { ... }
}trait Teen {
function complain() { ... }
}class Person {
protected $age;
function __construct($age) {
$this->age = $age;
if ($this->age <= 3) {
$this->addTrait('Baby');
}
if ($this->age >=13 && $this->age <=19) {
$this->addTrait(Teen');
}
}
}$p[1] = new Person(19);
$p[1]->complain();$p[2] = new Person(1);
$p[2]->crawl();foreach ($p as $person) {
if ($p instanceof Teen) {
$person->complain();
$person->parent->ground($person);
}
}I don't know if that's technically not feasible or technically not a Trait
anymore and therefore off topic in this thread (if it is, that's fine, let me
know and I'll shut up about it <g>), but that strikes me as more useful than
just runtime composition.
This is not Traits anymore, yes :) and as Rasmus suggests there would
be definitely some problems.
But you might be interested in Context-oriented Programming, it tries
to solve such crosscutting concerns you've described in an dynamic
way.
Unfortunately I've no code example at hand but you could have a lock
at http://www.swa.hpi.uni-potsdam.de/cop/
ContextR for Ruby is very promising to solve such problems.
I've had a thought on this for PHP, too. But think there would be many
people against something like "dynamic behavior" even if this looks
like an oxymoron ;)
Kind Regards
Stefan
Larry Garfield wrote:
Why not just allow 'include' here instead?
Because include requires the code in question to live in another file, which I
don't always want.
I'm with Richard Lynch here: Simply allow inclusion. No new language
concept, instead an existing one is made more orthogonal which is A
Desirable Thing(tm) IMHO.
About having it in the same file: You then need to also have the two
classes using the Trait to live in the same file (or include_once that
file) which is something I'm trying to avoid.
I've gotten fond of one class per file for the sake of an autoloader
anyway so I don't think separating the common code into a separate file
is a big disadvantage.
The part about being able to do partial Trait use seems hackish and
somewhat complicated to me so I'd rather not have it in the language. It
feels a little bit like being able to partially implement an Interface:
I think a class should either have a Trait or not. If we're using
include to emulate Traits then the same could be accomplished by
separating the parts into files (if really desired).
-1 for Traits as proposed
+1 for allowing include in class definitions
- Chris
<?php
trait ezcReflectionReturnInfo {
function getReturnType() { /1/ }
function getReturnDescription() { /2/ }
}class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;So it's just like an include for a re-used body of 'class' code.
Hmmmm.
Why not just allow 'include' here instead?
Because include happens during runtime, not compile time.
regards,
Derick
php@stefan-marr.de schrieb:
The following RFC deals with the questions what Traits are, how they are
used, why they are usefull and how they do look like in PHP.
Thank you, Stefan, for your thorough RFC.
A patch implementing this new language construct is available, too.
I tested an earlier version of the patch back in December when I read
(and commented on) the RFC.
I like the idea of Traits and your implementation for PHP and would just
love to see this make its way into PHP.
--
Sebastian Bergmann http://sebastian-bergmann.de/
GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69
Hi,
during last six months I've studied a language construct called Traits.
It is a construct to allow fine-grained code reuse and in my opinon
this would be a nice feature for PHP, which I did like to propose here.
The following RFC deals with the questions what Traits are, how they are
used, why they are usefull and how they do look like in PHP.
A patch implementing this new language construct is available, too.Thank you for your attention and I'm looking forward to hear your comments
:)Kind Regards
StefanRequest for Comments: Traits for PHP
:HTML: http://www.stefan-marr.de/artikel/rfc-traits-for-php.html ... contents:: This RFC will discuss at first the motivation for Traits describing the rationals and presenting a short real world use case. The main part will describe the concept of Traits in detail using the syntax for Traits implemented in a patch which is part of this proposal. In the end, the URL of the patch and additional resources about Traits are given. Introduction ============ *Traits* is a mechanism for code reuse in single inheritance languages such as PHP. A Trait is intended to reduce some limitations of single inheritance by enabeling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies. The semantics of the combination of Traits and classes is defined in a way, which reduces complexity and avoids the typical problems associated with multiple inheritance and Mixins. They are recognized for their potential in supporting better composition and reuse, hence their integration in newer versions of languages such as Perl 6, Squeak, Scala, Slate and Fortress. Traits have also been ported to Java and C#. Why do we need Traits? ---------------------- Code reuse is one of the main goals that object-oriented languages try to achieve with inheritance. Unfortunately, single inheritance often forces the developer to take a decision in favor for either code reuse *or* conceptual clean class hierarchies. To achieve code reuse, methods have either to be duplicated or to be moved near the root of the class hierarchy, but this hampers understandability and maintainability of code. To circumvent this problems multiple inheritance and Mixins have been invented. But both of them are complex and hard to understand. PHP5 has been explicitly designed with the clean and successful model of Java in mind: single inheritance, but multiple interfaces. This decision has been taken to avoid the known problems of for example C++. Traits have been invented to avoid those problems, too. They enable designer to build conceptually clean class hierarchies without the need to consider code reuse or complexity problems, but focusing on the real problem domain and maintainability instead. Traits: A Mechanism for Fine-grained Reuse ========================================== A Trait is a unit of reuse much like a class, but only intended to group functionality in a fine-grained and consistent way. It is not possible to instantiate a Trait on its own. It is an addition to traditional inheritance and enables horizontal composition of behavior. The following code illustrates the current implementation of an extended version of the PHP reflection API which provides detailed access to doc comment blocks. ReflectionMethod and ReflectionFunction are classes from the reflection API and have to be extended with exactly the same code. In some situations it would be possible to add a common base class, but in this case it is impossible, because the extended classes are not under our control, i.e., they are implemented in third party code or even in C, like it is the case here. :: <?php class ezcReflectionMethod extends ReflectionMethod { /* ... */ function getReturnType() { /*1*/ } function getReturnDescription() { /*2*/ } /* ... */ } class ezcReflectionFunction extends ReflectionFunction { /* ... */ function getReturnType() { /*1*/ } function getReturnDescription() { /*2*/ } /* ... */ } ?> With Traits it is possible to refactor this redundant code out. :: <?php trait ezcReflectionReturnInfo { function getReturnType() { /*1*/ } function getReturnDescription() { /*2*/ } } class ezcReflectionMethod extends ReflectionMethod { use ezcReflectionReturnInfo; /* ... */ } class ezcReflectionFunction extends ReflectionFunction { use ezcReflectionReturnInfo; /* ... */ } ?> This is just a small example of what Traits are useful for. The next sections will discuss on more advanced techniques and describe how the current implementation of Traits for PHP works. The Flattening Property ----------------------- As already mentioned, multiple inheritance and Mixins are complex mechanisms. Traits are an alternative which have been designed to impose no additional semantics on classes. Traits are only entities of the literal code written in your source files. There is no notion about Traits at runtime. They are used to group methods and reuse code and are totally flattened into the classes composed from them. It is almost like a language supported and failsafe copy'n'paste mechanism to build classes. Precedence Order """""""""""""""" Flattening is achieved by applying some simple rules on the composition mechanism. Instead of implementing a fancy and awkward algorithm to solve problems, the entire control about the composition is left in the hand of the developer and fits nicely into the known inheritance model of PHP. The following examples illustrate the semantics of Traits and their relation to methods defined in classes. :: <?php class Base { public function sayHello() { echo 'Hello '; } } trait SayWorld { public function sayHello() { parent::sayHello(); echo 'World!'; } } class MyHelloWorld extends Base { use SayWorld; } $o = new MyHelloWorld(); $o->sayHello(); // echos Hello World! ?> As shown in the above code, an inherited method from a base class is overridden by the method inserted into ``MyHelloWorld`` from the ``SayWorld`` Trait. The behavior is the same for methods defined in the ``MyHelloWorld`` class. The precedence order is that methods from the current class override Trait methods, which in return override methods from the base class. :: <?php trait HelloWorld { public function sayHello() { echo 'Hello World!'; } } class TheWorldIsNotEnough { use HelloWorld; public function sayHello() { echo 'Hello Universe!'; } } $o = new TheWorldIsNotEnough(); $o->sayHello(); // echos Hello Universe! ?> Multiple Traits Usage """"""""""""""""""""" To keep things simple in the beginning, there has only one Trait being used at a time, but obviously a class could use multiple Traits at the same time. :: <?php trait Hello { public function sayHello() { echo 'Hello '; } } trait World { public function sayWorld() { echo ' World'; } } class MyHelloWorld { use Hello; use World; public function sayExclamationMark() { echo '!'; } } $o = new MyHelloWorld(); $o->sayHello(); $o->sayWorld(); $o->sayExclamationMark(); // Results eventually in: Hello World! Conflict Resolution """"""""""""""""""" But now a problem will occur, if different Traits provide methods with the same name. :: <?php trait A { public function smallTalk() { echo 'a'; } public function bigTalk() { echo 'A'; } } trait B { public function smallTalk() { echo 'b'; } public function bigTalk() { echo 'B'; } } ?> Both classes have to be used in a class named ``Talker``. Multiple inheritance and Mixins define an algorithm to resolve this conflict. Traits don't. Conflicts aren't solved implicitly by any kind of precedence. Instead, to avoid implicit complexity, the developer has full control over class composition. :: <?php class Talker { use A; use B; } ?> In case of the above definition of ``Talker``, PHP will show a notice that there have been conflicts and name the methods ``smallTalk()`` and ``bigTalk()`` as the reason of this conflict. Therefore, neither of the given implementations will be available in the class. Instead, the developer can exactly define which methods are used and how the conflict is resolved. :: <?php class Talker { use A { !smallTalk } use B { !bigTalk } } ?> This definition will result in the exclusion of ``smallTalk()`` from the Trait A and ``bigTalk()`` from Trait B. Therefore, the resulting class Talker would echo ``'b'`` for ``smallTalk()`` and ``'A'`` for ``bigTalk().`` But simple exclusion of methods is not the best choice for all situations. :: <?php class Talker { use A { !smallTalk } use B { !bigTalk, talk => bigTalk } } ?> Beside the exclusion an alias operation is available, too. This alias operation, notated like a ``key => value`` for arrays even has a similar semantics like the array notation. The definition ``talk => bigTalk`` lets the new name ``talk`` refer to the method body of ``bigTalk`` of the Trait B. The resulting ``Talker`` class will consist of following three methods: * ``bigTalk() { echo 'A'; }`` * ``smallTalk() { echo 'b'; }`` * ``talk() { echo 'B'; }`` Since the alias operation adds a new name to an existing method body, the ``bigTalk`` method still has to be excluded. Otherwise, PHP would print a notice that two methods from Traits have a conflict and are excluded. Aliasing is not renaming and references in methods to a given method name aren't changed either. On the first look this may sound strange, but it provides the opportunity to build Traits and even hierarchies of Traits which fit together very well. Traits Composed from Traits """"""""""""""""""""""""""" Not explicitly mentioned jet, but implied by the flattening property is the composition of Traits from Traits. Since Traits are fully flattened away at compile time it is possible to use Traits to compose Traits without any additional impact on the semantics. The following code illustrates this:: <?php trait Hello { public function sayHello() { echo 'Hello '; } } trait World { public function sayWorld() { echo 'World!'; } } trait HelloWorld { use Hello; use World; } class MyHelloWorld { use HelloWorld; } $o = new MyHelloWorld(); $o->sayHello(); $o->sayWorld(); // Results eventually in: Hello World! ?> Traits itself can take part in arbitrary compositions, but Traits are not part of the inheritance tree i.e., it is not possible to inherit from a Trait to avoid confusion and misuse of Traits. Traits Semantics Summarized --------------------------- 1. Traits do not add runtime semantics, they only take part in the process of building a class. 2. Traits integrate into the precedence order of method overriding. 3. To avoid complexity, conflicts between Trait methods have to be solved explicitly. Otherwise a notice is generated and the conflicting methods are excluded. 4. Specific methods can be excluded from a composition to handle conflicts. 5. Aliases can be defined for methods to enable reuse of conflicting methods. 6. Traits can be composed from Traits. As a result of this semantics, at runtime, classes build using Traits are not distinguishable from classes not using Traits but traditional code duplication instead. Semantics of ``parent`` and ``$this`` hasn't changed, too. Used in a Trait method, they behave exactly the same as if the method has been defined in the class directly. Visibility and Interfaces ------------------------- Visibility modifiers have not been discussed so far. Since Traits are meant as units of reuse, modifiers should be changeable easily in the context of a composed class. Therefore, the aliasing operation is able to change the visibility modifier of a method, too. :: <?php trait HelloWorld { public function sayHello() { echo 'Hello World!'; } } class MyClass1 { use HelloWorld { protected sayHello } } class MyClass2 { use HelloWorld { private doHelloWorld => sayHello } } ?> The abstract and final modifiers are supported, too. Abstract methods in Traits are commonly used to define requirements a Trait needs to have implemented by a class. The static modifier is not supported, because it would change the methods semantics and references to ``$this`` would break. Another important feature of PHP is the support of interfaces. A often used metaphor to describe Traits is *Traits are interfaces with implementation*. Traits can be utilized to provide the implementation for a specific interface and since an interface is a guarantee that some methods are available it fits in the concept of Traits which provides those methods very well. To underpin this relationship, it is possible to declare that a Trait implements an interface like this:: <?php interface IHello { public function sayHello(); } trait SayHello implements IHello { public function sayHello() { echo 'Hello World!'; } } class MyHelloWorld { use SayHello; } $o = new MyHelloWorld(); var_dump($o instanceof IHello); // bool(true) If a Trait implements an interface, this definition is propagated to the class using the Trait. Therefore, it is possible to provide implementations for an interface and reuse them in different classes. Proposal and Patch ================== This Request for Comments proposes a new language feature for PHP named Traits. Traits are a nice approach to enhance the capabilities to design conceptual consistent class hierarchies and avoid code duplication. The patch against the PHP_5_2 branch is available at http://toolslave.net/snapshots/traits/traits.patch The written test cases are located here: http://toolslave.net/snapshots/traits/traits-tests.zip Additionally, the SVN repo used for developing this patch is located at https://instantsvc.svn.sourceforge.net/svnroot/instantsvc/branches/php-exten sion/traits-php/ Alternative Syntax Proposals ---------------------------- This section collects proposals for alternative Traits syntaxes. Traits Use Definition in the Class Header """"""""""""""""""""""""""""""""""""""""" Instead of declaring the Trait composition in the class body, it could be defined in the class prologue like this:: <?php trait Hello { public function sayHello() {} } class MyHelloWorld extends BaseClass uses Hello (hello => sayHello, !sayHello) { public function foo() {} } ?> The drawback of this notation is the implied notation of Traits as some kind of a type changing construct. Since they do not influence the type as their major feature, this notion would be misleading. Furthermore, this notation seams to have readability problems. Complex compositions are not as clearly arranged as they are with the *In-Body* notation. A patch implementing this notation is available at: http://toolslave.net/snapshots/traits/traits-head-syntax.patch More about Traits ----------------- As already mentioned, Traits is not a totally new concept, but the semantics used in this proposal has been fully defined at first in 2003. For scientific information and papers about Traits http://www.iam.unibe.ch/~scg/Research/Traits/ is a good starting point. Since it isn't a purely academic concepts, there are already languages supporting Traits out there. Squeak, Perl6, Scala, Slate, Fortress and even for C#/Rotor implementation are available. A detailed technical report has been published at http://www.iam.unibe.ch/~scg/Archive/Papers/Duca06bTOPLASTraits.pdf It explains Traits and gives some formal proves about the soundness of Traits, too. Last but not least, in this Phd thesis http://www.iam.unibe.ch/~scg/Archive/PhD/schaerli-phd.pdf two case studies have been publish illustrating the benefits Traits are providing.
From a userland perspective (i.e. not knowing the internals impact),
this looks like a great feature which treats the userland developer
with a bit of respect.
It allows me to take a class and add some addition functionality to it
without subclassing.
Conflicts are MY responsibility to solve.
No magic.
From a userland developer, +1.
A question (and again, no idea on feasibility, internals, etc.).
The above proposal covers adding/overriding internal methods with
external ones. Is it possible to also include additional properties?
If not, then you would have to override the constructor method (this
could be demoted to documentation rather than code then I suppose).
--
Richard Quadling
Zend Certified Engineer : http://zend.com/zce.php?c=ZEND002498&r=213474731
"Standing on the shoulders of some very clever giants!"
Hi Richard,
A question (and again, no idea on feasibility, internals, etc.).
The above proposal covers adding/overriding internal methods with
external ones. Is it possible to also include additional properties?If not, then you would have to override the constructor method (this
could be demoted to documentation rather than code then I suppose).
the current proposal does not allow state in your traits.
At the moment the solution would be like this:
trait A {
abstract protected function getFoo();
public function doSomeFoo($bar) {
return $this->getFoo().' baz ' . $bar;
}
}
Traits can defined abstract methods to define a required method. This
abstract methods can be implemented in the class or in an other trait.
There are also notions about stateful traits out there. For instance
Scala does implement them, but state is an additional level of
complexity and I have tried to avoid it in this implementation, since
I have had expected much more complains about the aliasing and exclude
operations and its pretended complexity.
In my opinion stateful traits are the next logical step, but with the
cost for some additional complexity, since you need handle conflicting
properties. (The stateful traits approach is to make it private to a
traits composition, but this might not be obvious to every user.)
Kind Regards
Stefan
Hi,
during last six months I've studied a language construct called Traits.
It is a construct to allow fine-grained code reuse and in my opinon
this would be a nice feature for PHP, which I did like to propose here.
The following RFC deals with the questions what Traits are, how they are
used, why they are usefull and how they do look like in PHP.
A patch implementing this new language construct is available, too.Thank you for your attention and I'm looking forward to hear your comments
:)Kind Regards
Stefan
I know this is in the bike-shed department, but the following syntax
doesn't look very PHP to me:
use B { !bigTalk, talk => bigTalk }
Could we separate exclusions from aliases somehow?
--
troels
Hello php,
looks good to me. See more detailed thoughts in separate mail resonses.
The biggest issue I see is finding a syntax everyone likes.
Personally I like everything but one tiny piece, that is you do '!method'
to ignore a method from a trait. Since renaming happens in a php array like
style I would prefer to have that approach apply for ignoring methods as
well. The way to do that imo is 'method=>false' or 'method=>NULL' which both
should be obvious to PHP programmers that heard about Traits.
Other than that I'd even appriciate it, if we could get this for 5.3. I mean
come on guys it is ready and a very nice solution to the code reuse problem.
marcus
Monday, February 18, 2008, 8:27:00 PM, you wrote:
Hi,
during last six months I've studied a language construct called Traits.
It is a construct to allow fine-grained code reuse and in my opinon
this would be a nice feature for PHP, which I did like to propose here.
The following RFC deals with the questions what Traits are, how they are
used, why they are usefull and how they do look like in PHP.
A patch implementing this new language construct is available, too.
Thank you for your attention and I'm looking forward to hear your comments
:)
Kind Regards
Stefan
Request for Comments: Traits for PHP
:HTML: http://www.stefan-marr.de/artikel/rfc-traits-for-php.html
... contents::
This RFC will discuss at first the motivation for Traits describing the
rationals
and presenting a short real world use case. The main part will describe the
concept of Traits in detail using the syntax for Traits implemented in a
patch
which is part of this proposal. In the end, the URL of the patch and
additional resources about Traits are given.
Introduction
Traits is a mechanism for code reuse in single inheritance languages such
as PHP. A Trait is intended to reduce some limitations of single inheritance
by enabeling a developer to reuse sets of methods freely in several
independent
classes living in different class hierarchies.
The semantics of the combination of Traits and classes is defined in a way,
which reduces complexity and avoids the typical problems associated with
multiple
inheritance and Mixins.
They are recognized for their potential in supporting better composition
and reuse, hence their integration in newer versions of languages
such as Perl 6, Squeak, Scala, Slate and Fortress.
Traits have also been ported to Java and C#.
Why do we need Traits?
Code reuse is one of the main goals that object-oriented languages try to
achieve
with inheritance. Unfortunately, single inheritance often forces the
developer
to take a decision in favor for either code reuse or conceptual clean
class
hierarchies. To achieve code reuse, methods have either to be duplicated or
to be moved near the root of the class hierarchy, but this hampers
understandability and maintainability of code.
To circumvent this problems multiple inheritance and Mixins have been
invented.
But both of them are complex and hard to understand. PHP5
has been explicitly designed with the clean and successful model of Java in
mind: single inheritance, but multiple interfaces. This decision has been
taken
to avoid the known problems of for example C++.
Traits have been invented to avoid those problems, too. They enable designer
to build
conceptually clean class hierarchies without the need to consider code reuse
or
complexity problems, but focusing on the real problem domain and
maintainability
instead.
Traits: A Mechanism for Fine-grained Reuse
A Trait is a unit of reuse much like a class, but only intended to group
functionality in a fine-grained and consistent way. It is not possible to
instantiate a Trait on its own. It is an addition to traditional inheritance
and enables horizontal composition of behavior.
The following code illustrates the current implementation of an extended
version of the PHP reflection API which provides detailed access to doc
comment
blocks.
ReflectionMethod and ReflectionFunction are classes from the reflection API
and
have to be extended with exactly the same code. In some situations it
would be possible to add a common base class, but in this case it is
impossible, because the extended classes are not under our control, i.e.,
they
are implemented in third party code or even in C, like it is the case here.
::
<?php
class ezcReflectionMethod extends ReflectionMethod {
/* ... /
function getReturnType() { /1/ }
function getReturnDescription() { /2/ }
/ ... */
}
class ezcReflectionFunction extends ReflectionFunction {
/* ... /
function getReturnType() { /1/ }
function getReturnDescription() { /2/ }
/ ... */
}
?>
With Traits it is possible to refactor this redundant code out.
::
<?php
trait ezcReflectionReturnInfo {
function getReturnType() { /1/ }
function getReturnDescription() { /2/ }
}
class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
/* ... */
}
class ezcReflectionFunction extends ReflectionFunction {
use ezcReflectionReturnInfo;
/* ... */
}
?>
This is just a small example of what Traits are useful for.
The next sections will discuss on more advanced techniques and describe how
the
current implementation of Traits for PHP works.
The Flattening Property
As already mentioned, multiple inheritance and Mixins are complex
mechanisms.
Traits are an alternative which have been designed to impose no
additional semantics on classes. Traits are only entities of the literal
code
written in your source files. There is no notion about Traits at runtime.
They are used to group methods and reuse code and are totally flattened
into the classes composed from them. It is almost like a language supported
and
failsafe copy'n'paste mechanism to build classes.
Precedence Order
""""""""""""""""
Flattening is achieved by applying some simple rules on the composition
mechanism. Instead of implementing a fancy and awkward algorithm to solve
problems, the entire control about the composition is left in the hand of
the
developer and fits nicely into the known inheritance model of PHP.
The following examples illustrate the semantics of Traits and their relation
to methods defined in classes.
::
<?php
class Base {
public function sayHello() {
echo 'Hello ';
}
}trait SayWorld {
public function sayHello() {
parent::sayHello();
echo 'World!';
}
}
class MyHelloWorld extends Base {
use SayWorld;
}
$o = new MyHelloWorld();
$o->sayHello(); // echos Hello World!
?>
As shown in the above code, an inherited method from a base class is
overridden
by the method inserted intoMyHelloWorld
from theSayWorld
Trait.
The behavior is the same for methods defined in theMyHelloWorld
class.
The precedence order is that methods from the current class override Trait
methods,
which in return override methods from the base class.
::
<?php
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
class TheWorldIsNotEnough {
use HelloWorld;
public function sayHello() {
echo 'Hello Universe!';
}
}
$o = new TheWorldIsNotEnough();
$o->sayHello(); // echos Hello Universe!
?>
Multiple Traits Usage
"""""""""""""""""""""
To keep things simple in the beginning, there has only one Trait being used
at
a time, but obviously a class could use multiple Traits at the same time.
::
<?php
trait Hello {
public function sayHello() {
echo 'Hello ';
}
}
trait World {
public function sayWorld() {
echo ' World';
}
}class MyHelloWorld {
use Hello;
use World;
public function sayExclamationMark() {
echo '!';
}
}$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
// Results eventually in: Hello World!
Conflict Resolution
"""""""""""""""""""
But now a problem will occur, if different Traits provide methods with the
same name.
::
<?php
trait A {
public function smallTalk() {
echo 'a';
}
public function bigTalk() {
echo 'A';
}
}
trait B {
public function smallTalk() {
echo 'b';
}
public function bigTalk() {
echo 'B';
}
}
?>
Both classes have to be used in a class named
Talker
. Multiple
inheritance
and Mixins define an algorithm to resolve this conflict. Traits don't.
Conflicts
aren't solved implicitly by any kind of precedence. Instead, to avoid
implicit
complexity, the developer has full control over class composition.
::
<?php
class Talker {
use A;
use B;
}
?>In case of the above definition of
Talker
, PHP will show a notice that
there
have been conflicts and name the methodssmallTalk()
andbigTalk()
as the reason of this conflict. Therefore, neither of the given
implementations
will be available in the class.
Instead, the developer can exactly define which methods are used and how the
conflict is resolved.
::
<?php
class Talker {
use A { !smallTalk }
use B { !bigTalk }
}
?>
This definition will result in the exclusion of
smallTalk()
from the
Trait A
andbigTalk()
from Trait B. Therefore, the resulting class Talker would
echo'b'
forsmallTalk()
and'A'
forbigTalk().
But simple exclusion of methods is not the best choice for all situations.
::
<?php
class Talker {
use A { !smallTalk }
use B { !bigTalk, talk => bigTalk }
}
?>Beside the exclusion an alias operation is available, too. This alias
operation, notated like akey => value
for arrays even has a similar
semantics like the array notation. The definitiontalk => bigTalk
lets the new nametalk
refer to the method body ofbigTalk
of the Trait B. The resultingTalker
class will consist of following
three methods:
bigTalk() { echo 'A'; }
smallTalk() { echo 'b'; }
talk() { echo 'B'; }
Since the alias operation adds a new name to an existing method body, the
bigTalk
method still has to be excluded. Otherwise, PHP would print
a notice that two methods from Traits have a conflict and are excluded.
Aliasing is not renaming and references in methods to a given method name
aren't changed either. On the first look this may sound strange, but it
provides the opportunity to build Traits and even hierarchies of Traits
which
fit together very well.
Traits Composed from Traits
"""""""""""""""""""""""""""
Not explicitly mentioned jet, but implied by the flattening property is the
composition of Traits from Traits.
Since Traits are fully flattened away at compile time it is possible to use
Traits to compose Traits without any additional impact on the semantics.
The following code illustrates this::
<?php
trait Hello {
public function sayHello() {
echo 'Hello ';
}
}
trait World {
public function sayWorld() {
echo 'World!';
}
}
trait HelloWorld {
use Hello;
use World;
}
class MyHelloWorld {
use HelloWorld;
}
$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
// Results eventually in: Hello World!
?>
Traits itself can take part in arbitrary compositions, but Traits are not
part
of the inheritance tree i.e., it is not possible to inherit from a Trait to
avoid confusion and misuse of Traits.
Traits Semantics Summarized
- Traits do not add runtime semantics, they only take part in the process
of
building a class.- Traits integrate into the precedence order of method overriding.
- To avoid complexity, conflicts between Trait methods have to be solved
explicitly. Otherwise a notice is generated and the conflicting methods
are excluded.- Specific methods can be excluded from a composition to handle conflicts.
- Aliases can be defined for methods to enable reuse of conflicting
methods.- Traits can be composed from Traits.
As a result of this semantics, at runtime, classes build using Traits are
not distinguishable
from classes not using Traits but traditional code duplication instead.
Semantics ofparent
and$this
hasn't changed, too. Used in a Trait
method, they behave exactly the same as if the method has been defined in
the
class directly.
Visibility and Interfaces
Visibility modifiers have not been discussed so far. Since Traits are meant
as
units of reuse, modifiers should be changeable easily in the context of a
composed class. Therefore, the aliasing operation is able to change the
visibility modifier of a method, too.
::
<?php
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
class MyClass1 {
use HelloWorld { protected sayHello }
}
class MyClass2 {
use HelloWorld { private doHelloWorld => sayHello }
}
?>
The abstract and final modifiers are supported, too. Abstract methods in
Traits are commonly used to define requirements a Trait needs to have
implemented by a class. The static modifier is not supported, because it
would
change the methods semantics and references to$this
would break.
Another important feature of PHP is the support of interfaces. A often used
metaphor to describe Traits is Traits are interfaces with implementation.
Traits can be utilized to provide the implementation for a specific
interface
and since an interface is a guarantee that some methods are available it
fits
in the concept of Traits which provides those methods very well.
To underpin this relationship, it is possible to declare that a Trait
implements an interface like this::
<?php
interface IHello {
public function sayHello();
}trait SayHello implements IHello {
public function sayHello() {
echo 'Hello World!';
}
}
class MyHelloWorld {
use SayHello;
}$o = new MyHelloWorld();
var_dump($o instanceof IHello); // bool(true)
If a Trait implements an interface, this definition is propagated to the
class
using the Trait. Therefore, it is possible to provide implementations for an
interface
and reuse them in different classes.
Proposal and Patch
This Request for Comments proposes a new language feature for PHP named
Traits.
Traits are a nice approach to enhance the capabilities to design conceptual
consistent class hierarchies and avoid code duplication.
The patch against the PHP_5_2 branch is available at
http://toolslave.net/snapshots/traits/traits.patch
The written test cases are located here:
http://toolslave.net/snapshots/traits/traits-tests.zip
Additionally, the SVN repo used for developing this patch is located at
https://instantsvc.svn.sourceforge.net/svnroot/instantsvc/branches/php-exten
sion/traits-php/
Alternative Syntax Proposals
This section collects proposals for alternative Traits syntaxes.
Traits Use Definition in the Class Header
"""""""""""""""""""""""""""""""""""""""""
Instead of declaring the Trait composition in the class body, it could be
defined in the class prologue like this::<?php
trait Hello {
public function sayHello() {}
}
class MyHelloWorld extends BaseClass
uses Hello (hello => sayHello, !sayHello)
{
public function foo() {}
}
?>
The drawback of this notation is the implied notation of Traits as some kind
of
a type changing construct. Since they do not influence the type as their
major
feature, this notion would be misleading. Furthermore, this notation seams
to
have readability problems. Complex compositions are not as clearly arranged
as
they are with the In-Body notation.
A patch implementing this notation is available at:
http://toolslave.net/snapshots/traits/traits-head-syntax.patch
More about Traits
As already mentioned, Traits is not a totally new concept, but the semantics
used in this proposal has been fully defined at first in 2003. For
scientific
information and papers about Traits
http://www.iam.unibe.ch/~scg/Research/Traits/
is a good starting point. Since it isn't a purely academic concepts, there
are
already languages supporting Traits out there. Squeak, Perl6, Scala, Slate,
Fortress and even for C#/Rotor implementation are available.
A detailed technical report has been published at
http://www.iam.unibe.ch/~scg/Archive/Papers/Duca06bTOPLASTraits.pdf
It explains Traits and gives some formal proves about the soundness of
Traits, too.
Last but not least, in this Phd thesis
http://www.iam.unibe.ch/~scg/Archive/PhD/schaerli-phd.pdf
two case studies have been publish illustrating the benefits Traits are
providing.
Best regards,
Marcus
Hi Marcus,
Am Dienstag, den 19.02.2008, 17:58 +0100 schrieb Marcus Boerger:
[...]
looks good to me. See more detailed thoughts in separate mail resonses.
The biggest issue I see is finding a syntax everyone likes.
I can't agree more.
Personally I like everything but one tiny piece, that is you do '!method'
to ignore a method from a trait. Since renaming happens in a php array like
style I would prefer to have that approach apply for ignoring methods as
well. The way to do that imo is 'method=>false' or 'method=>NULL' which both
should be obvious to PHP programmers that heard about Traits.
The irritating thing is that currently it would be null => 'oldmethod'.
This is what I would critisize in general. I would prefer
'oldMethodName' => 'newMethodName' instead. 'oldMethodName' => null
would be really cool.
Other than that I'd even appriciate it, if we could get this for 5.3.
I mean come on guys it is ready and a very nice solution to the code
reuse problem.
[...]
This would be a killer feature for 5.3! How realistic is it to get this
in?
[...]
cu, Lars
Personally I like everything but one tiny piece, that is you do '!method'
to ignore a method from a trait. Since renaming happens in a php array like
style I would prefer to have that approach apply for ignoring methods as
well. The way to do that imo is 'method=>false' or 'method=>NULL' which both
should be obvious to PHP programmers that heard about Traits.The irritating thing is that currently it would be null => 'oldmethod'.
This is what I would critisize in general. I would prefer
'oldMethodName' => 'newMethodName' instead. 'oldMethodName' => null
would be really cool.
It is not renaming is aliasing :)
Feels like I should give some thoughts on why renaming is not a good idea.
The question is, how should renaming work and in which scope is it applied?
Renaming would imply to adjust all references to a method name in all
methods of one composition. A composition are all methods flattened
into a class from one specific trait usage (including all methods from
traits the included trait is using). Ok this is the scope. Now we
would need to adjust all those method bodies, find all method calls on
$this->oldMethodName() and change them to $this->newMethodName().
Think this would be possible, yes.
But think the original traits idea goes a step further.
By excluding a method from a trait it is possible to mesh traits
together and build compositions where the result is a interleaving
construct, like gears.
The problem with renaming is, you will have no chance to achieve such
a meshed situation without additional glue code.
Excluding a trait method can be thought of as punching a hole into
your class which is either filled by another trait or by a method
defined in the class itself.
traits the included trait is using). Ok this is the scope. Now we
would need to adjust all those method bodies, find all method calls on
$this->oldMethodName() and change them to $this->newMethodName().
You can't - PHP has dynamic method resolution (think $this->$foo()).
Also, it has callbacks - what happens if method is registered as
callback is renamed?
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi Stanislav,
traits the included trait is using). Ok this is the scope. Now we
would need to adjust all those method bodies, find all method calls on
$this->oldMethodName() and change them to $this->newMethodName().You can't - PHP has dynamic method resolution (think $this->$foo()).
Also, it has callbacks - what happens if method is registered as
callback is renamed?
thanks, this is very good reason not to allow renaming, but aliasing.
:)
Kind Regards
Stefan
Stefan Marr wrote:
Hi Stanislav,
traits the included trait is using). Ok this is the scope. Now we
would need to adjust all those method bodies, find all method calls on
$this->oldMethodName() and change them to $this->newMethodName().You can't - PHP has dynamic method resolution (think $this->$foo()).
Also, it has callbacks - what happens if method is registered as
callback is renamed?thanks, this is very good reason not to allow renaming, but aliasing.
:)
Aliasing doesn't make a lot of sense, as you can always :
function newMethod() {
return $this->oldMethod();
}
just seems like unneeded complexity, without a clear benefit..
Aliasing doesn't make a lot of sense, as you can always :
function newMethod() {
return $this->oldMethod();
}
just seems like unneeded complexity, without a clear benefit..
I believe the idea was to resolve nameclashes.
--
troels
Hi Evert,
Aliasing doesn't make a lot of sense, as you can always :
function newMethod() {
return $this->oldMethod();
}
Don't think so.
You do use aliasing to handle conflicts and in the case of a conflict
there is no oldMethod.
trait A {
public function foo() {echo 'foo';}
}
trait B {
public function foo() {echo 'fooBar';}
}
class MyClass {
use A;
use B {
!foo, fooBar => foo
}
}
The result will be at runtime:
class MyClass {
public function foo() {echo 'foo';}
public function foo() {echo 'fooBar';}
}
Here you can't do something like this:
class MyClass {
use A;
use B;
}
since eventually there wont be any method in MyClass.
Hello Stefan,
so at th eend of the day users will get renaming and will abuse it.
That said I am wondering if at the beginning we might want to do Traits
without aliasing and dropping. If we feel it gets necessary we probbaly
might want to support a syntax like:
'trait_method' => false
'trait_method' => 'new_name'
'trait_method' => array('new_name', 'trait_method'
I am suggesting this because it is more what people expect to find. And it
is easier to understand imo.
marcus
Tuesday, February 19, 2008, 10:59:20 PM, you wrote:
Hi Evert,
Aliasing doesn't make a lot of sense, as you can always :
function newMethod() {
return $this->oldMethod();
}
Don't think so.
You do use aliasing to handle conflicts and in the case of a conflict
there is no oldMethod.
trait A {
public function foo() {echo 'foo';}
}
trait B {
public function foo() {echo 'fooBar';}
}
class MyClass {
use A;
use B {
!foo, fooBar => foo
}
}
The result will be at runtime:
class MyClass {
public function foo() {echo 'foo';}
public function foo() {echo 'fooBar';}
}
Here you can't do something like this:
class MyClass {
use A;
use B;
}
since eventually there wont be any method in MyClass.
Best regards,
Marcus
Hi Marcus,
Am Mittwoch, den 20.02.2008, 20:59 +0100 schrieb Marcus Boerger:
[...]
so at th eend of the day users will get renaming and will abuse it.
I disagree here. I think aliasing is fundamental to traits. If we think
of traits as buckets of functionality, the concrete structure of the
trait does not really matter. What matters is how the trait is used in
the consuming class. Part of the usage is how the trait methods are
exposed to the class. So I would really like to see aliasing support as
long as there are no technical limitations that forbid proper and simple
and understandable aliasing.
That said I am wondering if at the beginning we might want to do Traits
without aliasing and dropping. If we feel it gets necessary we probbaly
might want to support a syntax like:
'trait_method' => false
'trait_method' => 'new_name'
'trait_method' => array('new_name', 'trait_method'
I have another syntax proposal:
class Foo {
consume TFoo {
methodName as newMethodName;
ignore otherMethod;
}
}
"foo as bar" seems easy to grasp for me. Ignore seems to be technically
correct, as the method is not dropped or removed but just ignored for
the current class context. I would really prefer not to use "exhibit" or
"possess". Most OOP terms are really basic english to make also
non-english speakers get the concepts easily. "abstract", "class",
"object", "interface", etc. pp. is pretty basic, while "exhibit" or
"possess" is maybe not part of the common basic vocabulary.
cu, Las
If we feel it gets necessary we probbaly
might want to support a syntax like:
'trait_method' => false
'trait_method' => 'new_name'
'trait_method' => array('new_name', 'trait_method'
I'm not comfortable with this notation, since it strengthens the
impression that it is renaming.
In my eyes it looks like this:
"trait_method => false" means trait_method is moved to false, it is
deleted, but this leads to the wrong impression of this:
"trait_method => new_name" means trait_method is moved to new_name,
and I am absolutely against this notion.
Renaming implies in my opinion all references to this method might be
adjusted (what is not possible at all) and the original method is
"lost".
There are several problems with with "lost" method already discussed
on this list, like not fulfilled requirements (the delete method
thing).
The aliasing notion has the benefit that a method has to be explicitly
excluded to be removed. So the developer explicitly requests for a
change that he will handle on its own.
Kind Regards
Stefan
If we feel it gets necessary we probbaly
might want to support a syntax like:
'trait_method' => false
'trait_method' => 'new_name'
'trait_method' => array('new_name', 'trait_method'
I'm not comfortable with this notation, since it strengthens the
impression that it is renaming.In my eyes it looks like this:
"trait_method => false" means trait_method is moved to false, it is
deleted, but this leads to the wrong impression of this:
"trait_method => new_name" means trait_method is moved to new_name,
and I am absolutely against this notion.Renaming implies in my opinion all references to this method might be
adjusted (what is not possible at all) and the original method is
"lost".
There are several problems with with "lost" method already discussed
on this list, like not fulfilled requirements (the delete method
thing).The aliasing notion has the benefit that a method has to be explicitly
excluded to be removed. So the developer explicitly requests for a
change that he will handle on its own.
I agree that its important to make this concept clear in the syntax
and that Marcus's proposal does not make it sufficiently clear.
I do like Lars' proposal however:
I have another syntax proposal:
class Foo {
consume TFoo {
methodName as newMethodName;
ignore otherMethod;
}
}
maybe "as" should even be expanded to "alias"
though the following does look like the english grammar police will
get us:
methodName alias newMethodName;
regards,
Lukas
Hello Lukas,
alright,
'foo as bar' is ok to me and does not even add a new keyword.
Ignore or any more keywords are bad and I also think that the correct
would be hide(ing). But as I further more explained it should really be
something that only marks a method as private if at all. That can be
done as:
'foo as private' since private is a keyword it cannot conflice anyway.
marcus
Wednesday, February 20, 2008, 9:52:56 PM, you wrote:
If we feel it gets necessary we probbaly
might want to support a syntax like:
'trait_method' => false
'trait_method' => 'new_name'
'trait_method' => array('new_name', 'trait_method'
I'm not comfortable with this notation, since it strengthens the
impression that it is renaming.In my eyes it looks like this:
"trait_method => false" means trait_method is moved to false, it is
deleted, but this leads to the wrong impression of this:
"trait_method => new_name" means trait_method is moved to new_name,
and I am absolutely against this notion.Renaming implies in my opinion all references to this method might be
adjusted (what is not possible at all) and the original method is
"lost".
There are several problems with with "lost" method already discussed
on this list, like not fulfilled requirements (the delete method
thing).The aliasing notion has the benefit that a method has to be explicitly
excluded to be removed. So the developer explicitly requests for a
change that he will handle on its own.
I agree that its important to make this concept clear in the syntax
and that Marcus's proposal does not make it sufficiently clear.
I do like Lars' proposal however:
I have another syntax proposal:
class Foo {
consume TFoo {
methodName as newMethodName;
ignore otherMethod;
}
}
maybe "as" should even be expanded to "alias"
though the following does look like the english grammar police will
get us:
methodName alias newMethodName;
regards,
Lukas
Best regards,
Marcus
Marcus Boerger wrote:
Hello Lukas,
alright,
'foo as bar' is ok to me and does not even add a new keyword.
Ignore or any more keywords are bad and I also think that the correct
would be hide(ing). But as I further more explained it should really be
something that only marks a method as private if at all. That can be
done as:
'foo as private' since private is a keyword it cannot conflice anyway.
I like this solution.
I have been uncomfortable with the current trait suggestions because
they occur in the body of the class, whereas extends/implements is
outside. I think there is a way to handle this, however.
class Blah extends ... implements ... traits trait1, trait2, trait3 {
}
The above presents a problem if trait2 contains conflicting method
names, but a simple way to solve it would be to automatically alias
methods by prepending traitname::
<?php
trait trait1 { function a(){}}
trait trait2 { function a(){}}
class Blah extends ... implements ... traits trait1, trait2, trait3 {
}
?>
would create method "trait2::a" and we can alias it in the class body.
Also, $a->{'trait2::a'}(); is legal syntax, so no problems there.
Example:
<?php
trait trait1 { function a(){}}
trait trait2 { function a(){}}
class Blah extends ... implements ... traits trait1, trait2, trait3 {
function trait2::a as b;
}
?>
This would be 100% obvious to me, and far clearer for reading since we
would be explicitly saying we're aliasing a function.
So, I rescind my previous syntax suggestion in favor of the above.
Greg
Marcus Boerger wrote:
Hello Lukas,
alright,
'foo as bar' is ok to me and does not even add a new keyword.
Ignore or any more keywords are bad and I also think that the correct
would be hide(ing). But as I further more explained it should
really be
something that only marks a method as private if at all. That can be
done as:
'foo as private' since private is a keyword it cannot conflice
anyway.I like this solution.
Yes to me it seems like we can solve the entire "lack of
encapsulation" through aliasing/hiding by making the following changes
to the original proposal:
A trait may contain methods and properties. When importing a trait
into a class, all methods and properties are imported in a way that
makes them only visible within the trait (I dont like the use of
"private" here .. its what confused me in the first proposals of this
approach by Marcus/Andi). The user of the trait can however explicitly
make properties and methods from the trait visible to the class
importing the trait. Also the trait using class can explicitly say
that it wants to override a method/property in the scope of the trait.
This way:
- there is little chance of accidentally breaking a trait
- changes within the trait should not break anything in the trait
using class, unless the developer explicitly messed with the traits
internals in the class using the trait. in that case however he can
quickly spot the potentially problematic lines
I have been uncomfortable with the current trait suggestions because
they occur in the body of the class, whereas extends/implements is
outside. I think there is a way to handle this, however.
I dont agree here. traits are different. They do not affect "what" the
class is. They change what it can do and as such the stuff should be
defined next to properties and methods.
<?php
trait trait1 { function a(){}}
trait trait2 { function a(){}}
class Blah extends ... implements ... traits trait1, trait2, trait3 {
}
?>
Here it gets worse. Now if I refactor things into a trait, I have to
start changing all uses of those methods.
@Greg: I think you are going in the wrong direction here.
regards,
Lukas
Marcus Boerger wrote:
Hello Lukas,
alright,
'foo as bar' is ok to me and does not even add a new keyword.
Ignore or any more keywords are bad and I also think that the correct
would be hide(ing). But as I further more explained it should
really be
something that only marks a method as private if at all. That can be
done as:
'foo as private' since private is a keyword it cannot conflice
anyway.I like this solution.
Yes to me it seems like we can solve the entire "lack of
encapsulation" through aliasing/hiding by making the following changes
to the original proposal:A trait may contain methods and properties. When importing a trait
into a class, all methods and properties are imported in a way that
makes them only visible within the trait (I dont like the use of
"private" here .. its what confused me in the first proposals of this
approach by Marcus/Andi). The user of the trait can however explicitly
make properties and methods from the trait visible to the class
importing the trait. Also the trait using class can explicitly say
that it wants to override a method/property in the scope of the trait.This way:
- there is little chance of accidentally breaking a trait
- changes within the trait should not break anything in the trait
using class, unless the developer explicitly messed with the traits
internals in the class using the trait. in that case however he can
quickly spot the potentially problematic linesI have been uncomfortable with the current trait suggestions because
they occur in the body of the class, whereas extends/implements is
outside. I think there is a way to handle this, however.I dont agree here. traits are different. They do not affect "what" the
class is. They change what it can do and as such the stuff should be
defined next to properties and methods.<?php
trait trait1 { function a(){}}
trait trait2 { function a(){}}
class Blah extends ... implements ... traits trait1, trait2, trait3 {
}
?>Here it gets worse. Now if I refactor things into a trait, I have to
start changing all uses of those methods.@Greg: I think you are going in the wrong direction here.
Fun that you mentionned that structure greg because I was thinking of
something along those lines. Something very similar actually but the
aliasing could look like this:
trait One {
function name() { return TRAIT; }
function removeMe() {}
}
trait Two {
function name() { return TRAIT; }
}
class Reel traits One, Two
{
override One::name with Two::name;
remove Two::removeMe;
}
$object = new Reel();
echo $object->name(); // Echos "Two" of course.
I find that rather clean and clear that you we are either overriding
what with what and then removing a method. But as lukas mentionned, we
may be stepping out of line here...
regards,
Lukas--
--
David,
Re-read what you are replying.
Marcus Boerger wrote:
Hello Lukas,
alright,
'foo as bar' is ok to me and does not even add a new keyword.
Ignore or any more keywords are bad and I also think that the correct
would be hide(ing). But as I further more explained it should
really be
something that only marks a method as private if at all. That can be
done as:
'foo as private' since private is a keyword it cannot conflice
anyway.I like this solution.
Yes to me it seems like we can solve the entire "lack of
encapsulation" through aliasing/hiding by making the following changes
to the original proposal:A trait may contain methods and properties. When importing a trait
into a class, all methods and properties are imported in a way that
makes them only visible within the trait (I dont like the use of
"private" here .. its what confused me in the first proposals of this
approach by Marcus/Andi). The user of the trait can however explicitly
make properties and methods from the trait visible to the class
importing the trait. Also the trait using class can explicitly say
that it wants to override a method/property in the scope of the trait.This way:
- there is little chance of accidentally breaking a trait
- changes within the trait should not break anything in the trait
using class, unless the developer explicitly messed with the traits
internals in the class using the trait. in that case however he can
quickly spot the potentially problematic linesI have been uncomfortable with the current trait suggestions because
they occur in the body of the class, whereas extends/implements is
outside. I think there is a way to handle this, however.I dont agree here. traits are different. They do not affect "what" the
class is. They change what it can do and as such the stuff should be
defined next to properties and methods.<?php
trait trait1 { function a(){}}
trait trait2 { function a(){}}
class Blah extends ... implements ... traits trait1, trait2, trait3 {
}
?>Here it gets worse. Now if I refactor things into a trait, I have to
start changing all uses of those methods.@Greg: I think you are going in the wrong direction here.
Fun that you mentionned that structure greg because I was thinking of
something along those lines. Something very similar actually but the
aliasing could look like this:trait One {
function name() { return TRAIT; }
function removeMe() {}
}trait Two {
function name() { return TRAIT; }
}class Reel traits One, Two
{
override One::name with Two::name;
remove Two::removeMe;
It is of course
remove One::removeMe;
}
$object = new Reel();
echo $object->name(); // Echos "Two" of course.I find that rather clean and clear that you we are either overriding
what with what and then removing a method. But as lukas mentionned, we
may be stepping out of line here...regards,
Lukas--
--
David,
Re-read what you are replying.
--
David,
Re-read what you are replying.
Hi!
Fun that you mentionned that structure greg because I was thinking of
something along those lines. Something very similar actually but the
aliasing could look like this:trait One {
function name() { return TRAIT; }
function removeMe() {}
}trait Two {
function name() { return TRAIT; }
}class Reel traits One, Two
{
override One::name with Two::name;
remove Two::removeMe;
}$object = new Reel();
echo $object->name(); // Echos "Two" of course.
Two doesn't have removeMe. If you meant One::removeMe then why One is
needed at all? You didn't use any method of it.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi!
A trait may contain methods and properties. When importing a trait into
a class, all methods and properties are imported in a way that makes
them only visible within the trait (I dont like the use of "private"
How you are going to do that?
here .. its what confused me in the first proposals of this approach by
Marcus/Andi). The user of the trait can however explicitly make
properties and methods from the trait visible to the class importing the
Looks like composition/delegation to me. Maybe that's what it should be?
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi!
A trait may contain methods and properties. When importing a trait
into a class, all methods and properties are imported in a way that
makes them only visible within the trait (I dont like the use of
"private"How you are going to do that?
I have no clue how to technically make this possible, but Andi
appeared confident that it should be possible with reasonable effort.
regards,
Lukas
-----Original Message-----
From: Stanislav Malyshev [mailto:stas@zend.com]
Sent: Friday, February 22, 2008 9:21 AM
To: Lukas Kahwe Smith
Cc: Gregory Beaver; Marcus Boerger; php@stefan-marr.de; Evert|Rooftop;
internals@lists.php.net
Subject: Re: [PHP-DEV] RFC: Traits for PHPHi!
A trait may contain methods and properties. When importing a trait
into
a class, all methods and properties are imported in a way that makes
them only visible within the trait (I dont like the use of "private"How you are going to do that?
I think it's possible to do this but I haven't had time to dive deeper
in the past couple of days.
In my opinion the benefits of doing Traits in this way would be
significant.
You would still get the benefits of the original Traits proposal but it
would provide stronger encapsulation and less potential for breakage (on
par with typical OO polymorphism).
In addition I really believe that enabling state is extremely important
to make this a commonly useful feature (either via "private" or via a
"local" like Stefan suggested).
I won't be back at work until Tuesday but I will try and find some time
to figure out if this can be implemented. I am pretty sure it can if
during compilation of the Trait we mangle the properties&methods similar
to private properties&methods using the Trait's name; and then when we
flatten into a class then for the methods we expose we make sure the
mangled methods use the new method's op_array. This should not be too
hard and as Traits flattening does not require the same semantics as
inheritance I think it should be quite doable.
Bed time over here but I wanted to drop a short note so you didn't think
I dropped of the face of the planet in the midst of this interesting
discussion :)
Andi
Lukas Kahwe Smith wrote:
I have been uncomfortable with the current trait suggestions because
they occur in the body of the class, whereas extends/implements is
outside. I think there is a way to handle this, however.I dont agree here. traits are different. They do not affect "what" the
class is. They change what it can do and as such the stuff should be
defined next to properties and methods.<?php
trait trait1 { function a(){}}
trait trait2 { function a(){}}
class Blah extends ... implements ... traits trait1, trait2, trait3 {
}
?>Here it gets worse. Now if I refactor things into a trait, I have to
start changing all uses of those methods.@Greg: I think you are going in the wrong direction here.
I think you may be confused, because your statement about refactoring is
inaccurate - for most methods, there would be no change from the current
proposal. In other words, in my example above, you could use
trait1::a() simply as Blah::a, for instance:
<?php
$a = new Blah;
$a->a();
?>
Only if one wanted to access trait2::a() would one need to either alias
via "function trait2::a as b" to use as "$a->b()" or explicitly refer to
"$a->{'trait2::a'}()"
To be absolutely clear, here is how it could work:
<?php
trait A {
function myfunc(){ echo 'myfunc';}
function a(){ echo 'A::a';}
}
trait B {
function a(){ echo 'B::a';}
function another(){echo 'another';}
}
class Blah extends... implements... traits A, B {
}
$a = new Blah();
$a->myfunc(); // myfunc
$a->a(); // B::a
$a->another(); // another
$a->{'A::a'}() // A::a
?>
So, as you can see, all of the trait methods are available as regular
methods except for A::a which is overridden by B::a. If the order were
reversed, i.e. "traits B, A" then "$a->a()" would instead call A::a.
The current proposal would cause the above to fail with a name conflict
error rather than automatically aliasing.
Here is another example with the overriding syntax:
<?php
trait A {
function myfunc(){ echo 'myfunc';}
function a(){ echo 'A::a';}
}
trait B {
function a(){ echo 'B::a';}
function another(){echo 'another';}
}
class Blah extends... implements... traits A, B {
function B::a as private;
function A::a as a;
}
$a = new Blah();
$a->myfunc(); // myfunc
$a->a(); // A::a
$a->another(); // another
?>
Greg
I think you may be confused, because your statement about
refactoring is
inaccurate - for most methods, there would be no change from the
current
proposal. In other words, in my example above, you could use
trait1::a() simply as Blah::a, for instance:<?php
$a = new Blah;
$a->a();
?>Only if one wanted to access trait2::a() would one need to either
alias
via "function trait2::a as b" to use as "$a->b()" or explicitly
refer to
"$a->{'trait2::a'}()"
Ah ok, I thought that trait2::a was the only way to access the trait2
a() method. Ok then its not as problematic. However I do not really
like that syntax much. Like I said I also do not like the use of the
word "private" in this context. I find it totally confusing, since its
not private in the PPP sense. And like I also said before, I am not
convinced that traits belong outside of the curly brackets next to the
class name and the interface the class may implement. That would make
it appear like the trait defines the "type" of the class, which it
should not.
So I am still of the opinion that by default all properties/methods
should be imported as encapsulated units and the developer can decide
which parts of the trait should become visibile to the trait using
class.
regards,
Lukas
Gregory Beaver wrote:
So, as you can see, all of the trait methods are available as regular
methods except for A::a which is overridden by B::a. If the order were
reversed, i.e. "traits B, A" then "$a->a()" would instead call A::a.
I could see this becoming a problem when it plays along with namespaces.
Let me modify your example a little bit to show what I mean.
Some/Name/Space/A.php
<?php
namespace Some::Name::Space;
trait A {
function myfunc(){ echo 'myfunc';}
function a(){ echo 'Some::Name::Space::A::a';}
}
?>
Some/Other/Name/Space/A.php
<?php
namespace Some::Other::Name::Space;
trait A {
function a(){ echo 'Some::Other::Name::Space::B::a';}
function another(){echo 'another';}
}
?>
Blah.php
<?php
class Blah extends... implements...
traits
Some::Name::Space::A,
Some::Other::Name::Space::A {
}
$a = new Blah();
$a->myfunc(); // myfunc
$a->a(); // Some::Other::Name::Space::B::a
$a->another(); // another
$a->{'Some::Name::Space::A::a'}() // Some::Name::Space::A::a
?>
I think this could lead to some problems that namespacing is trying to
solve. Also, what happens if you decide to refactor one of the traits
and change its name or namespace. You would then have to search for
every use of an "autoaliased" method. I think the original proposal is
much better in this regard, because you only have to find the classes
that use the trait.
Another thing that bothers me about this idea is that the order that
traits are added to a class determines which methods the class will
have. This seems more and more like a mixin. I like the idea that the
programmer has to make the choice over which implementation. It should
be easy for the programmer to decide on one of these three:
- Use one of the trait's implementations of the method and exclude
all others (and possibly provide an alias to them). - Override the method in the class with a custom implementation.
- Alias each of the methods and use them in the overridden method
implementation.
One more thing that I do not like about the auto aliasing is flattening.
If I want to flatten all the traits into my class, I don't know of a way
to make a method called {'Some::Name::Space::A::a'} inside the class in
userspace (even though the interpreter can obviously handle it.
--
"Joshua Thompson" spam.goes.in.here@gmail.com
<http://www.schmalls.com
Hi,
The above presents a problem if trait2 contains conflicting method
names, but a simple way to solve it would be to automatically alias
methods by prepending traitname::<?php
trait trait1 { function a(){}}
trait trait2 { function a(){}}
class Blah extends ... implements ... traits trait1, trait2, trait3 {
}
?>would create method "trait2::a" and we can alias it in the class body.
Also, $a->{'trait2::a'}(); is legal syntax, so no problems there.
Example:
<?phptrait trait1 { function a(){}}
trait trait2 { function a(){}}
class Blah extends ... implements ... traits trait1, trait2, trait3 {
function trait2::a as b;
}
?>This would be 100% obvious to me, and far clearer for reading since we
would be explicitly saying we're aliasing a function.
Could you please explain where in your proposal is the difference to the
multiple inheritance available in C++?
Think the auto-aliasing will lead to nothing else then multiple
inheritance, with all its power and complexity.
One of the main problems here is the fragility, already pointed out be
Joshua in its response from today.
Kind Regards
If we feel it gets necessary we probbaly
might want to support a syntax like:
'trait_method' => false
'trait_method' => 'new_name'
'trait_method' => array('new_name', 'trait_method'I'm not comfortable with this notation, since it strengthens the
impression that it is renaming.In my eyes it looks like this:
"trait_method => false" means trait_method is moved to false, it is
deleted, but this leads to the wrong impression of this:
"trait_method => new_name" means trait_method is moved to new_name,
and I am absolutely against this notion.Kind Regards
Stefan
What would you all read this to be ...
void := 'trait_method'
'new_name' := 'trait_method'
array('new_name', 'trait_method') := 'trait_method'
:= is "becomes equal".
--
Richard Quadling
Zend Certified Engineer : http://zend.com/zce.php?c=ZEND002498&r=213474731
"Standing on the shoulders of some very clever giants!"
Hi Marcus,
Hi Troels,
The biggest issue I see is finding a syntax everyone likes.
Well, lets try some variations.
Since renaming happens in a php array like
style I would prefer to have that approach apply for ignoring methods as
well. The way to do that imo is 'method=>false' or 'method=>NULL' which both
should be obvious to PHP programmers that heard about Traits.
At first I'll have to make this clear. Aliasing != renaming.
It is not renaming operation. Aliasing will give you an additional
name for a method body.
By example:
trait Foo {
public function foo() {echo 'foo';}
}
class FooFoo {
use Foo {
bar => foo
}
}
This will eventually result in the following class at runtime:
class FooFoo { /* RUNTIME */
public function foo() { echo 'foo';}
public function bar() { echo 'foo';}
}
My idea behind this notation was the key => value thing
use Trait {
additionalMethodName => method
}
but may be we should use a keyword here which will be more clear.
Troels ask for a separation of aliases and exclusions.
Here are some notation proposals:
[1] Explicit Except/Without List
use Trait except foo1, foo2 {
bar => foo1
}
the keyword except could may be replaced by exceptfor or without if it
fits better.
[2a] ! is not readable --> except
use Trait {
except foo1, foo2;
bar => foo1
}
[2b] ! is not readable --> without
use Trait {
without foo1;
without foo2;
bar => foo1;
}
[Aa] Aliasing is not obvious
use Trait {
bar is foo1; //aliasMethod is method
}
[Ab] Aliasing is not obvious
use Trait {
bar from foo1; //aliasMethod from method
}
I'm not sure about Aa and Ab because they do not read well in my opinion.
What do you think?
how about 'possesses' or 'exhibits' - both these words are closer to the
natural language usage of 'trait' with regard to a subject.John exhibits a **** trait
Jack possesses a **** trait
I prefer without
over except
, because except
is close to
exception
and because it has a different meaning in other
languages (Eg. catch
).
I'm not sure if the use-keyword is a good idea as namespaces are already
"used". If we use "use" for traits, maybe going back to "import" for
namespaces would be the way to go.
I agree that use
is a bad choice, because of the nameclash with
the namespaces syntax (pun intended). possesses
is hideous, but
exhibit
isn't too bad.
Here are some notation proposals:
(snip)
I'm not sure about Aa and Ab because they do not read well in my opinion.
What do you think?
[Ac] How about this:
class FooFoo {
exhibit Traitor {
alias foo as bar;
}
}
--
troels
Hi Stefan,
Am Montag, den 18.02.2008, 20:27 +0100 schrieb php@stefan-marr.de:
[...]
class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
/* ... */
}
I'm not sure if the use-keyword is a good idea as namespaces are already
"used". If we use "use" for traits, maybe going back to "import" for
namespaces would be the way to go.
cu, Lars
Lars Strojny
Senior Software Developer MediaVentures GmbH (http://mediaventures.de)
Hello Lars,
we could even go for include here if we wanted to avoid use as much as
adding a new keyword. Personally I don't mind using keywords for different
stuff as long as it cannot conflict. That is in this case true for both
include and use.
marcus
Tuesday, February 19, 2008, 9:31:29 PM, you wrote:
Hi Stefan,
Am Montag, den 18.02.2008, 20:27 +0100 schrieb php@stefan-marr.de:
[...]class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
/* ... */
}
I'm not sure if the use-keyword is a good idea as namespaces are already
"used". If we use "use" for traits, maybe going back to "import" for
namespaces would be the way to go.
cu, Lars
Best regards,
Marcus
Hi Marcus,
Am Dienstag, den 19.02.2008, 21:42 +0100 schrieb Marcus Boerger:
[...]
we could even go for include here if we wanted to avoid use as much as
adding a new keyword. Personally I don't mind using keywords for different
stuff as long as it cannot conflict. That is in this case true for both
include and use.
I'm not sure about this. I can imagine it would not be really intuitive
why someone "use" a namespace and "use" a trait while he also "includes"
a file and "includes" a trait. So my only concern is that this great
language feature would be harder to understand as it must be.
cu, Lars
Lars Strojny
Senior Software Developer MediaVentures GmbH (http://mediaventures.de)
firstly, I'd like to reiterate the general sentiment
that Stefans RFC is blinding! (that's a good thing in this context ;-)
Marcus Boerger schreef:
Hello Lars,
we could even go for include here if we wanted to avoid use as much as
adding a new keyword. Personally I don't mind using keywords for different
stuff as long as it cannot conflict. That is in this case true for both
include and use.
how about 'possesses' or 'exhibits' - both these words are closer to the
natural language usage of 'trait' with regard to a subject.
John exhibits a **** trait
Jack possesses a **** trait
a person coming accross 'use' or 'include' in the context of
trait attribution may either make assumptions or become confused as to
possible changes/additions to the use and/or include functionality, a
new keyword that aptly describes the intention will more likely force
users to actually find out what it means.
an another alternative might be 'applies' - which doesn't fit the
natural language usage of 'trait' but does succintly describe what is happening.
just a thought.
marcus
Tuesday, February 19, 2008, 9:31:29 PM, you wrote:
Hi Stefan,
Am Montag, den 18.02.2008, 20:27 +0100 schrieb php@stefan-marr.de:
[...]class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
/* ... */
}I'm not sure if the use-keyword is a good idea as namespaces are already
"used". If we use "use" for traits, maybe going back to "import" for
namespaces would be the way to go.cu, Lars
Best regards,
Marcus
Hello Jochem,
good arguments. And good ideas. I'd favor 'posesses' then.
marcus
Tuesday, February 19, 2008, 9:54:09 PM, you wrote:
firstly, I'd like to reiterate the general sentiment
that Stefans RFC is blinding! (that's a good thing in this context ;-)
Marcus Boerger schreef:
Hello Lars,
we could even go for include here if we wanted to avoid use as much as
adding a new keyword. Personally I don't mind using keywords for different
stuff as long as it cannot conflict. That is in this case true for both
include and use.
how about 'possesses' or 'exhibits' - both these words are closer to the
natural language usage of 'trait' with regard to a subject.
John exhibits a **** trait
Jack possesses a **** trait
a person coming accross 'use' or 'include' in the context of
trait attribution may either make assumptions or become confused as to
possible changes/additions to the use and/or include functionality, a
new keyword that aptly describes the intention will more likely force
users to actually find out what it means.
an another alternative might be 'applies' - which doesn't fit the
natural language usage of 'trait' but does succintly describe what is happening.
just a thought.
marcus
Tuesday, February 19, 2008, 9:31:29 PM, you wrote:
Hi Stefan,
Am Montag, den 18.02.2008, 20:27 +0100 schrieb php@stefan-marr.de:
[...]class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
/* ... */
}I'm not sure if the use-keyword is a good idea as namespaces are already
"used". If we use "use" for traits, maybe going back to "import" for
namespaces would be the way to go.cu, Lars
Best regards,
Marcus
Best regards,
Marcus
Marcus, Jochem,
Although I agree with the general idea, I feel strongly against
possesses, mainly because for a non-native english speaker it's a pita
to write and will (and I'm sure of this) very often be misspelled
(leading to general annoyances while debugging). I can't give any better
fitting keywords for it though, but possesses is definitly not the best
choice imo.
- Tul
Marcus Boerger wrote:
Hello Jochem,
good arguments. And good ideas. I'd favor 'posesses' then.
marcus
Tuesday, February 19, 2008, 9:54:09 PM, you wrote:
firstly, I'd like to reiterate the general sentiment
that Stefans RFC is blinding! (that's a good thing in this context ;-)Marcus Boerger schreef:
Hello Lars,
we could even go for include here if we wanted to avoid use as much as
adding a new keyword. Personally I don't mind using keywords for different
stuff as long as it cannot conflict. That is in this case true for both
include and use.how about 'possesses' or 'exhibits' - both these words are closer to the
natural language usage of 'trait' with regard to a subject.John exhibits a **** trait
Jack possesses a **** traita person coming accross 'use' or 'include' in the context of
trait attribution may either make assumptions or become confused as to
possible changes/additions to the use and/or include functionality, a
new keyword that aptly describes the intention will more likely force
users to actually find out what it means.an another alternative might be 'applies' - which doesn't fit the
natural language usage of 'trait' but does succintly describe what is happening.just a thought.
marcus
Tuesday, February 19, 2008, 9:31:29 PM, you wrote:
Hi Stefan,
Am Montag, den 18.02.2008, 20:27 +0100 schrieb php@stefan-marr.de:
[...]class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
/* ... */
}
I'm not sure if the use-keyword is a good idea as namespaces are already
"used". If we use "use" for traits, maybe going back to "import" for
namespaces would be the way to go.
cu, LarsBest regards,
MarcusBest regards,
Marcus
how about 'possesses' or 'exhibits' - both these words are closer to the
natural language usage of 'trait' with regard to a subject.John exhibits a **** trait
Jack possesses a **** traita person coming accross 'use' or 'include' in the context of
trait attribution may either make assumptions or become confused as to
possible changes/additions to the use and/or include functionality, a
new keyword that aptly describes the intention will more likely force
users to actually find out what it means.
IMO, the keyword should be a verb because the code is instructing the
class to add a behavior. "possesses" and "exhibits" are adjectives
which doesn't make much sense. My vote would be something like
"acquire".
Regards,
John Campbell
John Campbell schreef:
how about 'possesses' or 'exhibits' - both these words are closer to the
natural language usage of 'trait' with regard to a subject.John exhibits a **** trait
Jack possesses a **** traita person coming accross 'use' or 'include' in the context of
trait attribution may either make assumptions or become confused as to
possible changes/additions to the use and/or include functionality, a
new keyword that aptly describes the intention will more likely force
users to actually find out what it means.IMO, the keyword should be a verb because the code is instructing the
class to add a behavior. "possesses" and "exhibits" are adjectives
which doesn't make much sense. My vote would be something like
"acquire".
that's a good point - someone else mentioned 'consumes' which also seems
to fit quite nicely.
my main point was to use a different keyword to either 'use' or 'include'
to avoid misconception, assumptions and basically get anyone not familiar
with the syntax to actually RTFM (I'd probably be guilty of glossing
over it, if I saw 'include' myself ... ;-)
personally I like 'exhibits' ... it sounds right, to me. I don't agree with
the argument that non english speaking users would have problems with such a
word any more than most other keywords already in use (e.g. 'implements') ...
although the spelling of 'possesses' could indeed be rather annoying :-)
Regards,
John Campbell
Hi,
Am Montag, den 18.02.2008, 20:27 +0100 schrieb php@stefan-marr.de:
[...]
To underpin this relationship, it is possible to declare that a Trait
implements an interface like this::<?php
interface IHello {
public function sayHello();
}trait SayHello implements IHello {
public function sayHello() {
echo 'Hello World!';
}
}class MyHelloWorld {
use SayHello;
}$o = new MyHelloWorld();
var_dump($o instanceof IHello); // bool(true)
We have discussed that in IRC a bit and a lot of questions remain. The
most important issue to me how to handle interface implementations in
cases where methods from the interface implementing trait are renamed in
the trait consumer class.
I would propose to not overcomplicate things here and I see no real
advantage in allowing traits to implement interfaces. I think also for
the sake of conceptual integrity separating interfaces clearly from
traits is a good idea: interfaces define structure while traits are
function buckets. A class may use traits, may implement interfaces and
may extend another class. This paradigm is pretty easy to explain to the
user.
cu, Lars
Lars Strojny
Senior Software Developer MediaVentures GmbH (http://mediaventures.de)
most important issue to me how to handle interface implementations in
cases where methods from the interface implementing trait are renamed in
the trait consumer class.
Renaming poses problem not only with interfaces. Imagine trait having
these methods:
function add($key, $value) { ... }
function delete($key) { ... }
function replace($key, $value) { $this->delete($key); $this->add($key,
$value); }
What happens if add() is renamed or replaced with function from other
trait/interface or dropped?
--
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi Stas,
Am Dienstag, den 19.02.2008, 14:58 -0800 schrieb Stanislav Malyshev:
[...]
Renaming poses problem not only with interfaces. Imagine trait having
these methods:function add($key, $value) { ... }
function delete($key) { ... }
function replace($key, $value) { $this->delete($key); $this->add($key,
$value); }What happens if add() is renamed or replaced with function from other
trait/interface or dropped?
As the $this is resolved after flattening, the delete()-method of the
current class is used, isn't it?
cu, Lars
As the $this is resolved after flattening, the delete()-method of the
current class is used, isn't it?
Exactly, and who knows if it does whatever replace() needs it to do?
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi Stas,
Am Dienstag, den 19.02.2008, 15:59 -0800 schrieb Stanislav Malyshev:
[...]
Exactly, and who knows if it does whatever replace() needs it to do?
As traits are fixed compositions in contrast to the dynamic concept
mixin it's in the hands of the developer to let replace() do the right
thing [tm] as the developer has all the information he needs to decide
what replace() needs to do when writing code.
cu, Lars
Hi!
As traits are fixed compositions in contrast to the dynamic concept
mixin it's in the hands of the developer to let replace() do the right
thing [tm] as the developer has all the information he needs to decide
what replace() needs to do when writing code.
replace() does the right thing - it uses add() and delete(). The problem
here is that current proposal allows any user to yank the ground from
under the feet of the trait API creator and replace various bits of the
class with any other functionality without any regard for the
interdependency between them, so either each function in the traits
should be completely self-reliant and never use other functions - which
prevents one from create complete non-trivial APIs - or every rename
should be painfully verified with original trait developer or against
the actual source code, breaking the abstraction. Both don't seem too
practical to me.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi,
Am Dienstag, den 19.02.2008, 16:37 -0800 schrieb Stanislav Malyshev:
[...]
replace() does the right thing - it uses add() and delete(). The problem
here is that current proposal allows any user to yank the ground from
under the feet of the trait API creator and replace various bits of the
class with any other functionality without any regard for the
interdependency between them, so either each function in the traits
should be completely self-reliant and never use other functions - which
prevents one from create complete non-trivial APIs - or every rename
should be painfully verified with original trait developer or against
the actual source code, breaking the abstraction. Both don't seem too
practical to me.
To allow myself a final judgement, I must play around with it of course
but I have the feeling, that managing interdependency wouldn't be that
hard. Normally you would just use the trait. If you are brave and really
know what you are doing, you can rename and excludes symbols.
cu, Lars
Hello Stanislav,
if we go the route of allowing that, then we have to do some protocl
checks. Meaning if a renamed/aliased method will become an implementation of
an abstract method then of course it has to be compatible. The beigger
problem I assume you have in mind as well is that replace might be
implemented in a way that it uses both delete and add. If now add gets
dropped or renamed then there is a problem, called missing function. We
could avoid that easily if we have a compiler that allowed us to check
which functions are being called. Becasue then we could easily prevent a
used function from getting renamed. But all I just said does not really
sound like the PHP approach. It is something I would do in my own language.
In PHP we always allowed all kinds of nasty. We even hesitate to increase
the severity of any E_STRICT. Not even if it would be better for opcode
caches. We not even get rid of the insanest stuff... So where do we want to
go today with this?
marcus
Tuesday, February 19, 2008, 11:58:06 PM, you wrote:
most important issue to me how to handle interface implementations in
cases where methods from the interface implementing trait are renamed in
the trait consumer class.
Renaming poses problem not only with interfaces. Imagine trait having
these methods:
function add($key, $value) { ... }
function delete($key) { ... }
function replace($key, $value) { $this->delete($key); $this->add($key,
$value); }
What happens if add() is renamed or replaced with function from other
trait/interface or dropped?
--
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Best regards,
Marcus
Hi!
if we go the route of allowing that, then we have to do some protocl
checks. Meaning if a renamed/aliased method will become an implementation of
an abstract method then of course it has to be compatible. The beigger
I'm kind of confused on a use case for that. Suppose you have trait TFoo
that does some API - like implementing serialization, for example. And
then you have some abstract method bar() that is supposed to do
something unrelated - like convert pounds to kilograms. And then you
just rename one of the methods of TFoo into bar() and suddenly it starts
being used for converting pounds to kilograms just because it has the
same argument count? I don't see any sane application structure working
this way. What would be the use case of taking methods from one API and
using them as implementation for totally unrelated parts of other API?
What if one of the APIs changes? What if change required to implement
TFoo but bar() should stay the same? This just looks like very cludgy
attempt to avoid typing a few extra lines, which will definitely will
come back to bite the developer in the future. I think this is important
- to figure out what is the good use case for this.
dropped or renamed then there is a problem, called missing function. We
could avoid that easily if we have a compiler that allowed us to check
which functions are being called. Becasue then we could easily prevent a
We can't - as I already mentioned, call resolution is dynamic in PHP,
which means compiler can't check which methods are called. On top of
that, nobody said methods are always to be called on $this with ->
operator - there are many other ways to call a method on an object.
In PHP we always allowed all kinds of nasty. We even hesitate to increase
No, we didn't allow all kinds of nasty. PHP never had dynamic method
addition, dynamic object/class redefinition, etc. even though
engine-wise it's completely possible. That's the domain of hackish
extensions like runkit and IMO it should stay there - in it's own closet
which is clearly labeled "beware of the leopard". I would have no
objection for runkit to have traits-like functions - in fact, I think it
can do it right now. Having this in core is another, very different
business.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
2008/2/19, Lars Strojny lars@strojny.net:
Hi,
Am Montag, den 18.02.2008, 20:27 +0100 schrieb php@stefan-marr.de:
[...]To underpin this relationship, it is possible to declare that a Trait
implements an interface like this::<?php
interface IHello {
public function sayHello();
}trait SayHello implements IHello {
public function sayHello() {
echo 'Hello World!';
}
}class MyHelloWorld {
use SayHello;
}$o = new MyHelloWorld();
var_dump($o instanceof IHello); // bool(true)We have discussed that in IRC a bit and a lot of questions remain. The
most important issue to me how to handle interface implementations in
cases where methods from the interface implementing trait are renamed in
the trait consumer class.
Well, it is aliasing, not renaming.
Think only the exclusion of a method will break the interface, but
since the interface is propagated to the class the class has to take
care that it is implemented properly and an error will be raised if it
is not implemented correctly at compile time.
Let say I modify the example like this:
class MyHelloWorld {
use SayHello {
!sayHello
}
}
the compiler will give an error that sayHello is missing since
MyHelloWorld states it is implementing IHello.
I would propose to not overcomplicate things here and I see no real
advantage in allowing traits to implement interfaces. I think also for
the sake of conceptual integrity separating interfaces clearly from
traits is a good idea: interfaces define structure while traits are
function buckets. A class may use traits, may implement interfaces and
may extend another class. This paradigm is pretty easy to explain to the
user.
Yes it is definitely a conceptual problem to propagate interfaces,
because the notation does not imply this behavior and you can not see
the interfaces added by the trait.
Therefore, I'm with you, interface propagation might be a point to not
be included.
Kind Regards
Stefan
Hi,
Lars Strojny schreef:
Hi,
Am Montag, den 18.02.2008, 20:27 +0100 schrieb php@stefan-marr.de:
[...]To underpin this relationship, it is possible to declare that a Trait
implements an interface like this::<?php
interface IHello {
public function sayHello();
}trait SayHello implements IHello {
public function sayHello() {
echo 'Hello World!';
}
}class MyHelloWorld {
use SayHello;
}$o = new MyHelloWorld();
var_dump($o instanceof IHello); // bool(true)We have discussed that in IRC a bit and a lot of questions remain. The
most important issue to me how to handle interface implementations in
cases where methods from the interface implementing trait are renamed in
the trait consumer class.
I would propose to not overcomplicate things here and I see no real
advantage in allowing traits to implement interfaces. I think also for
the sake of conceptual integrity separating interfaces clearly from
traits is a good idea: interfaces define structure while traits are
function buckets. A class may use traits, may implement interfaces and
may extend another class. This paradigm is pretty easy to explain to the
user.
if a trait would could contain all the methods required to implement an interface
and a class uses it (the trait) and implements the relevant interface would it
(the interface) be satified?
also might it be an idea to allow a trait to specify that it implements an interface
for the purposes of development (errors triggered due to incorrect/incomplete implementation)
BUT not have the interface be carried to any classes that use the trait? ... classes
would have to explicitly state that they implement the interface and the fact that they
use a trait to do so would be irrelevant as far as 'where' the method came from.
interface iFoo {
function doFoo();
}
// causes a compile time error due to missing function
//
trait tOneFoo implements iFoo {
function doBar() { echo "hello"; }
}
// no compile time error
//
trait tTwoFoo implements iFoo {
function doFoo() { echo "hello"; }
}
// using a trait that implements something without actually
// taking on the implementation
//
class cOneFoo {
uses tTwoFoo;
}
// any iFoo method exclusion, aliasing, renaming
// in the following class would cause a compile time error
// due to missing iFoo function unless the class itself
// defined the method(s) in question
//
class cTwoFoo implements iFoo {
uses tTwoFoo;
}
$one = new cOneFoo;
$two = new cTwoFoo;
// outputs: false, true
var_dump( ($one instanceof iFoo), ($one instanceof iFoo) );
something like the above might offer developers desired OO strictness
without actually blurring the boundaries between trait and interface
conceptually.
cu, Lars
Hi Jochem,
Am Mittwoch, den 20.02.2008, 00:06 +0100 schrieb Jochem Maas:
[...]
if a trait would could contain all the methods required to implement
an interface and a class uses it (the trait) and implements the
relevant interface would it (the interface) be satified?
Yes. The following should work:
interface IFoo {
public function method();
}
trait TFoo {
public functin method() {
echo "Hello World!";
}
}
class CFoo implements IFoo
{
use TFoo;
}
This would be fine.
also might it be an idea to allow a trait to specify that it
implements an interface for the purposes of development (errors
triggered due to incorrect/incomplete implementation)
BUT not have the interface be carried to any classes that use the
trait?
I don't see any sence in it. Why should one generalize an interface of a
trait?
cu, Lars
Lars Strojny schreef:
Hi Jochem,
Am Mittwoch, den 20.02.2008, 00:06 +0100 schrieb Jochem Maas:
[...]if a trait would could contain all the methods required to implement
an interface and a class uses it (the trait) and implements the
relevant interface would it (the interface) be satified?Yes. The following should work:
interface IFoo {
public function method();
}trait TFoo {
public functin method() {
echo "Hello World!";
}
}class CFoo implements IFoo
{
use TFoo;
}This would be fine.
also might it be an idea to allow a trait to specify that it
implements an interface for the purposes of development (errors
triggered due to incorrect/incomplete implementation)
BUT not have the interface be carried to any classes that use the
trait?I don't see any sence in it. Why should one generalize an interface of a
trait?
I don't suppose you would, I thinking more along the lines of having it
a developer tool - using implements on a trait would force the developer to
put in the correct methods, it might help a team that had say 1 interface, 2 traits
(which both contain a set of methods that satify said interface) and a large number
of classes that implement one or other of the traits ... having the trait state
that it's capable of handling an implementation might save some WTF because
compile time errors would happen on the trait if it broke the interface signature,
rather than on the class ... an interface related error on the class it might not be
obvious to the developer with regard to the fact that the/a trait the class is using
was for the purpose of satifying an interface the class states it is implementing.
I'm thinking that it will proably occur quite often that a fairly simple interface
will be 'covered' by a trait and that said trait would be used solely for satifying
said interface implementation and used as such for classes that wish to implement
said interface.
to quote Troels:
"Class inheritance (The extends
keyword) inherits type
- implementation. Interface inheritance (The
implements
keyword)
inherits type (but not implementation). Traits fill the missing hole
by allowing inheritance of implementation, but not type."
that sounds more than reasonable, but it might be worth offering an aid
to developers during the compile time phase, with regard to the 'link' between
a trait and an interface (assuming you would agree that it's not unlikely that
the two would be used in tandem on occasion) without imposing/implying anything
at run time (i.e. a trait may implement an interface to ensure correctness but
that has no effect on any class that uses it, classes must explicitly state it's
intention to implement an interface)
I may have said something stupid - but given the ability of your average php user
to stupid things with the functionality offered to him/her, my comments might
aid in thrashing out such details in order to limit a user's ability to do stupid
things :-)
cu, Lars
Hi Jochem,
Am Mittwoch, den 20.02.2008, 01:20 +0100 schrieb Jochem Maas:
[...]
that sounds more than reasonable, but it might be worth offering an
aid to developers during the compile time phase, with regard to the
'link' between a trait and an interface (assuming you would agree that
it's not unlikely that the two would be used in tandem on occasion)
without imposing/implying anything at run time (i.e. a trait may
implement an interface to ensure correctness but that has no effect on
any class that uses it, classes must explicitly state it's intention
to implement an interface)
I totally understand what you are trying to outline but I have the
feeling that it would introduce a huge amount of complexity without
adequate benefits. But that's of course just my opinion.
cu, Lars
Lars Strojny
Senior Software Developer MediaVentures GmbH (http://mediaventures.de)
I think also for
the sake of conceptual integrity separating interfaces clearly from
traits is a good idea: interfaces define structure while traits are
function buckets. A class may use traits, may implement interfaces and
may extend another class. This paradigm is pretty easy to explain to the
user.
Definitely. Class inheritance (The extends
keyword) inherits type
- implementation. Interface inheritance (The
implements
keyword)
inherits type (but not implementation). Traits fill the missing hole
by allowing inheritance of implementation, but not type.
--
troels
Lars Strojny schrieb:
I think also for the sake of conceptual integrity separating
interfaces clearly from traits is a good idea
Interfaces are about (multiple) interface inheritance and traits are
about (multiple) implementation inheritance. This separation of
interface inheritance and implementation inheritance is ... awesome :)
--
Sebastian Bergmann http://sebastian-bergmann.de/
GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69
Lars Strojny schrieb:
I think also for the sake of conceptual integrity separating
interfaces clearly from traits is a good ideaInterfaces are about (multiple) interface inheritance and traits are
about (multiple) implementation inheritance. This separation of
interface inheritance and implementation inheritance is ... awesome :)
Is there any reason why we cannot support multiple inheritance (which
some other languages already do)? The only thing that needs to be
clearly defined is sorting order. This avoids trying to create some
entirely new functionality to do something where we can simply extend
what we already do.
--
Geoffrey Sneddon
<http://gsnedders.com/
Hi!
Is there any reason why we cannot support multiple inheritance (which
Yes, there is. Ambiguity.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi Geoffrey,
Am Mittwoch, den 20.02.2008, 20:14 +0000 schrieb Geoffrey Sneddon:
[...]
Is there any reason why we cannot support multiple inheritance (which
some other languages already do)? The only thing that needs to be
clearly defined is sorting order. This avoids trying to create some
entirely new functionality to do something where we can simply extend
what we already do.
Did you read the academic paper about the purpose of traits? In short:
traits fix the common misconceptions of multiple inheritance. The most
common problem is risky and fragile hierarchies. Also traits do the
right thing[tm]: they allow compositions, not uncontrolable hotchpotch
architectures.
cu, Lars
Am Mittwoch, den 20.02.2008, 20:14 +0000 schrieb Geoffrey Sneddon:
[...]Is there any reason why we cannot support multiple inheritance (which
some other languages already do)? The only thing that needs to be
clearly defined is sorting order. This avoids trying to create some
entirely new functionality to do something where we can simply extend
what we already do.Did you read the academic paper about the purpose of traits? In short:
traits fix the common misconceptions of multiple inheritance. The most
common problem is risky and fragile hierarchies. Also traits do the
right thing[tm]: they allow compositions, not uncontrolable hotchpotch
architectures.
Oh, wait. The previous email I was going to send in this thread was
still a draft. That explains why that comment makes so little sense :)
What I was going to say is that how is something like:
<?php
trait foo {
function output() {
echo 'foo';
}
}
trait bar {
function output() {
echo 'bar';
}
}
class foobar {
use foo;
use bar;
}
?>
Better than:
<?php
class foo {
function output() {
echo 'foo';
}
}
class bar {
function output() {
echo 'bar';
}
}
class foobar extends foo, bar {
}
?>
Both are ambiguous if there is no documented conflict resolution: just
because there's a proposal for traits doesn't mean you can't clearly
define conflict resolution for multiple inheritance. I don't see
either being intrinsically better than the other (and yes, I have read
the papers about both).
--
Geoffrey Sneddon
<http://gsnedders.com/
Hi,
Am Montag, den 18.02.2008, 20:27 +0100 schrieb php@stefan-marr.de:
[...]
Thank you for your attention and I'm looking forward to hear your comments
:)
What about abstract methods in traits? I think this could be handy to
enforce the user class implement a data getter.
trait Foo {
public function doSomething()
{
return str_replace("foo", "bar", $this->_getString());
}
abstract protected _getString();
}
class Consumer
{
use Foo;
protected function _getString()
{
return "foo";
}
}
What do you think?
cu, Lars
Lars Strojny schrieb:
What about abstract methods in traits?
I do not think that this would make sense. Traits are about reusing
code. Abstract methods do not hold concrete code.
--
Sebastian Bergmann http://sebastian-bergmann.de/
GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69
Hi Lars,
What about abstract methods in traits? I think this could be handy to
enforce the user class implement a data getter.trait Foo {
public function doSomething()
{
return str_replace("foo", "bar", $this->_getString());
}
abstract protected _getString();
}class Consumer
{
use Foo;
protected function _getString()
{
return "foo";
}
}What do you think?
I am sorry :) This part has been missing in the RFC.
But is already implemented in the patch and supposed to work exactly
like you has described it.
Kind Regards
Stefan
Hi Lars,
What about abstract methods in traits? I think this could be handy to
enforce the user class implement a data getter.trait Foo {
public function doSomething()
{
return str_replace("foo", "bar", $this->_getString());
}
abstract protected _getString();
}class Consumer
{
use Foo;
protected function _getString()
{
return "foo";
}
}What do you think?
I am sorry :) This part has been missing in the RFC.
But is already implemented in the patch and supposed to work exactly
like you has described it.
Maybe its best to first leave out interfaces/abstract etc. Start
simple, see how things get used, get more comfortable with the feature
and then see about potentially expanding it.
regards,
Lukas
Hi Lukas,
Am Mittwoch, den 20.02.2008, 09:44 +0100 schrieb Lukas Kahwe Smith:
[...]
Maybe its best to first leave out interfaces/abstract etc. Start
simple, see how things get used, get more comfortable with the feature
and then see about potentially expanding it.
I totally agree also I would like to have abstract methods in traits
(and don't beat me, I would like interface now too, for conceptual
integrity but the implemented interfaces should not derived by the using
classes). But: let us try to include a simple version of traits, without
abstract methods and trait interfaces in 5.3, get a huge amount of
feedback from the community and than think about implementing the more
complex stuff. I would like to see the following featureset:
* Method renaming (method => newMethod)
* Method hiding (method => null, method => false, both might work)
* No interfaces
* No abstract methods
* No visibility modifications
The last three point could be done for 5.4.
cu, Lars
Thank you for your attention and I'm looking forward to hear your comments
And thank you for this very detailed proposal, I wish most other feature
proposals were like this. I like the concept, it seems to solve some
of the problems we had to hack around now as well. I've two quick
questions for you:
- can traits be put in a file of their own (I assume so actually)
- can traits be autoloaded?
regards,
Derick
--
Derick Rethans
http://derickrethans.nl | http://ezcomponents.org | http://xdebug.org
Hi Derick,
- can traits be put in a file of their own (I assume so actually)
- can traits be autoloaded?
yes, since traits are zend_class_entrys with ce_flags set to
ZEND_ACC_TRAIT in the current implementation, they will follow the
same rules for definition and autoloading like classes.
Kind Regards
Stefan
Hi,
generally asking, how would Reflection act on traits? Can this be
detected or will the information be lost after a class has been
assembled? E.g. detect if a certain method comes from a trait and which one?
lG
Markus Fischer wrote:
generally asking, how would Reflection act on traits? Can this be
detected or will the information be lost after a class has been
assembled? E.g. detect if a certain method comes from a trait and which
one?
Dammit, sorry, I didn't intended to send it to all individuals.
- Markus
Hi Markus,
Am Mittwoch, den 20.02.2008, 12:13 +0100 schrieb Markus Fischer:
[...]
generally asking, how would Reflection act on traits? Can this be
detected or will the information be lost after a class has been
assembled? E.g. detect if a certain method comes from a trait and which one?
Good question. The current patch does not include anything about
reflections. But as the information about traits is present in the
struct zend_class_entry is is possible to expose trait information via
the reflection API. I would propose something like this:
class ReflectionTrait:
- getMethods(): array[ReflectionMethod]
- ... pretty similiar to ReflectionClass
extending class ReflectionClass:
- getTraits(): array[ReflectionTrait]
- usesTraits(): bool
extending class ReflectionMethod:
- flattened(): bool
- getTrait(): ReflectionTrait
Maybe ReflectionMethod::flattened() should be
ReflectionMethod::belongsToTrait() or something like this, I'm not sure
here.
cu, Lars
Hi,
Markus Fischer schrieb:
Hi,
generally asking, how would Reflection act on traits? Can this be
detected or will the information be lost after a class has been
assembled? E.g. detect if a certain method comes from a trait and which
one?
think will work on the reflection part when details on the semantics has
been finished.
Kind Regards
Stefan
Hi,
I've updated the RFC on several sections to reflect the discussion on this list.
The following parts have been changed or added:
- introduced explicit description of abstract methods to be used as
requirements
specification for traits (useful to access state) - moved part about interface propagation to the section of rejected features
- added a section about common misconceptions i.e. aliasing is not renaming
- added various syntax proposals
The important parts are included below. The new version of the RFC is available
at http://www.stefan-marr.de/rfc-traits-for-php.txt
and http://www.stefan-marr.de/artikel/rfc-traits-for-php.html
Some people proposed to implement only a subset of the proposed features,
especially !renaming! and abstract methods and visibility changes has
been mentioned to postpone on
a later version. I disagree on some of them.
At first, there is no renaming :) Hope, we could get this clear in the
next few days.
May be I should change the way of explaining it, but I've added the part about
misconceptions in the first place.
Second, abstract methods is a really common concept, the have the same
semantics like
for classes. So I expect them to be used the same way.
Third, the changes of visibility. Ok, they are nice but not essentially.
Would be ok to leave them out for now.
The next step could be to try to agree on one of the proposed notations?
Kind Regards
Stefan
Express Requirements by Abstract Methods
""""""""""""""""""""""""""""""""""""""""
Since Traits do not contain any state/properties, there is a need to describe
the requirements a Trait will rely on. In PHP it would be possible to utilize
the dynamic language features, but it is a common practice to give this
requirements explicitly.
This is possible with abstract methods like it is used for abstract classes.
::
<?php
trait Hello {
public function sayHelloWorld() {
echo 'Hello'.$this->getWorld();
}
abstract public function getWorld();
}
class MyHelloWorld {
private $world;
use Hello;
public function getWorld() {
return $this->world;
}
public function setWorld($val) {
$this->world = $val;
}
}
?>
The usage of abstract methods allows to state not always obvious relation ships
and requirements explicitly. It is favored over the implicit usage of the
dynamic method resolution and property creation in the context of complex
projects for the sake of readability.
Common Misconceptions
Aliasing vs. Renaming
The presented aliasing operation has not a semantic of renaming.
Instead it does only provide a new name to be able to invoke the original
method with this new name even if the original name was excluded.
::
<?php
trait A {
public function a() {
echo 'a';
$this->b();
}
public function b() {
echo 'b';
}
}
class Foo {
use A {
c => b
}
}
$foo = new Foo();
$foo->a(); //echos ab
$foo->b(); //echos b
$foo->c(); //echos b
?>
Since it is not renaming the original method b is still available and has not
been influenced at all.
Alternative Keywords for use
""""""""""""""""""""""""""""
The keyword use is already reserved for the new namespace feature.
Thus, alternative keywords already proposed on the mailing list are listed
here::
[1] exhibit
class Foo {
exhibit Bar;
}
[2] possess
class Foo {
possess Bar;
}
Alternative Expression of Exclusion
"""""""""""""""""""""""""""""""""""
Some people do not like the notation with the exclamation mark.
Possible alternatives::
[1] not keyword
use Trait {
not foo1, foo2;
bar => foo1
}
[2] without keyword
use Trait {
without foo1, foo2;
bar => foo1
}
Alternatives for the Aliasing Notation
""""""""""""""""""""""""""""""""""""""
Aliasing is often misunderstood as renaming.
May be some of the following notations will help::
[1] is keyword instead of the arrow
use Trait {
bar is foo1; //methodAlias is method
}
Interpretation: is
state something about bar
, there is nothing stated
about foo1
.
Alternative keyword::
[2] from
use Trait {
bar from foo1; //methodAlias from method
}
Or an very explicit variation::
[3] from
use Trait {
alias bar as foo1;
}
Hello Stefan,
the biggest issue here is that renaming is still possible. So meanwhile I
think that the best approach would be to only allow a method to be inherited
as private. So instead of hiding a method completely you get it as a private
one. Thus if it collides with an interface you get an error. If not and you
still want it to implement an abstract method you simply aliase it.
marcus
Wednesday, February 20, 2008, 7:00:52 PM, you wrote:
Hi,
I've updated the RFC on several sections to reflect the discussion on this list.
The following parts have been changed or added:
- introduced explicit description of abstract methods to be used as
requirements
specification for traits (useful to access state)- moved part about interface propagation to the section of rejected features
- added a section about common misconceptions i.e. aliasing is not renaming
- added various syntax proposals
The important parts are included below. The new version of the RFC is available
at http://www.stefan-marr.de/rfc-traits-for-php.txt
and http://www.stefan-marr.de/artikel/rfc-traits-for-php.html
Some people proposed to implement only a subset of the proposed features,
especially !renaming! and abstract methods and visibility changes has
been mentioned to postpone on
a later version. I disagree on some of them.
At first, there is no renaming :) Hope, we could get this clear in the
next few days.
May be I should change the way of explaining it, but I've added the part about
misconceptions in the first place.
Second, abstract methods is a really common concept, the have the same
semantics like
for classes. So I expect them to be used the same way.
Third, the changes of visibility. Ok, they are nice but not essentially.
Would be ok to leave them out for now.
The next step could be to try to agree on one of the proposed notations?
Kind Regards
Stefan
Express Requirements by Abstract Methods
""""""""""""""""""""""""""""""""""""""""
Since Traits do not contain any state/properties, there is a need to describe
the requirements a Trait will rely on. In PHP it would be possible to utilize
the dynamic language features, but it is a common practice to give this
requirements explicitly.
This is possible with abstract methods like it is used for abstract classes.
::
<?php
trait Hello {
public function sayHelloWorld() {
echo 'Hello'.$this->getWorld();
}
abstract public function getWorld();
}
class MyHelloWorld {
private $world;
use Hello;
public function getWorld() {
return $this->world;
}
public function setWorld($val) {
$this->world = $val;
}
}
?>>
The usage of abstract methods allows to state not always obvious relation ships
and requirements explicitly. It is favored over the implicit usage of the
dynamic method resolution and property creation in the context of complex
projects for the sake of readability.
Common Misconceptions
Aliasing vs. Renaming
The presented aliasing operation has not a semantic of renaming.
Instead it does only provide a new name to be able to invoke the original
method with this new name even if the original name was excluded.
::
<?php
trait A {
public function a() {
echo 'a';
$this->b();
}
public function b() {
echo 'b';
}
}
class Foo {
use A {
c => b
}
}
$foo = new Foo();
$foo->a(); //echos ab
$foo->b(); //echos b
$foo->c(); //echos b
?>>
Since it is not renaming the original method b is still available and has not
been influenced at all.
Alternative Keywords for use
""""""""""""""""""""""""""""
The keyword use is already reserved for the new namespace feature.
Thus, alternative keywords already proposed on the mailing list are listed
here::
[1] exhibit
class Foo {
exhibit Bar;
}
[2] possess
class Foo {
possess Bar;
}
Alternative Expression of Exclusion
"""""""""""""""""""""""""""""""""""
Some people do not like the notation with the exclamation mark.
Possible alternatives::
[1] not keyword
use Trait {
not foo1, foo2;
bar => foo1
}
[2] without keyword
use Trait {
without foo1, foo2;
bar => foo1
}
Alternatives for the Aliasing Notation
""""""""""""""""""""""""""""""""""""""
Aliasing is often misunderstood as renaming.
May be some of the following notations will help::
[1] is keyword instead of the arrow
use Trait {
bar is foo1; //methodAlias is method
}
Interpretation:
is
state something aboutbar
, there is nothing stated
aboutfoo1
.
Alternative keyword::
[2] from
use Trait {
bar from foo1; //methodAlias from method
}
Or an very explicit variation::
[3] from
use Trait {
alias bar as foo1;
}
Best regards,
Marcus
Hi Stefan,
Thanks for writing such a good proposal.
In general I think traits is a very interesting idea. I can think of a few cases in my life as a programmer where it could have helped me out.
I think some comments re: the syntax/naming are good ones but I prefer not to get into naming in this email. I believe we can brainstorm re: naming when we figure out exactly what we want to do. I do support though finding a way which is simple for non-English speakers but not too simple like Perl:)
Thinking back at the times when something like this would have been useful to me I think there are a couple of main points I'd like to brainstorm about:
a)
I think Traits should be able to act as a self-contained behavior which can always be expected to work. For example if I want a Counter behavior I would like that not to depend on the properties in the containing class. While I don't think we should enable public nor protected properties in Traits I think allowing for private properties in Traits would come in very handy. It also is no problem when it comes to mangling as we can use the Trait name.
class Trait {
private $counter = 0;
function getNextSerialNumber() {
return $this->counter++;
}
}
I strongly recommend not to support protected/public and not to even get into the discussion of dealing with conflicts of such properties. But I think private is very useful.
b)
I haven't thought this through completely but I think we may want to consider a different model from supporting "removing" and "aliasing" of functions. I think this can create a lot of complications especially in larger works and it'll be write-once code and hard for a newcomer to the code to really understand.
I think the main reason to support removing/aliasing of functions is in order to avoid conflicts with other Traits/Class methods. Maybe what we can do is to have only "aliasing" but what it would do is to create a public function with the new name and convert the old function into a private function.
Benefits:
- Keeps the old code from running predictably without breaking dependencies.
- Possibly even allowing some form of "is-a" relationship to continue to be valid (and therefore the interface discussion may even be resolved; at least to a certain level). In the case I faced an is-a relationship (i.e. working instanceof operator) would have been nice.
I need to put a bit more thought into this but it's an initial attempt to provide an additional point of view which may actually give some more structure to the proposal while still delivering the benefits of this idea.
I think if this kind of idea could be fully baked it would solve the problems I have had in the past possibly without the more dangerous renaming/removing of functions. I have not looked into feasibility of this idea yet.
Anyway, good ideas and thanks for the very professional way you have brought this idea to the community.
Let's try and nail down the behavior and then we can have the harder naming discussion :) Just saying harder because I've always considered naming the hardest part in software engineering...
Andi
a)
I think Traits should be able to act as a self-contained behavior which can
always be expected to work. For example if I want a Counter behavior I
would like that not to depend on the properties in the containing class.
While I don't think we should enable public nor protected properties in
Traits I think allowing for private properties in Traits would come in very
handy. It also is no problem when it comes to mangling as we can use the
Trait name.class Trait {
private $counter = 0;
function getNextSerialNumber() {
return $this->counter++;
}
}I strongly recommend not to support protected/public and not to even get
into the discussion of dealing with conflicts of such properties. But I
think private is very useful.
Hi Andi. Why no ppp on Traits? It seems useful to be able to pull in a
utility function or functions that can be leveraged from other methods, like
domain-specific parsers, without exposing them to the outside world. Is it
an engine-level implementation issue, or a conceptual complexity issue? I
don't really see a complexity issue on the developer level, at least for me.
--
Larry Garfield AIM: LOLG42
larry@garfieldtech.com ICQ: 6817012
"If nature has made any one thing less susceptible than all others of
exclusive property, it is the action of the thinking power called an idea,
which an individual may exclusively possess as long as he keeps it to
himself; but the moment it is divulged, it forces itself into the possession
of every one, and the receiver cannot dispossess himself of it." -- Thomas
Jefferson
Because then you get into similar problems as with multiple inheritance
when you have property clashes and other issues.
The reason why private is beneficial and different because it will allow
you to develop self contained functionality which can be attached to
random classes. This can be quite useful for Timers, Counters,
Containers, etc...
Andi
-----Original Message-----
From: Larry Garfield [mailto:larry@garfieldtech.com]
Sent: Wednesday, February 20, 2008 9:54 PM
To: internals@lists.php.net
Subject: Re: [PHP-DEV] RFC: Traits for PHPa)
I think Traits should be able to act as a self-contained behavior
which can
always be expected to work. For example if I want a Counter behavior
I
would like that not to depend on the properties in the containing
class.
While I don't think we should enable public nor protected properties
in
Traits I think allowing for private properties in Traits would come
in very
handy. It also is no problem when it comes to mangling as we can use
the
Trait name.class Trait {
private $counter = 0;
function getNextSerialNumber() {
return $this->counter++;
}
}I strongly recommend not to support protected/public and not to even
get
into the discussion of dealing with conflicts of such properties.
But
I
think private is very useful.Hi Andi. Why no ppp on Traits? It seems useful to be able to pull in
a
utility function or functions that can be leveraged from other
methods,
like
domain-specific parsers, without exposing them to the outside world.
Is it
an engine-level implementation issue, or a conceptual complexity
issue?
I
don't really see a complexity issue on the developer level, at least
for me.--
Larry Garfield AIM: LOLG42
larry@garfieldtech.com ICQ: 6817012"If nature has made any one thing less susceptible than all others of
exclusive property, it is the action of the thinking power called an
idea,
which an individual may exclusively possess as long as he keeps it to
himself; but the moment it is divulged, it forces itself into the
possession
of every one, and the receiver cannot dispossess himself of it." --
Thomas
Jefferson
I am unclear on how this:
trait ParseThingie {
protected function parseThingie($thingie) {...}
}
trait ProcessThingie {
protected function parseThingie($thingie) {...}
}
class Thingies {
use ParseThingie;
use ProcessThingie;
}
allows more opportunity for confusion and clashes than this:
trait ParseThingie {
public function parseThingie($thingie) {...}
}
trait ProcessThingie {
public function parseThingie($thingie) {...}
}
class Thingies {
use ParseThingie;
use ProcessThingie;
}
(Presumably an un-qualified method would be public for traits just like for
classes.)
OK the above example is a bit of a strawman, but I don't know a non-strawman
example here. :-) That's the clarification I'm asking for.
Because then you get into similar problems as with multiple inheritance
when you have property clashes and other issues.
The reason why private is beneficial and different because it will allow
you to develop self contained functionality which can be attached to
random classes. This can be quite useful for Timers, Counters,
Containers, etc...Andi
-----Original Message-----
From: Larry Garfield [mailto:larry@garfieldtech.com]
Sent: Wednesday, February 20, 2008 9:54 PM
To: internals@lists.php.net
Subject: Re: [PHP-DEV] RFC: Traits for PHPa)
I think Traits should be able to act as a self-contained behaviorwhich can
always be expected to work. For example if I want a Counter behavior
I
would like that not to depend on the properties in the containing
class.
While I don't think we should enable public nor protected properties
in
Traits I think allowing for private properties in Traits would come
in very
handy. It also is no problem when it comes to mangling as we can use
the
Trait name.
class Trait {
private $counter = 0;
function getNextSerialNumber() {
return $this->counter++;
}
}I strongly recommend not to support protected/public and not to even
get
into the discussion of dealing with conflicts of such properties.
But
I
think private is very useful.
Hi Andi. Why no ppp on Traits? It seems useful to be able to pull in
a
utility function or functions that can be leveraged from othermethods,
like
domain-specific parsers, without exposing them to the outside world.
Is it
an engine-level implementation issue, or a conceptual complexityissue?
I
don't really see a complexity issue on the developer level, at least
for me.--
Larry Garfield AIM: LOLG42
larry@garfieldtech.com ICQ: 6817012"If nature has made any one thing less susceptible than all others of
exclusive property, it is the action of the thinking power called an
idea,
which an individual may exclusively possess as long as he keeps it to
himself; but the moment it is divulged, it forces itself into the
possession
of every one, and the receiver cannot dispossess himself of it." --
Thomas
Jefferson--
--
Larry Garfield AIM: LOLG42
larry@garfieldtech.com ICQ: 6817012
"If nature has made any one thing less susceptible than all others of
exclusive property, it is the action of the thinking power called an idea,
which an individual may exclusively possess as long as he keeps it to
himself; but the moment it is divulged, it forces itself into the possession
of every one, and the receiver cannot dispossess himself of it." -- Thomas
Jefferson
My email talked about properties not methods. Please re-read it :)
Andi
-----Original Message-----
From: Larry Garfield [mailto:larry@garfieldtech.com]
Sent: Wednesday, February 20, 2008 10:51 PM
To: internals@lists.php.net
Subject: Re: [PHP-DEV] RFC: Traits for PHPI am unclear on how this:
trait ParseThingie {
protected function parseThingie($thingie) {...}
}
trait ProcessThingie {
protected function parseThingie($thingie) {...}
}
class Thingies {
use ParseThingie;
use ProcessThingie;
}allows more opportunity for confusion and clashes than this:
trait ParseThingie {
public function parseThingie($thingie) {...}
}
trait ProcessThingie {
public function parseThingie($thingie) {...}
}
class Thingies {
use ParseThingie;
use ProcessThingie;
}(Presumably an un-qualified method would be public for traits just
like
for
classes.)OK the above example is a bit of a strawman, but I don't know a non-
strawman
example here. :-) That's the clarification I'm asking for.Because then you get into similar problems as with multiple
inheritance
when you have property clashes and other issues.
The reason why private is beneficial and different because it will
allow
you to develop self contained functionality which can be attached to
random classes. This can be quite useful for Timers, Counters,
Containers, etc...Andi
-----Original Message-----
From: Larry Garfield [mailto:larry@garfieldtech.com]
Sent: Wednesday, February 20, 2008 9:54 PM
To: internals@lists.php.net
Subject: Re: [PHP-DEV] RFC: Traits for PHPa)
I think Traits should be able to act as a self-contained
behaviorwhich can
always be expected to work. For example if I want a Counter
behaviorI
would like that not to depend on the properties in the
containingclass.
While I don't think we should enable public nor protected
propertiesin
Traits I think allowing for private properties in Traits would
comein very
handy. It also is no problem when it comes to mangling as we can
usethe
Trait name.
class Trait {
private $counter = 0;
function getNextSerialNumber() {
return $this->counter++;
}
}I strongly recommend not to support protected/public and not to
evenget
into the discussion of dealing with conflicts of such
properties.But
I
think private is very useful.
Hi Andi. Why no ppp on Traits? It seems useful to be able to
pull
in
a
utility function or functions that can be leveraged from othermethods,
like
domain-specific parsers, without exposing them to the outside
world.
Is it
an engine-level implementation issue, or a conceptual complexityissue?
I
don't really see a complexity issue on the developer level, at
least
for me.--
Larry Garfield AIM: LOLG42
larry@garfieldtech.com ICQ: 6817012"If nature has made any one thing less susceptible than all others
of
exclusive property, it is the action of the thinking power called
an
idea,
which an individual may exclusively possess as long as he keeps it
to
himself; but the moment it is divulged, it forces itself into the
possession
of every one, and the receiver cannot dispossess himself of it."
Thomas
Jefferson--
--
Larry Garfield AIM: LOLG42
larry@garfieldtech.com ICQ: 6817012"If nature has made any one thing less susceptible than all others of
exclusive property, it is the action of the thinking power called an
idea,
which an individual may exclusively possess as long as he keeps it to
himself; but the moment it is divulged, it forces itself into the
possession
of every one, and the receiver cannot dispossess himself of it." --
Thomas
Jefferson
a)
I think Traits should be able to act as a self-contained behavior
which can always be expected to work. For example if I want a
Counter behavior I would like that not to depend on the properties
in the containing class. While I don't think we should enable public
nor protected properties in Traits I think allowing for private
properties in Traits would come in very handy. It also is no problem
when it comes to mangling as we can use the Trait name.class Trait {
private $counter = 0;
function getNextSerialNumber() {
return $this->counter++;
}
}I strongly recommend not to support protected/public and not to even
get into the discussion of dealing with conflicts of such
properties. But I think private is very useful.
Of course this could be nice, but I think this is maybe the point
where one should move to delegate?
b)
I haven't thought this through completely but I think we may want to
consider a different model from supporting "removing" and "aliasing"
of functions. I think this can create a lot of complications
especially in larger works and it'll be write-once code and hard for
a newcomer to the code to really understand.I think the main reason to support removing/aliasing of functions is
in order to avoid conflicts with other Traits/Class methods. Maybe
what we can do is to have only "aliasing" but what it would do is to
create a public function with the new name and convert the old
function into a private function.
Benefits:
- Keeps the old code from running predictably without breaking
dependencies.- Possibly even allowing some form of "is-a" relationship to
continue to be valid (and therefore the interface discussion may
even be resolved; at least to a certain level). In the case I faced
an is-a relationship (i.e. working instanceof operator) would have
been nice.
I am probably not seeing the obvious here, but how does making the
function private solve the naming collisions?
regards,
Lukas
+1
Thanks for very detailed proposal.
I think "Traits for PHP" is good idea.
But I think, that "use" keword make some confusing, I propose "have" or
"hold" instead.
And "!" not readable (imho) - I'd like "not" or "except"
And aliasing syntax (bar=>foo) looks good in my mind.
See below:
-----Original Message-----
From: Lukas Kahwe Smith [mailto:mls@pooteeweet.org]
Sent: Thursday, February 21, 2008 12:41 AM
To: Andi Gutmans
Cc: php@stefan-marr.de; internals@lists.php.net; Marcus Börger;
Johannes Schlüter; Sebastian Bergmann; Alexandre Bergel; Falko Menge;
Sara Golemon; derick@php.net
Subject: Re: [PHP-DEV] RFC: Traits for PHPa)
I think Traits should be able to act as a self-contained behavior
which can always be expected to work. For example if I want a
Counter behavior I would like that not to depend on the properties
in the containing class. While I don't think we should enable public
nor protected properties in Traits I think allowing for private
properties in Traits would come in very handy. It also is no problem
when it comes to mangling as we can use the Trait name.class Trait {
private $counter = 0;
function getNextSerialNumber() {
return $this->counter++;
}
}I strongly recommend not to support protected/public and not to even
get into the discussion of dealing with conflicts of such
properties. But I think private is very useful.Of course this could be nice, but I think this is maybe the point
where one should move to delegate?
I don't think so. I think the value here is not copy&paste but to be able to encapsulate some functionality which a class can be decorated with. Giving a basic storage mechanism to that would be very beneficial in a large amount of uses. Again, I am proposing private storage only which would be for internal use-only by the decorating methods.
b)
I haven't thought this through completely but I think we may want to
consider a different model from supporting "removing" and "aliasing"
of functions. I think this can create a lot of complications
especially in larger works and it'll be write-once code and hard for
a newcomer to the code to really understand.I think the main reason to support removing/aliasing of functions is
in order to avoid conflicts with other Traits/Class methods. Maybe
what we can do is to have only "aliasing" but what it would do is to
create a public function with the new name and convert the old
function into a private function.
Benefits:
- Keeps the old code from running predictably without breaking
dependencies.- Possibly even allowing some form of "is-a" relationship to
continue to be valid (and therefore the interface discussion may
even be resolved; at least to a certain level). In the case I faced
an is-a relationship (i.e. working instanceof operator) would have
been nice.I am probably not seeing the obvious here, but how does making the
function private solve the naming collisions?
My concern about removing/aliasing functions is that you can really do some very funky stuff. It almost takes me back to the spaghetti code days where you can jump into code someplace in the middle.
My proposal would make Traits a bit stricter and ensure that the original interface would still exist after it is aggregated. Either by honoring the interface and redefining methods which are consistent with the Trait or by leaving the original methods around as privates so that they can still reference each other.
I think being able to remove a method which may need to be used by some internal method is a bit too chaotic. When you're inheriting large code bases it can become complicated to manage.
I very much see the value of Traits but I think we should also consider making them a bit stricter.
Btw, the "private property" discussion is different from the 2nd one. They are mutually exclusive. The latter idea would give more structure, still enforce a contract but should provide the same value. I am not convinced it's feasible on an implementation side but I think we should first consider it from a functional point of view.
Andi
I don't think so. I think the value here is not copy&paste but to be
able to encapsulate some functionality which a class can be
decorated with. Giving a basic storage mechanism to that would be
very beneficial in a large amount of uses. Again, I am proposing
private storage only which would be for internal use-only by the
decorating methods.
if someone desperately needs that they could just write a method with
a static variable.
class foo {
function bar($value = null) {
if (!isset($var)) {
static $var = 'bar';
}
if (func_num_args() > 0) {
$var = $value;
}
return $var;
}
}
$foo = new foo();
var_dump($foo->bar());
var_dump($foo->bar('foo'));
var_dump($foo->bar());
ok its a hack .. but you can see how this could work. and you could
even expand it to handle an arbitrary number of "properties" in a self
contained method. all you need to do is find a name that doesnt clash.
again i prefer to keep the feature as simple as possible without
crippeling it the point of not being useful.
regards,
Lukas
-----Original Message-----
From: Lukas Kahwe Smith [mailto:mls@pooteeweet.org]
Sent: Thursday, February 21, 2008 10:32 AM
To: Andi Gutmans
Cc: php@stefan-marr.de; internals@lists.php.net; Marcus Börger;
Johannes Schlüter; Sebastian Bergmann; Alexandre Bergel; Falko Menge;
Sara Golemon; derick@php.net
Subject: Re: [PHP-DEV] RFC: Traits for PHPI don't think so. I think the value here is not copy&paste but to be
able to encapsulate some functionality which a class can be
decorated with. Giving a basic storage mechanism to that would be
very beneficial in a large amount of uses. Again, I am proposing
private storage only which would be for internal use-only by the
decorating methods.if someone desperately needs that they could just write a method with
a static variable.
Not really because if you want a container shared across the methods it wouldn't be very feasible. I'm sure there are some hacks you can do but I just think it'd be a pretty common use case.
Stefan, you have done a lot of research in this area and I assume have also looked at the most common
use-cases where this functionality is used in other languages. What is your sense?
again i prefer to keep the feature as simple as possible without
crippeling it the point of not being useful.
This suggestion is actually geared towards the opposite of crippling (private property is actually an addition). I actually think this is something which would be a very common use case for our users.
Btw, the 2nd part where I recommend brainstorming about a slightly stronger contract would actually also drive more towards simplicity.
In any case, I think we all agree this is a good thing to have in PHP. I am not advocating the opposite but I would like to explore some of these use-cases and options for a slightly stricter contact.
Andi
Hi,
Andi Gutmans schrieb:
I don't think so. I think the value here is not copy&paste but to be
able to encapsulate some functionality which a class can be
decorated with. Giving a basic storage mechanism to that would be
very beneficial in a large amount of uses. Again, I am proposing
private storage only which would be for internal use-only by the
decorating methods.
if someone desperately needs that they could just write a method with
a static variable.Not really because if you want a container shared across the methods it wouldn't be very feasible. I'm sure there are some hacks you can do but I just think it'd be a pretty common use case.
Stefan, you have done a lot of research in this area and I assume have also looked at the most common
use-cases where this functionality is used in other languages. What is your sense?
I've mainly investigated the Squeak implementation which does not
support stateful traits. Therefore, state has been access via required
accessors implemented in the class using the trait. It is a quite
practicable approach, even if it is very strict. But as already
mentioned, there are ideas to allow state and trait-composition local
properties. But here I've not seen any code using it and I am only aware
of the pro and cons discussed in the paper
(http://www.iam.unibe.ch/~scg/Archive/Papers/Berg07eStatefulTraits.pdf).
My be anybody has connections to Scala guys using there Traits construct?
Kind Regards
Stefan
Hello Lukas,
Thursday, February 21, 2008, 9:41:10 AM, you wrote:
a)
I think Traits should be able to act as a self-contained behavior
which can always be expected to work. For example if I want a
Counter behavior I would like that not to depend on the properties
in the containing class. While I don't think we should enable public
nor protected properties in Traits I think allowing for private
properties in Traits would come in very handy. It also is no problem
when it comes to mangling as we can use the Trait name.class Trait {
private $counter = 0;
function getNextSerialNumber() {
return $this->counter++;
}
}I strongly recommend not to support protected/public and not to even
get into the discussion of dealing with conflicts of such
properties. But I think private is very useful.
Of course this could be nice, but I think this is maybe the point
where one should move to delegate?
b)
I haven't thought this through completely but I think we may want to
consider a different model from supporting "removing" and "aliasing"
of functions. I think this can create a lot of complications
especially in larger works and it'll be write-once code and hard for
a newcomer to the code to really understand.I think the main reason to support removing/aliasing of functions is
in order to avoid conflicts with other Traits/Class methods. Maybe
what we can do is to have only "aliasing" but what it would do is to
create a public function with the new name and convert the old
function into a private function.
Benefits:
- Keeps the old code from running predictably without breaking
dependencies.- Possibly even allowing some form of "is-a" relationship to
continue to be valid (and therefore the interface discussion may
even be resolved; at least to a certain level). In the case I faced
an is-a relationship (i.e. working instanceof operator) would have
been nice.
I am probably not seeing the obvious here, but how does making the
function private solve the naming collisions?
Private classes are from a users perspective bound to the implementing
class. That would be the Trait. And from a compiler/executors point of view
they are prefixed with the implementing class. That would be the Ttrait as
well. So three functions f, one of Trait a, one of Trait b and one of the
class C itself would all have different names and would all resolve. But
sure they would scare the hell for any user so lets not do any of this. For
the sake of clarification here comes anyway:
Trait a {
function f() {}
}
Trait b {
function f() {}
}
class c {
uses a, b;
function f() {}
}
Reflection(c):
function "\0a\0f" function a::f
function "\0b\0f" function b::f
function "f" function f
All nicely callable using array syntax:
class d extends c {
function t() {
call_user_func(array($this, "a::f")):
call_user_func(array($this, "b::f")):
call_user_func(array($this, "f")):
}
}
$o = new c:
$o->test();
Best regards,
Marcus
Hi,
Marcus Boerger schrieb:
Trait a {
function f() {}
}Trait b {
function f() {}
}class c {
uses a, b;
function f() {}
}Reflection(c):
function "\0a\0f" function a::f
function "\0b\0f" function b::f
function "f" function fAll nicely callable using array syntax:
class d extends c {
function t() {
call_user_func(array($this, "a::f")):
call_user_func(array($this, "b::f")):
call_user_func(array($this, "f")):
}
}
$o = new c:
$o->test();
May be I don't get the point completly, but having this, every thing
what traits are trying to solve is lost... IMHO
Instead, you are playing with the devil (C++ multiple-inheritance).
The main part of the trait idea is to be able to solve conflicts
explicitly. Think this is the major contribution of traits and the
things being discussed here right now are not THIS kind of traits anymore.
Your other mail from today goes in the same direction. I don't feel
comfortable with this :(
I will put together some thoughts on how we could build real traits
semantics with the intended benefits you might be a bit more comfortable
with.
But I absolutely think the proposals from the last days are going in the
wrong direction. Instead of abusing the traits idea for this, it would
be a valuable contribution to make a cut and drop the traits ideas for
the sake of simplicity and use some mixin mechanism instead.
I'll try to have a better "real trait thing" ready in this evening.
Kind Regards
Stefan
Hi!
The main part of the trait idea is to be able to solve conflicts
explicitly. Think this is the major contribution of traits and the
things being discussed here right now are not THIS kind of traits anymore.
What I still don't understand it's how you can solve these conflicts at
all. I.e., suppose you have Counter API and DBConnection API on each of
which you have clean() method - for Counter it resets it to zero and for
DB it resets authentication details. How it is possible to resolve it in
any way so that resulting class would support both APIs?
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi,
Stanislav Malyshev schrieb:
What I still don't understand it's how you can solve these conflicts at
all. I.e., suppose you have Counter API and DBConnection API on each of
which you have clean() method - for Counter it resets it to zero and for
DB it resets authentication details. How it is possible to resolve it in
any way so that resulting class would support both APIs?
Would exactly do you mean with API? And how would you solve this problem
without traits? Traits can not solve stuff like colliding methods with
colliding semantics in interfaces.
One solution would be to do the following:
trait Counter {
public function clean() {
$this->setCntValue(0);
}
}
trait DBConnection {
public function clean() { // one problem we probably should not
// discuss here is the naming, think it
// should be named resetCredentials
$this->setCredentials(null);
}
}
class DBCounter {
use Counter {
!clean,
cleanCounter => clean
}
use DBConnection {
!clean,
resetCredentials => clean
}
}
This would be a class reusing the behavior implemented in both traits,
and this is the contribution of the traits idea. With mixins this
wouldn't be possible and C++ multiple-inheritance would introduce fragility.
If DBCounter should implement ICounter and IDBConnection with each
having a clean method, there is no meaningful possible implementation
within a single class. Since the semantics of the two clean methods are
not as near as it would be necessary.
and a
class DBCounter {
use Counter {
!clean,
cleanCounter => clean
}
use DBConnection {
!clean,
resetCredentials => clean
}
public function clean() {
$this->cleanCounter();
$this->resetCredentials();
}
}
would be a bit pointless, but possible...
Kind Regards
Stefan
Hi,
Andi Gutmans schrieb:
a)
I think Traits should be able to act as a self-contained behavior which can
always be expected to work. For example if I want a Counter behavior
I would like
that not to depend on the properties in the containing class. While I
don't
think we should enable public nor protected properties in Traits I think
allowing for private properties in Traits would come in very handy.
It also
is no problem when it comes to mangling as we can use the Trait name.class Trait {
private $counter = 0;
function getNextSerialNumber() {
return $this->counter++;
}
}I strongly recommend not to support protected/public and not to even get into
the discussion of dealing with conflicts of such properties.
But I think private is very useful.
Hope I got it right, since your example is a class?
Ok, stateful traits are useful in the sense of having self-contained
units of reuse. Personally, I prefer them over stateless traits.
But, here we will get additional confusion.
You don't like to handle with visibilities of properties? Fine :)
One way to do stateful traits is described in
http://www.iam.unibe.ch/~scg/Archive/Papers/Berg07eStatefulTraits.pdf
But the way you have proposed is stricter and may be sufficient.
To avoid confusion and misconception I would like to change your
proposal a bit. Private does suggest a semantics like methods, and would
require to apply the flattening on properties like on methods. Since we
do not like to handle conflicts, this would have to be done a bit
different, in my opinion.
Let's change private
to local
:
trait TCounter {
local $counter = 0;
function getNextSerialNumber() {
return $this->counter++;
}
}
class Website {
use TCounter;
}
The resulting composition would have a property resulting from the
TCounter property, but is only accessible from a method of the specific
trait, i.e., it is composition local.
Just an additional though :)
Kind Regards
Stefan
Hi,
Andi Gutmans schrieb:
a)
I think Traits should be able to act as a self-contained behavior which can
always be expected to work. For example if I want a Counter behavior
I would like
that not to depend on the properties in the containing class. While I
don't
think we should enable public nor protected properties in Traits I think
allowing for private properties in Traits would come in very handy.
It also
is no problem when it comes to mangling as we can use the Trait name.class Trait {
private $counter = 0;
function getNextSerialNumber() {
return $this->counter++;
}
}I strongly recommend not to support protected/public and not to even get into
the discussion of dealing with conflicts of such properties.
But I think private is very useful.
Hope I got it right, since your example is a class?Ok, stateful traits are useful in the sense of having self-contained
units of reuse. Personally, I prefer them over stateless traits.
But, here we will get additional confusion.You don't like to handle with visibilities of properties? Fine :)
One way to do stateful traits is described in
http://www.iam.unibe.ch/~scg/Archive/Papers/Berg07eStatefulTraits.pdfBut the way you have proposed is stricter and may be sufficient.
To avoid confusion and misconception I would like to change your
proposal a bit. Private does suggest a semantics like methods, and would
require to apply the flattening on properties like on methods. Since we
do not like to handle conflicts, this would have to be done a bit
different, in my opinion.Let's change
private
tolocal
:
trait TCounter {
local $counter = 0;
function getNextSerialNumber() {
return $this->counter++;
}
}class Website {
use TCounter;
}The resulting composition would have a property resulting from the
TCounter property, but is only accessible from a method of the specific
trait, i.e., it is composition local.Just an additional though :)
Kind Regards
Stefan
I think I see an issue with including properties.
If a trait is partially included at different levels of a layered object
base uses 2 methods of a trait.
extended uses 2 more methods of the same trait
another_base uses 1 method of the trait.
another_extended_base uses the remaining methods of the trait.
You would need to control the inclusion of the properties.
--
Richard Quadling
Zend Certified Engineer : http://zend.com/zce.php?c=ZEND002498&r=213474731
"Standing on the shoulders of some very clever giants!"
Hi,
Andi Gutmans schrieb:
I haven't thought this through completely but I think we may
want to consider a different model from supporting "removing"
and "aliasing" of functions. I think this can create a lot
of complications especially in larger works and it'll be
write-once code and hard for a newcomer to the code to really understand.I think the main reason to support removing/aliasing of functions
is in order to avoid conflicts with other Traits/Class methods.
Maybe what we can do is to have only "aliasing" but what it would
do is to create a public function with the new name and convert
the old function into a private function.
Sounds a bit like what had been proposed in this paper:
http://www.iam.unibe.ch/~scg/Archive/Papers/Duca07b-FreezableTrait.pdf
The first point, yes exclusion and aliasing are only meant to handle
conflicts, and should be used only in this context. Yeah you can break
the trait with it, but you did it explicitly, think this should be made
clear a bit more in the discussion about "breaking interfaces".
Nobody would request unset to be removed from the function set even
though you can do this:
$a = new A();
unset($a);
$a->foo(); //damn...
It is "almost" like excluding a method from a trait and be worried about
the trait breaks. Isn't it? :)
Benefits:
- Keeps the old code from running predictably without breaking dependencies.
Well, in the code I've read so far, exclusion has been used to
cog/dovetail to traits which supplement each other very well.
- Possibly even allowing some form of "is-a" relationship to continue
to be valid (and therefore the interface discussion may even be resolved;
at least to a certain level). In the case I faced an is-a relationship
(i.e. working instanceof operator) would have been nice.
Hm, I would prefer to not add the type notion to traits. They are only
units of reuse, not types IMHO.
I need to put a bit more thought into this but it's an initial attempt to
provide an additional point of view which may actually give some more
structure to the proposal while still delivering the benefits of this
idea.I think if this kind of idea could be fully baked it would solve the problems
I have had in the past possibly without the more dangerous
renaming/removing
of functions. I have not looked into feasibility of this idea yet.
Think user-changeable location of composed methods is quite possible,
but for the cost of complexity. (Have a look at the paper for the
complete idea of trait-local/freezed methods.)
Kind Regards
Stefan
Am 22.02.2008 um 00:16 schrieb Stefan Marr:
Sounds a bit like what had been proposed in this paper: http://www.iam.unibe.ch/~scg/Archive/Papers/Duca07b-FreezableTrait.pdf
The first point, yes exclusion and aliasing are only meant to handle
conflicts, and should be used only in this context. Yeah you can
break the trait with it, but you did it explicitly, think this
should be made clear a bit more in the discussion about "breaking
interfaces".Nobody would request unset to be removed from the function set even
though you can do this:$a = new A();
unset($a);
$a->foo(); //damn...It is "almost" like excluding a method from a trait and be worried
about the trait breaks. Isn't it? :)
I agree. It's called "asking for it". Since traits can only be added
in dynamically, a programmer always knows what he is doing when
excluding or aliasing a method in a trait.
PHP needs to stop worrying about the "confusing newbies" thing when
discussing advanced features. We're not talking about a basic bread
and butter language construct here. Other parts of PHP aren't beginner
friendly either, and not all parts ever could, let alone should.
David
PHP needs to stop worrying about the "confusing newbies" thing when
discussing advanced features. We're not talking about a basic bread
and butter language construct here. Other parts of PHP aren't beginner
friendly either, and not all parts ever could, let alone should.
Absolutelty.
Parameter sequence or the names of functions being inconsistent.
These cause more of a headache for newbies.
is_(a/array/etc/null) vs isset
Why?
Sure you learn it but forever ask yourself why.
And is the knowledge required to understand Unicode going to be easier
to attain than for traits?
--
Richard Quadling
Zend Certified Engineer : http://zend.com/zce.php?c=ZEND002498&r=213474731
"Standing on the shoulders of some very clever giants!"
All,
I can imagine a case where you would want to box common functionality
into a trait and be able to use that trait as a class also. Will we end
up with a lot of classes like this?:
class SomeClass {
use SomeTrait;
}
What's wrong with making all 'Class'es be 'Trait's and the only thing
which makes a Trait a Trait is when you use it as such?
class MyClass {
trait SomeClass;
}
In other words, why mustn't a trait be instantiable? Why can't every
Class be a Trait?
-- Dante
Hi,
D. Dante Lorenso schrieb:
All,
I can imagine a case where you would want to box common functionality
into a trait and be able to use that trait as a class also. Will we end
up with a lot of classes like this?:class SomeClass {
use SomeTrait;
}What's wrong with making all 'Class'es be 'Trait's and the only thing
which makes a Trait a Trait is when you use it as such?class MyClass {
trait SomeClass;
}In other words, why mustn't a trait be instantiable? Why can't every
Class be a Trait?
Well, the trait concept is not a silver bullet for all problems out there.
What is wrong with inheritance or delegation for reusing complex
behavior? Traits are for finer grained reuse in my eyes.
The usage I've seen so far suggests typical traits are about 1 to 5
methods, but the average method count on a trait would be 1.88 or
something like this (just a rule of thumb. from what I personally have
looked at).
Kind Regards
Stefan
I hate to piss on anybody's bonfire, but...
I'm not, never was, an OO fan. OO is one of those things I'm more and more
forced to deal with... even in C coding (wtf is that all about?). It's OK, I
won't create this OO stuff but I can deal with it when I see it... nowadays.
But I also remember asking what 'void' meant when I first started out. I
remember not understanding 'return', never mind the clever stuff with
classes and all that.
Everybody's getting so excited about how they can make OO in PHP better. I
just see another thing I'll need to learn about if it goes into core. It's
not a major problem to me, I'm just looking at whether I'd have been
attracted to PHP all those years ago if it had been as it is now, never mind
if it gets extra Java-esque features.
All I'm seeing here is people with CS degrees saying trait would be cool.
What about us peasants? Or am I the last living peasant? :\ That's
possible, I suppose.
Sell it to me?
- Steph
----- Original Message -----
From: "Stefan Marr" php@stefan-marr.de
To: "D. Dante Lorenso" dante@lorenso.com
Cc: "Internals Mailing List" internals@lists.php.net
Sent: Thursday, February 21, 2008 11:44 PM
Subject: Re: [PHP-DEV] Re: Traits for PHP ... Why can't every Class be a
Trait?
Hi,
D. Dante Lorenso schrieb:
All,
I can imagine a case where you would want to box common functionality
into a trait and be able to use that trait as a class also. Will we end
up with a lot of classes like this?:class SomeClass {
use SomeTrait;
}What's wrong with making all 'Class'es be 'Trait's and the only thing
which makes a Trait a Trait is when you use it as such?class MyClass {
trait SomeClass;
}In other words, why mustn't a trait be instantiable? Why can't every
Class be a Trait?Well, the trait concept is not a silver bullet for all problems out there.
What is wrong with inheritance or delegation for reusing complex behavior?
Traits are for finer grained reuse in my eyes.
The usage I've seen so far suggests typical traits are about 1 to 5
methods, but the average method count on a trait would be 1.88 or
something like this (just a rule of thumb. from what I personally have
looked at).Kind Regards
Stefan
Hi,
Steph Fox schrieb:
All I'm seeing here is people with CS degrees saying trait would be
cool. What about us peasants? Or am I the last living peasant? :\
That's possible, I suppose.Sell it to me?
A very cool thing of traits is the flattening property ensuring that
there is no difference between a method from a trait imported to the
class and a method defined in the class.
Ok, there are people trying to mess up the notion proposed by additional
complexity.
But based on the original proposal made by me, traits can be seen as a
language supported copy'n'past mechanism, like some complex macro
copying the traits methods to a class body (with those excluding and
aliasing features added).
In the implementation for the Squeak Smalltalk language, they have
introduced a modified class browser. The class browser is the embedded
IDE of the Smalltalk guys, with similar features as the outline view of
eclipse.
To avoid the need for developers to struggle with traits if they don't
know about or do not like them, they build a class browser which only
shows the flattened view on the classes, including all methods from
traits but avoiding to annoy the developers with the trait notion.
This is possible to be implemented in the Eclipse PDT, too.
Some Java guys have already demonstrated a comparable implementation for
the Java part of IDE to demonstrate traits for Java.
In my opinion, traits is a thing developers has only to be aware of if
they really did like to use them. (iff proper tool support is available.
Guess it wouldn't be that way for VIM users, for example)
Kind Regards
Stefan
Hi!
In my opinion, traits is a thing developers has only to be aware of if
they really did like to use them. (iff proper tool support is available.
Guess it wouldn't be that way for VIM users, for example)
I have little experience about how Smalltalk users look on other's code.
I know, however, how PHP users always did it - they just read the code,
since PHP is simple enough to be just read. Adding magic stuff like
class methods that aren't class methods, names that aren't names, etc.
makes it harder. Even if you don't use it, you'd have to know it if
other code uses it.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Stefan Marr wrote:
Hi,
D. Dante Lorenso schrieb:
All,
I can imagine a case where you would want to box common functionality
into a trait and be able to use that trait as a class also. Will we
end up with a lot of classes like this?:class SomeClass {
use SomeTrait;
}What's wrong with making all 'Class'es be 'Trait's and the only thing
which makes a Trait a Trait is when you use it as such?class MyClass {
trait SomeClass;
}In other words, why mustn't a trait be instantiable? Why can't every
Class be a Trait?Well, the trait concept is not a silver bullet for all problems out there.
What is wrong with inheritance or delegation for reusing complex
behavior? Traits are for finer grained reuse in my eyes.
The usage I've seen so far suggests typical traits are about 1 to 5
methods, but the average method count on a trait would be 1.88 or
something like this (just a rule of thumb. from what I personally have
looked at).
I agree Traits are cool for what they do. I'm saying that we don't need
to introduce a new "thing" called a Trait just to do what traits are doing.
The magical part of Traits is the flattening of included class
components. Just do this flattening with things defined as a Class and
we won't have to introduce yet another kind of thing.
Not all classes are meant to work as traits but the ones created with
being a trait in mind would. If you create a Class/Trait with 5
methods, fine. But you could just as easily create one with 50 methods.
Current Class + New Trait features = new Class
All you need to implement is the flattening declaration code:
class MyClassWhichCouldBeATrait {
...
}
class MyClass {
suck MyClassWhichCouldBeATrait;
}
BTW, can we add 'suck' to the list of alternate keywords ;-)
-- Dante
Replies to a) and b) below:
Andi Gutmans wrote:
Hi Stefan,
Thanks for writing such a good proposal.
In general I think traits is a very interesting idea. I can think of a few cases in my life as a programmer where it could have helped me out.I think some comments re: the syntax/naming are good ones but I prefer not to get into naming in this email. I believe we can brainstorm re: naming when we figure out exactly what we want to do. I do support though finding a way which is simple for non-English speakers but not too simple like Perl:)
Thinking back at the times when something like this would have been useful to me I think there are a couple of main points I'd like to brainstorm about:
a)
I think Traits should be able to act as a self-contained behavior which can always be expected to work. For example if I want a Counter behavior I would like that not to depend on the properties in the containing class. While I don't think we should enable public nor protected properties in Traits I think allowing for private properties in Traits would come in very handy. It also is no problem when it comes to mangling as we can use the Trait name.class Trait {
private $counter = 0;
function getNextSerialNumber() {
return $this->counter++;
}
}I strongly recommend not to support protected/public and not to even get into the discussion of dealing with conflicts of such properties. But I think private is very useful.
If you read the stateful traits paper Stefan linked to, you can see a
precedent for how this has been implemented. By default the properties
are private to the scope of the trait. The client class can also make an
alias to the property and have access to this property through this
private alias property. The client class can also merge multiple
properties into one common properties.
The problem with this is during flattening. When there is a conflicting
property, all references have to be "alpha-renamed" in all the trait
methods. This is a more costly operation as compared to the flattening
of stateless traits. This may not be reason enough not to include it,
but it should definitely be a consideration.
b)
I haven't thought this through completely but I think we may want to consider a different model from supporting "removing" and "aliasing" of functions. I think this can create a lot of complications especially in larger works and it'll be write-once code and hard for a newcomer to the code to really understand.I think the main reason to support removing/aliasing of functions is in order to avoid conflicts with other Traits/Class methods. Maybe what we can do is to have only "aliasing" but what it would do is to create a public function with the new name and convert the old function into a private function.
Benefits:
- Keeps the old code from running predictably without breaking dependencies.
- Possibly even allowing some form of "is-a" relationship to continue to be valid (and therefore the interface discussion may even be resolved; at least to a certain level). In the case I faced an is-a relationship (i.e. working instanceof operator) would have been nice.
I need to put a bit more thought into this but it's an initial attempt to provide an additional point of view which may actually give some more structure to the proposal while still delivering the benefits of this idea.
I think if this kind of idea could be fully baked it would solve the problems I have had in the past possibly without the more dangerous renaming/removing of functions. I have not looked into feasibility of this idea yet.
Anyway, good ideas and thanks for the very professional way you have brought this idea to the community.
Let's try and nail down the behavior and then we can have the harder naming discussion :) Just saying harder because I've always considered naming the hardest part in software engineering...
Andi
Let me use a quote on aliasing from this paper
http://www.iam.unibe.ch/~scg/Archive/Papers/Scha03aTraits.pdf: "The
new name is used to obtain access to a method that would otherwise be
unreachable because it has been overridden." The point of aliasing is to
give the client class a way to call the old method after it has been
overridden.
Generally, if two traits have the same method names, you would want to
alias both of them, override the method, and use the aliases to compose
the overridden method. The other option is to just choose one of the
trait's method implementations and exclude all others.
And if you are talking about making the old method that is aliased into
a method that is private to just that trait, flattening would then
require some kind of "alpha-rename" for even stateless traits in some cases.
--
"Joshua Thompson" spam.goes.in.here@gmail.com
<http://www.schmalls.com
php@stefan-marr.de wrote:
Hi,
during last six months I've studied a language construct called Traits.
It is a construct to allow fine-grained code reuse and in my opinon
this would be a nice feature for PHP, which I did like to propose here.
The following RFC deals with the questions what Traits are, how they are
used, why they are usefull and how they do look like in PHP.
A patch implementing this new language construct is available, too.Thank you for your attention and I'm looking forward to hear your comments
:)Kind Regards
Stefan
I came across one piece of information on aliasing that may not be
obvious at first and therefore should probably be explicitly stated in
the RFC and in the manual when this makes it into the core.
Quoting http://www.iam.unibe.ch/~scg/Archive/Papers/Scha03aTraits.pdf:
"Note that because the body of the aliased method is not changed in any
way, so an alias to a recursive method is not recursive."
This means that because there is no renaming of anything in the method
body when aliased, a recursive call to the method in the trait does not
make a recursive call in the aliased method. It will instead call the
original method name, which could be the same body, a method defined in
a different trait, or the method overridden by the client class.
--
"Joshua Thompson" spam.goes.in.here@gmail.com
<http://www.schmalls.com
Hi,
I came across one piece of information on aliasing that may not be
obvious at first and therefore should probably be explicitly stated in
the RFC and in the manual when this makes it into the core.Quoting http://www.iam.unibe.ch/~scg/Archive/Papers/Scha03aTraits.pdf:
"Note that because the body of the aliased method is not changed in any
way, so an alias to a recursive method is not recursive."
thanks, yes, should have state that explicitly. Will add a note and an
example.
Kind Regards
Stefan