[Python-ideas] Method chaining notation

Masklinn masklinn at masklinn.net
Fri Feb 21 23:31:27 CET 2014


On 2014-02-21, at 18:30 , Chris Angelico <rosuav at gmail.com> wrote:
> So here's the proposal. Introduce a new operator to Python, just like
> the dot operator but behaving differently when it returns a bound
> method. We can possibly use ->, or maybe create a new operator that
> currently makes no sense, like .. or .> or something. Its semantics
> would be:

As Yuri noted the concept exists, AFAIK it was introduced by smalltalk
as "message cascading". Basically, the ability to send a sequence of
messages to the same subject without having to repeatedly specify the
subject. I believe Dart is the first language to have resurrected this
feature so far.

The cascading operator in smalltalk was `;` (the message-send operator
is the space), so e.g.

    foo message
      ; message2: aParameter
      ; message3.

would send all of message, message2:aParameter and message 3 to `foo`,
in that specific order.

In smalltalk, a cascade returns the result of the last message in the
cascade. smalltalk provides a `yourself` operator to return the
subject itself. Cascading is very commonly used to initialise collections
as smalltalk was born at a time where literal high-level collections
were not exactly a thing:

    aCollection := (OrderedCollection new) add: 1
                                         ; add: 2
                                         ; add: 3
                                         ; youself.

> 1) Look up the attribute following it on the object, exactly as per
> the current . operator
> 2) If the result is not a function, return it, exactly as per current.
> 3) If it is a function, though, return a wrapper which, when called,
> calls the inner function and then returns self.

I could be wrong, but I'm pretty sure this is an over-complication
when you look at it at the bytecode level: you can load the subject
as many times as you've got attr accesses to do on it, or you could
have an alternate attr access which puts TOS back. No need for a
wrapper.

> Effectively, x->y would be equivalent to chain(x.y):
> 
> def chain(func):
>    def chainable(self, *args, **kwargs):
>        func(self, *args, **kwargs)
>        return self
>    return chainable
> 
> Could be useful in a variety of contexts.
> 
> Thoughts?

No need for a wrapper. Where `a.b` compiles to

    LOAD_FAST a
    LOAD_ATTR b
    POP_TOP

`a->b` would compile to

    LOAD_FAST a
    DUP_TOP
    LOAD_ATTR b
    POP_TOP

at this point you've got an a left on the stack
and can reuse it:

`a->b()->c()->d()` would be

    LOAD_FAST a

    DUP_TOP
    LOAD_ATTR b
    CALL_FUNCTION
    POP_TOP

    DUP_TOP
    LOAD_ATTR c
    CALL_FUNCTION
    POP_TOP

    DUP_TOP
    LOAD_ATTR d
    CALL_FUNCTION
    POP_TOP


The tail end of the cascade would be slightly more complex in that it
would be:

    ROT_TWO
    POP_TOP

so that the subject is discarded and the value of the
last attribute/method is available on the stack (it
may get popped as well if it's unused).

Or maybe it would do nothing special and the cascade would yield (or
pop) the subject unless closed by an attribute access or regular method
call. That would avoid the requirement of a `yourself`-type method when
initialising mutables, although the final irregularity may lack
visibility.


More information about the Python-ideas mailing list