It sounds like it would be a useful exercise to design a complete, modern Traits implementation for Python 3 (maybe starting with Simoniato's straits). An extra challenge would be to make it fit in the type system as perceived by PEP-484-based type checkers (mypy, pyre etc.) -- without this it would end up being a dynamically-typed dead end in Python's evolution.

On Sat, Feb 15, 2020 at 9:23 AM Stephen J. Turnbull <turnbull.stephen.fw@u.tsukuba.ac.jp> wrote:
Executive summary:

I'm sorry so many people have not gotten much out of the parent
thread.  Despite my disagreements with the OP, I find traits to be an
interesting concept, and I'll probably try out Simionato's strait
library[1].  I agree with several posters that traits neither need nor
are important enough to deserve special syntax.

In my previous post, I pointed out that traits conform to a slew of
Zen that general multiple inheritance violates, and even mixins can
only avoid with disciplined use.  I think they're worth considering if
you use mixins as they prevent foot-shooting in some reasonably
frequent cases.  Of course the flip side is that undisciplined use of
mixins is not always going to put you on crutches, so YMMV.

Some details follow, if I've piqued your interest.

The point of traits as defined by Schärli et al is more or less that
the support for traits respects the Zen:

- Flat is better than nested.
- Readability counts.
- In the face of ambiguity, refuse the temptation to guess.
- If the implementation is hard to explain, it's a bad idea.
- Namespaces are one honking great idea -- let's do more of those!

Unlike mixins, which are just a disciplined use of classes in Python,
proper traits satisfy a "flattening" property: a class or trait
composed from several traits behaves exactly the same as if all
methods were defined directly in the class or composite trait,
regardless of nesting of traits or order of addition.  If you combine
two traits, you get a new trait.  You can still combine the new trait
with either or both of the older ones, because the method
implementations are the same.  (I don't know if strait implements this
check; it may do the easy thing and just check for name conflicts.)
To extend this property to work with class inheritance, the only
method resolution order (MRO) you need to know for traits is: a
definition in a class overrides a definition in a trait used by the
class which overrides a definition in a superclass of the class.
Composite traits aren't actually flat, and so cost some readability,
but at least you don't need to compute the MRO.

Again, unlike mixins which invoke the MRO to disambiguate methods
defined in multiple mixins in the class hierarchy, when multiple
traits define methods for the same name, the class using those traits
must "refuse the temptation to guess" (even if obvious!) and
explicitly disambiguate.  Traits improve "readability" by avoiding the
MRO, which is "hard to explain."  Everything you need to know about
which methods will be invoked is in the class definition, although you
may need to read trait documentation to know all the methods
implemented.  And as the OP points out, traits provide "namespaces"
(so I guess we can count Uncle Tim as a vote for traits! ;-)

The bottom line is that disciplined use of mixins likely provides most
of the benefits of traits if that discipline includes a very flat
class hierarchy and sufficient care in avoiding method name collisions.

Finally, an aside on Simionato's blog post.  He claims that although
traits are a lot safer than general multiple inheritance or even
mixins, generic functions are "better" than traits.  I don't
understand the interaction between generic functions and traits in
Rust yet, but in that language they are evidently complementary
features, not alternatives.

Further reading:

This tutorial on Rust (readable, although the examples are excessively
verbose)

http://gradebot.org/doc/ipur/trait.html

indicates that in that language, interface definition is the primary
purpose of a trait in (I don't take a tutorial as definitive, although
the tutorial mirrors Soni's focus on interface), but traits are
allowed to have default implementations of methods.  zope.interface
interfaces deliberately avoid that; they're "pure" interfaces (at
least where I've used them).  Nothing in any of the Rust documentation
I looked at so far emphasizes code reuse or default implementations.

I gather Rust also implements at least some "trait algebra," as
Wikipedia describes:

https://en.wikipedia.org/wiki/Trait_(computer_programming)#Characteristics

After further research, I see that only "+" is implemented, and so
far I have not been able to find documentation of whether it's
disjoint or overriding.  Soni's post implies that if there are
multiple implementations, one will be chosen (but any others are
still accessible to code that knows which trait it wants).

See also Fisher and Reppy for an extended treatment of trait algebra

http://newtraell.cs.uchicago.edu/files/tr_authentic/TR-2003-13.pdf

which cites Schärli et al, but not vice versa. The Schärli et
paper is here:

http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf


Footnotes:
[1]  Which I keep spelling as "Simionati", mea culpa.
     https://www.artima.com/weblogs/viewpost.jsp?thread=246488

--
Associate Professor              Division of Policy and Planning Science
http://turnbull/sk.tsukuba.ac.jp/     Faculty of Systems and Information
Email: turnbull@sk.tsukuba.ac.jp                   University of Tsukuba
Tel: 029-853-5175                 Tennodai 1-1-1, Tsukuba 305-8573 JAPAN
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-leave@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/TAKYHTLSSFIMAMYDMC6H5MZU6SF7UCPD/
Code of Conduct: http://python.org/psf/codeofconduct/


--
--Guido van Rossum (python.org/~guido)
Pronouns: he/him (why is my pronoun here?)