On 2017-10-28 02:51 PM, Steven D'Aprano wrote:
On Sat, Oct 28, 2017 at 10:19:09AM -0200, Soni L. wrote:

class Car:
     def __init__(self):
         self.engine = Engine()
         self.accelerator = AcceleratorPedal()
         ...

     def start(self):
         # Delegate to the ignition component.
         self.ignition.start()


etc. Obviously this is just a very loose sketch, don't take it too
literally. Is this the sort of thing you are talking about?
So how do you call car.ignition.start() from car.key.turn()?
You don't -- the key is not a component of the car, its an argument of
ignition.start. If the key doesn't fit, ignition.start() raises an
exception and the car doesn't start.

I'm not really interested in getting into a tedious debate over the best
way to design a Car object. As I said, the above is just a loose sketch
illustrating composition, not a carefully planned and debugged object.
The aim here is to understand your proposal: what exactly do you mean
for Python to support composition over inheritence, and how does it
differ from Python's existing support for composition?

You ignored my question: Is that the sort of thing you mean by
composition? If not, then what do you mean by it? This is not a
rhetorical question: I'm having difficulty understanding your proposal.
It is too vague, and you are using terminology in ways I don't
understand.

Maybe that's my ignorance, or maybe you're using non-standard
terminology. Either way, if I'm having trouble, probably others are too.
Help us understand your proposal.

With composition, you can have car.key.turn() call car.ignition.start(), without having to add car to key or ignition to key. You just have to put both in a car and they can then see eachother!



I am not sure how you'd set components, or test for components,
If you don't know how to set components, or test for them, what do you
know how to do with components?

And how are components different from attributes?
They're more like conflict-free interfaces, and in this specific case
they're designed with duck typing in mind. (You can dynamically add and
remove components, and use whatever you want as the component. You
cannot do that with inheritance.)
What do you mean by "conflict-free interfaces"?

I can only repeat my earlier request:

If might help if you give a concrete example, with meaningful names. It
would help even better if you can contrast the way we do composition now
with the way you think we should do it.

In Rust, you can put as many "conflicting" traits as you want on the same object, and it'll still work! It'll compile, it'll run, you'll be able to use the object in existing code that expects a specific trait, and code that operates on the object itself is fairly easy to write.


I'm afraid that at the moment I'm parsing your post as:

"composition is cool, we should use it; and o.[c].m() is cool syntax, we
should use it for composition; I'll leave the details to others".
Again, how do you call car.ignition.start() from car.key.turn()?
Maybe you can't. Maybe this is a crippling example of why composition
isn't as good as inheritence and the OOP community is right that
inheritence is the best thing since sliced bread. Maybe my design of the
Car object sucks.

But who cares? None of this comes any closer to explaining your
proposal.

Composition works fine, if you do it like Rust.



Thus I think o.[c].m() should be syntax sugar for o[c].m(o), with o
being evaluated only once,
I don't see why you're using __getitem__ instead of attribute access;
nor do I understand why m gets o as argument instead of c.

Wait... is this something to do with Lieberman-style delegation?

http://web.media.mit.edu/~lieber/Lieberary/OOP/Delegation/Delegation.html

http://code.activestate.com/recipes/519639-true-lieberman-style-delegation-in-python/

