[Python-ideas] Dart-like method cascading operator in Python

Bruce Leban bruce at leapyear.org
Fri Nov 22 10:16:48 CET 2013


Steven D'Aprano:
>> Perhaps, but that's not relevant to the proposed syntax. The proposed
syntax has more to do with "then" rather than "there", that is, "the
last object used", not "the object at location 1234".

>> For starters, you should explain precisely how the compiler will
determine what is the "last object used" in practice. Your examples so far
suggest it is based on the last line of source code, but that's tricky.

Perešíni Peter:
> I think the reasonable way to define it would be that the cascade
operator will act only if the last thing (on the previous line) was an
expression. In particular, all statements like "del y, for, while, if-else"
followed by a cascade should be a parse error with the exception of the
assignment which is a statement but we want to allow it. I am not sure
about yield -- it can be a bit difficult to agree on the exact semantic
there (e.g. should I yield first, then continue with the cascade or should
I execute the whole cascade and yield as a final step?)

I don't think that's well-defined at all. Furthermore last object on
*previous* line is less useful than object on line that introduces the
suite. Consider:

obj::
    ..x = b
    # is last object b? That's not useful. obj.x? That may not be an object
    ..y = 3
    # even more clearly not useful
    ..sort()
    # this always returns None

In:

<expression>::
    <lines all referencing that one expression via ..>

I know exactly what that does without reading each intervening line which
could change the object being operated on. I can use this with objects that
*don't* support chaining which is what makes it useful. I can reorder lines
worrying only about method execution order not breaking chaining.

The traceback complaint is a red herring as it can easily inject the line
that has the expression being operated on.

In summary I would only allow the first line to contain an expression or
assignment which by definition has a single explicit value. That values is
what all statements reference:

obj::
    ..x = 3
    ..y()  # operates on obj not obj.x or 3
    ..z(). # ditto

a[index] = foo(...) ::  # don't need to repeat a[index] below or use a
temporary name
    ..x = 3
    ..y()
    ..z()
On Nov 22, 2013 12:24 AM, "Perešíni Peter" <ppershing at gmail.com> wrote:

> Thanks Steve for very useful comments
>
>
>>  Please give examples of how this might do such a thing, rather than just
>> claim it will. "This syntax will end world hunger and bring peace to the
>> Middle East..." I don't see how this syntax can simplify instantiating
>> objects:
>>
>> obj = MyClass("spam", "eggs")
>>
>> is already pretty simple. Perhaps you're referring only to a small
>> subset of (in my opinion) *poorly designed* if not outright buggy
>> objects which aren't instantiated completely on creation and need to be
>> tweaked by hand before being ready to use. Or those with excessively
>> complicated APIs that could really do with a few helper functions.
>>
>
> I do not agree that all objects that are mutable are poorly designed.
> Anything which is tree-like (e.g. mentioned DSLs, API wrappers around
> complicated objects (e.g. xml), configurations, gnuplot example, ...) can
> be difficult to instantiate using normal syntax:
>
> tree = Tree()
>   ..newChild(value=5)
>       ..newChild(value=3)
>       ..newChild()
>   ..newChild(color="green")
>
>
> Now, that said, sometimes you can turn this instatiation to be more
> Pythonic, e.g. (using lists instead of repetitive calls of "addSomething")
> but it is hard to visually parse (distinguish between end of constructors
> vs end of lists):
>
> tree = Tree(children=[
>    Node(value=5, children=[
>        Node(value=3),
>        Node()
>    ]),
>    Node(color="green")
> ])
>
> Alternative is to do inverse of instantiation, e.g.
>
> tmp1 = [Node(value=3),
>             Node()]
> tmp2 = [Node(value=5, children=tmp1),
>             Node(color="green")]
> tree = Tree(children=tmp2)
>
> which is hard to comprehend what is going on there.
>
>
>>  > In particular, we can
>> > make this much more powerful in Python (as opposed to Dart) because
>> Python
>> > recognizes scope by indentation and therefore it would be possible to do
>> > something like
>> >
>> > gnuplot.newPlot()
>> >  ..set("xrange [0:5]")
>> >  ..set("yrange [0:20]")
>> >  ..newPlot()
>> >      ..addSeries("Linear", [1,2,3])
>> >      ..addSeries("Quadratic", [1,4,6])
>> >  ..run()
>>
>> I had to read that multiple times before I was able to interpret what
>> this is supposed to mean. It doesn't help that I'm not familiar enough
>> with the gnuplot API to tell exactly what you're doing. I *think* that
>> it would be the equivalent of this:
>>
>> p = gnuplot.newPlot()
>> p.set("xrange [0:5]")
>> p.set("yrange [0:20]")
>> q = p.newPlot()
>> q.addSeries("Linear", [1,2,3])
>> q.addSeries("Quadratic", [1,4,6])
>> p.run()
>>
>> Yes, this is correct
>
>
>>  If I'm wrong, I think that suggests that your extension to the syntax
>> isn't as clear as you hoped. If I'm right, I wonder what the point of
>> the inner sub-block is, since you don't appear to do anything with the q
>> plot. What have I missed?
>>
>
> I apologize for confusion. maybe newPlot() isn't the best name. In my head
> the newPlot() function would
> - add a new Plot to the Gnuplot object
> - return this Plot object so you can work with it (e.g. add series,
> customize, etc.)
>
> When I reflect about this example, the p.set("xrange") is also a bit
> misleading to people not familiar with gnuplot -- gnuplot has "set" command
> and p.set("xrange [0:1]") was meant to imitate writing "set xrange[0:1]"
> line in gnuplot
>
>
>
>
>> For starters, you should explain precisely how the compiler will
>> determine what is the "last object used" in practice. Your examples
>> so far suggest it is based on the last line of source code, but
>> that's tricky. For example:
>>
>> x = spam(); y = eggs()
>>   ..method()
>>
>> I would *assume* that this implicitly calls y.method rather than
>> x.method, but that's not a given. How about this?
>>
>
> I would assume the same
>
>
>>
>> y = spam()
>> x = eggs()
>> del y
>>   ..method()
>>
>> Does that call x.method()? And what do this do?
>>
>>
> I think the reasonable way to define it would be that the cascade operator
> will act only if the last thing (on the previous line) was an expression.
> In particular, all statements like "del y, for, while, if-else" followed by
> a cascade should be a parse error with the exception of the assignment
> which is a statement but we want to allow it. I am not sure about yield --
> it can be a bit difficult to agree on the exact semantic there (e.g. should
> I yield first, then continue with the cascade or should I execute the whole
> cascade and yield as a final step?)
>
>
>> myClass()
>> for i in range(3):
>>     ..method()
>>
>> Is it a syntax error? Or does it call i.method()? Or perhaps even call
>> method on the range object? Or does the myClass object still count as
>> the implicit "last object used"?
>>
>
> I think this should be a syntax error. (as it anyway defeats the purpose
> of making things clear)
>
>
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20131122/346c3529/attachment-0001.html>


More information about the Python-ideas mailing list