TL;DR. But no, it's not some form of delegation.
One of us is using non-standard terminology, and I don't think it is me.
(Happy to be corrected if I'm wrong.)

I understand that composition and delegation go hand in hand: you can't
have one without the other. Composition refers to the arrangement of an
object that is composed of other objects. Delegation refers to the way
that the compound object calls methods on the component objects.

The point (as I understand it) of composition is that a Car doesn't just
have an Engine, it delegates functionality to the Engine: the Car object
derives functionality by calling Engine methods directly, rather than
inheriting them. Car.forward() delegates to Engine.forward().

The point is that the implementation of Car.forward is found in the
self.engine object, rather than being inherited from an Engine class.

Without delegation, the components aren't components at all, merely data
attributes: Car.colour = 'red'.

Does this match your proposal? If not, how is your proposal different?

Meet ECS:

https://en.wikipedia.org/wiki/Entity_component_system

Now, keep in mind, the usual ECS is crap. Why's it crap? Because there's no reasonable call convention that's also performant!

System.action(entity) # crap. can't override.
entity.action() # crap. conflicts easily. either not very dynamic or awful semantics. entity.get(System).action(entity) # crap. while you can override this one, you get to evaluate entity twice! entity.get(System).action() # crap. creates an object every time it's used (or hogs some RAM to cache the object).

I could keep going but the point is that Rust traits solve all these problems if you let them.



It still gets `self` (which is whatever is in o[c] - which may be c
itself, or an arbitrary object that fulfills the contract defined by c),
but also gets `o` in addition to `self`. (Unless it's a plain function,
in which case it gets no `self`.)
That sounds like a hybrid of Lieberman-style delegation and the more
common form. At first glance, that seems to add complexity without
giving the advantages of either form of delegation.

It's not delegation.



as that solves a lot of current issues
relating to inheritance while introducing very few issues relating to
python's "everything is separate" (e.g. __get__ vs __getattr__)
policy.This also makes setting components and testing for components
fairly trivial, and completely avoids the issues mentioned above by
making their syntax illegal.
Above you said that you don't know how to set and test for components,
now you say that doing so is trivial. Which is it?
If you pay closer attention, you'll notice the two different paragraphs
talk about two different syntaxes.
I don't care about syntax yet. I'm still trying to understand the
semantics of your proposal. Whether you spell this thing

     instance.[component]

     get_component(instance, 'component')

     instance!component

is less important than understand what it *does*.

Semantics is Rust traits at runtime, without the delegation (this is by design - with delegation, libraries could make assumptions, and adding new traits to an object could break them).



- o.[c] as a standalone syntax element, allowing things like
x=o.[c1].[c2]; and x=o.[c1][c2];.
- o.[c].m() as a standalone syntax element, *disallowing* the above.
That makes no sense to me. I cannot make head or tail of what that is
supposed to mean.

It means whether your parser looks for "o.[c]" and emits an opcode for it, or it looks for "o.[c].m()" and emits an opcode for that instead.



(Disclaimer: This was inspired by my own programming language,
Cratera[1], so I'm a bit biased here. Cratera was, in turn, inspired by
Rust[2] traits.
Traits are normally considered to be a more restricted, safer form of
multiple inheritence, similar to mixins but even more restrictive.
What do you mean more restricted?
I mean that if you have two traits with the same method:

class SpamTrait:
     def foo(self): ...

class EggTrait:
     def foo(self): ...


then you cannot use them both in a single class:

class MyClass(SpamTrait, EggTrait):
     ...

since the foo method clashes, unless MyClass explicitly specifies which
foo method to use. Mixins and regular multiple inheritence do not have
that restriction.

If you expect both foo methods to be called, that's just regular
multiple inheritence, with all its complexity and disadvantages.
(See Michele Simionato numerous posts on Artima about super, multiple
inheritence, mixins and his own traits implementation.)

The point of traits is to prevent the existence of such conflicts: either by
prohibiting the use of both SpamTrait and EggTrait at the same time, or
by forcing MyClass to explicitly choose which foo method gets used.
That's safer than unrestricted mixins and multiple inheritence, since it
reduces the complexity of the inheritence heirarchy.


They let you have the same method in multiple components/traits and
not have them conflict, among other things.
I think we are in agreement here.

But in any case... traits are a form of inheritence, not composition.
You said this proposal is inspired by Rust traits. Can you explain the
connection between inheritence of traits and composition?


Rust traits are the best traits.

You can have a Rust struct implement 2 traits with the same method. And it just works. You can call each trait method separately - disambiguation is done at the call site. This doesn't translate well to a dynamic language, so, for python, it's best to always specify the trait (no delegation - o.m() is always o.m() and if you want o.[c].m() you need to be explicit about it).

Otherwise, it's the same as always: called method gets main object, etc.

(Disclaimer: I wrote this message starting with the last question. It should still make sense tho. PS: You should go play with some Rust. As far as compiled languages go, Rust is pretty much the best.)



_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to