Dart-like method cascading operator in Python

Recently I fell in love with Dart method cascading operator .. to the degree that I find it really convenient and I am missing it in the Python. The cascade operator myObj ..setX(5) ..y=6 is the syntactic sugar equivalent of the following: tmp = myObj tmp.setX(5) tmp.y=6 This can be used to greatly simplify instantiating objects and can also enable creating domain specific languages in Python. 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() or template = HtmlTemplate() ..head() ..script(src="xyz") ..body() ..div(class="main") ..paragraph("I am the first paragraph") ..paragraph("I am the second paragraph") Note that this is strictly better than method chaining, e.g. template = MyObject().setX(1).setY(2) because a) method chaining is hard to write on multiple lines, the only two options are template = MyObject() \ .setX(1) \ .setY(2) which is fragile (explicit line continuation is discouraged in Python) or (template = MyObject() .setX(1) .setY(2)) which looks weird. b) method chaining cannot take advantage of multiple scoping, e.g. the only way to write Gnuplot example is (Gnuplot() .set("...") .newPlot() .addSeries() .addSeries() *.endPlot()* .run() ) with added method *endPlot()* to the Plot class. Moreover, Plot class now needs a direct reference to its parent (e.g. Gnuplot) so it can return its reference. This adds unnecessary cyclic dependencies. c) method chaining needs specialized API, e.g. each method needs to return self d) method chaining cannot be used with attributes, e.g. there is no equivalent to obj = MyObject() ..x=1 ..y=2 Note that this proposal is different from "in" statement [ https://mail.python.org/pipermail/python-ideas/2012-November/017736.html] in the sense that this proposal does not bring anything new into the scope, e.g. obj1 ..obj2 ..x = y translates to tmp1 = obj1 tmp2 = obj2 tmp2.x = y or (simplified) obj1.obj2.x = y no matter if obj2 or obj1 contains variable y. In my opinion, such cascading operator would greatly help creating domain specific languages APIs in Python which are 1) easily readable. It is better than being lost in a repetitive stream of lines looking like obj.x(), obj.y(), obj.z(). Note that repetitive stream of lines (apart from being too verbose) also introduces a possibility of mistyping the name of the object 2) easy to implement (no special API considerations as returning self from each function call) What do you think? Peter

On 21 November 2013 20:55, Perešíni Peter <ppershing@gmail.com> wrote:
If you just want structural grouping of some code, you can already define an appropriate context manager: @contextlib.contextmanager def value(x) yield x with value(gnuplot.newPlot()) as p: p.set("xrange [0:5]") p.set("yrange [0:20]") with value(p.newPlot()) as n: n.addSeries("Linear", [1,2,3]) n.addSeries("Quadratic", [1,4,6]) p.run() It doesn't define a new scope, but it can sometimes help break up a large block of initialisation code (although often a helper function or two may be a better way to do that). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 2013-11-21, at 13:40 , Nick Coghlan <ncoghlan@gmail.com> wrote:
And it requires naming things. An other drawback is that it is a statement, where the cascading operator yields an expression (at least in Smalltalk it did) (well of course more or less everything was an expression in smalltalk so that helped). I really liked message cascading when I played with it in Smalltalk, but: 1. I find Dart’s cascading syntax rather noisy, and examples on The Internets seem to show it regularly used in single-line expression which I find a detriment to readability: document.body.children.add(new ButtonElement()..id='awesome'..text='Click Me!’;); 2. Unless I missed it, the original suggestion failed to specify what the overall expression returns. In Smalltalk, it returns the value of the last message of the cascade, and Smalltalk has a `yourself` message which returns, well, its self. IIRC, Python has no such message “built in”. 3. It encourages and facilitates APIs based on object mutation and incompletely initialised objects, the former being a slight dislike and the latter being something I loathe. 4. I doubt OP’s (a) would be fixed, it’s not an issue of attribute deref, so a cascading operator would have the exact same behaviour.

On Thu, Nov 21, 2013 at 2:34 PM, Masklinn <masklinn@masklinn.net> wrote:
Exactly, point of the proposal is to avoid repetitive naming
Agree, this example is extra bad and unreadable. If we want cascading (and especially nested cascading), I would force cascading operator to be the first token on a new (and indented) line as in my examples
It would return the result of the expression before cascading, e.g. o = MyObject() ..set(x) ..set(y) is a syntactic sugar for tmp = MyObject() tmp.set(x) tmp.set(y) o = tmp Note that we need a get the priority right though, e.g. cascading operator takes precedence over assignment in order to avoid surprises 3. It encourages and facilitates APIs based on object mutation and
incompletely initialised objects, the former being a slight dislike and the latter being something I loathe.
Agree. It may lead to a bit more sloppier API design than usual.

On Fri, Nov 22, 2013 at 1:08 AM, Perešíni Peter <ppershing@gmail.com> wrote:
In that case, why have a separate cascade operator? o = MyObject() .set(x) .set(y) There is one problem with this syntax, though (whether it's a separate operator or not): it makes parsing a little harder. The previous statement looks complete, and then there's an indented block. Trying to type this at the interactive interpreter will be awkward - there'll need to be a way to tell it "Hey, there's more coming, don't finish yet" even though there's nothing on that opening line that tells it so. Would it be worth putting a colon at the end, as per if/while/etc? o = MyObject(): .set(x) .set(y) The same considerations apply to editors that auto-indent, too; making it clear that there's more to come is, imho, a Good Thing. ChrisA

On 2013-11-21, at 15:19 , Chris Angelico <rosuav@gmail.com> wrote:
The primary issue with it is that the API must be crafted specifically for chaining: everything must be done with methods, and methods must return their `self`. Not only does this mean mutator methods which could return something else can’t, it goes against the usual Python grain (at least that of the builtins and standard library) where mutator methods generally return None. Cascading adds “chaining” to all (mutable) objects without having to alter them or build the API specifically to that end. Or duplicate setattr & setitem via additional methods. But yes, as noted if the situation of “infix operators line breaks” is not changed, it will also affect chaining (not that I think it’s a big deal to put a chain in parens).

On Fri, Nov 22, 2013 at 1:56 AM, Masklinn <masklinn@masklinn.net> wrote:
I get that. But what I'm saying is that this is needing some clear definitions in terms of indentation and beginnings of lines anyway, so it clearly cannot conflict with method chaining. It can simply use the dot, so it'll look like normal method invocation, but with an indent meaning "same as the previous" - like how a BIND file is often laid out: @ IN SOA .... blah blah .... IN NS ns1.blah.blah IN NS ns2.blah.blah IN MX 10 mail This would have things look pretty much the same: foo.bar(): .quux() .asdf() .qwer() ChrisA

I really like the colon to show that the indentation is going to change -- it is Pythonic and consistent with the language plus helps both interactive console and parsers/editors to expect the indentation. However, I would still make cascading operator different from dot just to be more explicit (to avoid confusion of the programmers that do not know about this feature yet -- double dot will indicate that this is a new syntax) On Thu, Nov 21, 2013 at 4:00 PM, Chris Angelico <rosuav@gmail.com> wrote:

On Fri, Nov 22, 2013 at 3:17 AM, Perešíni Peter <ppershing@gmail.com> wrote:
Good point. I tend to prefer using less syntactic elements rather than more; when there's no reason to distinguish, why distinguish? (Compare C++ with its two different object-member operators, dot for objects and arrow for pointer-to-object. There's no sensible meaning for pointer-dot-token, so why not merge the two operators?) I do see the value here in distinguishing, though. The colon makes parsing unambiguous, as a leading dot has no meaning. I'd say -0 on new syntax (double dot), it might be valuable for clarity but I don't know how necessary it is. ChrisA

IMO this: x = MyString \ .replace(blah, blub) \ .lower() And this: x = MyString: .replace(blah, blub) .lower() ...look way too similar and might introduce really bad bugs. So i am +1 on introducing more new syntax for this new feature. "Perešíni Peter" <ppershing@gmail.com> wrote:

On Nov 21, 2013, at 5:34, Masklinn <masklinn@masklinn.net> wrote:
Is it really that hard to name a plot "p"? Is typing "p.", or reading it, more work than ".."? The FAQ already suggests the "just give it a short name" answer (http://docs.python.org/3.3/faq/design.html#why-doesn-t-python-have-a-with-st...). Nick's suggestion does that plus an indent for readability. Do we actually need more than that?
Cascading has to be a statement. It has statements inside it. It uses indentation. If it's an expression it will have all of the same ambiguity problems that a naive multiline lambda proposal does, and break the simplicity of the language. (That also means it can't be an operator. It has to be special syntax, like =.) So if this is any part of the argument for the proposal, I'm -1.

Is it really that hard to name a plot "p"? Is typing "p.", or reading it, more work than ".."?
Yes, reading `..` can be *considerably* less work than reading `p.`. - With `..` you know exactly where the thing is coming from (the preceding lines) whereas with `p.`, `p` could come from anywhere - when you're eyes are tired you better look closely to make sure you didn't accidentally write `b` which may mean something else in this scope and cause your program to silently malfunction. - You also better make sure you didn't have a variable somewhere else called `p` for Pressure in one of your equations which you just stomped over, or `p` for Momentum, or Price, or Probability. It's not inconceivable that you'd want to plot these things! Generally, having fewer things in the local namespace is good hygiene, and helps prevent name collisions. This is separate from the issue of the proposed syntax (which I don't really like) and whether it's useful enough to warrant special syntax (not sure) On Thu, Nov 21, 2013 at 9:26 AM, Andrew Barnert <abarnert@yahoo.com> wrote:

On Thu, Nov 21, 2013 at 09:48:56AM -0800, Haoyi Li wrote:
If this argument were good, it would be an argument against naming in general. But *naming things* is one of the simplest, most powerful techniques for understanding that human beings have in their mental toolbox, so much so that people tend to name things which don't even exist -- "cold", "dark", "death", "silence" etc. all of which are merely the absense of something rather than something. But I digress. Explicitly named variables are nearly always better than implicit, unnamed variables. It's either to talk about "do something with foo" than "do something with the thing that you last used". In order to reason about such an anonymous implicit block, I reckon that most people will mentally translate it into a named variable. When you have an implicit operand: ..method() # implicitly operates on some unnamed operand that's rather like having an (invisible) placeholder variable: (it)..method() where "it" is implicitly defined elsewhere. I've used such a language, Hypertalk, where you can write code like this: get the number of items of field "Counter" put it + 2 into field "Result" Kind of cool, right? I'm not entirely against the idea. But when you have longer blocks of code, it soon becomes painful to keep track of the implicit "it", and named variables become much more sensible. What is a shortcut for *writing* code becomes a longcut for *reading* and *comprehending* code. If you allow multiple levels of double-dot implicit variables, it becomes much harder to track what the implicit invisible variable is at any such time. The indentation helps, I agree, but even so, I expect it will be rather like dealing with one of those conversations where nobody is ever named directly: "Then he said that she told him that she went to the party he threw last weekend and saw her there, and he tells me that he's upset that he tried to hit on her and he reckons that she wasn't doing enough to discourage him, but I spoke to her and she reckons it was just a bit of harmless flirting and he's just being unreasonable, absolutely nothing happened between her and him and if he keeps on like this there's be nothing happening between her and him either..." Consider also Python tracebacks, you'll see something like this: Traceback (most recent call last): File "spam.py", line 123, in <module> main() File "spam.py", line 97, in main func() File "spam.py", line 26, in func ..draw() TypeError: draw() takes at least 1 argument (0 given) In general, I'd much prefer to see the last two lines with an explicitly named variable: my_artist.draw() TypeError: draw() takes at least 1 argument (0 given) but of course that's not available with the .. proposed syntax.
"People might use the wrong variable name if they're coding while tired, or drunk, or simply careless" is not a good argument against using explicit variable names. People might type + when they mean -, or .. when they mean a single dot, or a dot when they mean >, or any error at all. I find it ironic that you are defending syntax that looks like a speck of dust on the monitor, or a dead pixel, on behalf of people who have tired eyes.
That's an argument against using dumb variable names, not an argument for using variable names.
This at least I agree with. -- Steven

But *naming things* is one of the simplest, most powerful techniques for understanding that human beings have in their mental toolbox
On a philosophical level, there's another simple and powerful technique: putting things somewhere. Many things are defined entirely by their location and only incidentally by their name. The city center isn't defined by the thing called Main Street, but by some vague notion of "there".
Consider also Python tracebacks, you'll see something like this:
Here's somewhere where I think we disagree. When I see tracebacks, I don't care what the snippet that caused it looks like in the traceback. I care about the line number and file name (I don't know how long you spend looking at tracebacks before going to the file/line). This is my point, that often the location alone is enough, and the explicit name doesn't help all that much.
I expect it will be rather like dealing with one of those conversations where nobody is ever named directly:
Have you tried reading an essay/paper which doesn't use he/she/it? I have, and it melts the eyes. Explicitly naming everything instead of using "it" sometimes does *not* make things more clear!
People might type + when they mean -, or .. when they mean a single dot, or a dot when they mean >, or any error at all.
Just because any error has a non-zero change of happening doesn't mean they're all equally probably. In particular, confusing variable names with some language-blessed syntax is almost unheard of. Nobody accidentally shadows *self* in python, or *this* in java, or "_" in scala. Yes, they can, but it just doesn't happen. n != 0, m != 0 doesn't mean n == m
That's an argument against using dumb variable names, not an argument for using variable names.
We're talking in circles though: - You should name all your variables because naming them short isn't harder than not-naming them - We should name them long, because short names are dumb - We should name them short, because long names are verbose and obscure the logic. - GOTO 1 People keep saying "you should do this" and "you should do that", and of course both the suggestions can solve all problems, except that you can't possibly apply both suggestions at the same time -.- On Thu, Nov 21, 2013 at 7:45 PM, Steven D'Aprano <steve@pearwood.info>wrote:

On Fri, Nov 22, 2013 at 3:44 PM, Haoyi Li <haoyi.sg@gmail.com> wrote:
- You should name all your variables because naming them short isn't harder than not-naming them
Converse to this: Sometimes it's a LOT clearer to show that you're working twice with the same object than to show exactly what object you're working with each time. Consider it a case of DRY. Chaining methods shows that you're doing three things to one object; naming the object each time separates them. Both have their uses. ChrisA

On Thu, Nov 21, 2013 at 08:44:14PM -0800, Haoyi Li wrote:
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".
Funny you say that. I hardly ever pay attention to the line number in tracebacks. Most of the time, seeing the immediate call and the relevant line of code is enough for me to zero in on the relevant section ("ah, that failure is in the foo function...."). Almost the only time I care about the line number is when debugging code that I'm not familiar with. But that's okay. I'm not proposing that we strip line numbers just because they're of minimal use to me. I understand that they're of use to some people, and even of me sometimes. So line numbers help in debugging. So does printing the actual line of source code. In the interactive interpreter, where it is not available, I often miss it. Consider: gunslinger.draw() # looks obviously correct artist.draw() # looks obviously incorrect, a drawing needs a subject but: ..draw() # no idea One of the costs, and in my opinion a severe cost, of this proposed syntax is that it encourages a style of writing code which is optimized for writing instead of reading. We write code much more often than we write it.
You've deleted the context of my objection. You objected to the obvious solution of a temporary variable name (say, p) on the basis that when the coder is tired, he might write: p = MyClass() p.spam() b.eggs() by mistake. True, but he also might write: p = MyClass( ..spam() .eggs() # and somewhere further down the code base ) The "tired programmer" objection can be applied to *anything*. I've written . instead of , when tired, **args instead of *args, length(mylist) instead of len(mylist), and just about every error a programmer can make. Out of the infinity of possible errors a tired person might make, why single out the risk of misusing a named temp variable?
Nobody accidentally shadows *self* in python,
I've seen plenty of code that shadows self.
No. It depends on the name. "for i in range(100)" is fine, i as an integer variable is fine. i = MyClass() is not. Dumb names are dumb because they are misleading, not because they're short.
- We should name them short, because long names are verbose and obscure the logic.
I've certainly never made that argument. It is possible to have names which are too long, but that's a style issue. Don't call something the_variable_holding_an_integer_value when "myint" or even "i" will do. -- Steven

Haoyi Li writes:
Is it really that hard to name a plot "p"? Is typing "p.", or reading it, more work than ".."?
Yes, reading `..` can be *considerably* less work than reading `p.`.
Unless you're Tim Peters, in which case ".." looks like grit on your screen. Surely we wouldn't want *that*! Seriously, as far as I can see it's also possible that reading ".." could be much /more? work than reading "p.", especially if ".." can be nested. You may need to parse backward all the way to the beginning of the expression (suite?) to figure out what it refers to.
Well, that's *really* bad style. Such programmers can't be helped. They'll find a way to abuse "..", too. For example, people often recommend reserving something like "_" (or perhaps all single letter identifiers) only in this kind of context where it's used repeatedly in each of a suite of contiguous lines. With such a convention (especially once it becomes habit), the kind of problem you describe is just not going to arise.
Generally, having fewer things in the local namespace is good hygiene, and helps prevent name collisions.
True, but that is easy enough to get by defining a function. Steve

On Thu, Nov 21, 2013 at 09:48:56AM -0800, Haoyi Li wrote:
I stumbled across this and thought it was relevent and amusing: http://www.johndcook.com/blog/2008/02/23/everything-begins-with-p/ -- Steven

On 2013-11-21, at 18:26 , Andrew Barnert <abarnert@yahoo.com> wrote:
Is it really that hard to name a plot "p"? Is typing “p.", or reading it, more work than ".."?
What’s the point of anything then? p = gnuplot.newPlot() p.set(‘xrange [0:5]’) p.set(‘xrange [0:20]’) n = p.newPlot() n.addSeries(“Linear”, [1, 2, 3]) n.addSeries(“Quadratic”, [1, 4, 6]) p.run() is terser and adds no more pressure to the local namespace, the only loss is the pseudo-nested formatting and that’s not really a core goal of cascading.
So if this is any part of the argument for the proposal, I'm -1.
As far as there’s any interest to cascading it’s that it’s terser than sequencing calls with explicit receivers, and that it’s an expression allowing inline initialisation sequences and not requiring creating a binding.

On 22 Nov 2013 05:11, "Masklinn" <masklinn@masklinn.net> wrote:
On 2013-11-21, at 18:26 , Andrew Barnert <abarnert@yahoo.com> wrote:
Is it really that hard to name a plot "p"? Is typing “p.", or reading
it, more work than ".."?
If a statement local namespace is the goal, then see PEPs 403 and 3150. Given the problems with those, this far more limited suggestion has even less chance of approval. Cheers, Nick.

On Nov 21, 2013, at 11:10, Masklinn <masklinn@masklinn.net> wrote:
Are you actually using the word "expression" in it's usual programming-language meaning here? As in, you really do want an expression, not a statement, that has indentation and statements and the like inside of it?

On Fri, Nov 22, 2013 at 10:09 AM, Masklinn <masklinn@masklinn.net> wrote:
An expression, in programming languages, has a value - you can use it as a function parameter, etc. In Python, assignment is NOT an expression, although it kinda looks like one (having two operands and a symbol between them, chained assignment aside). The original proposal definitely worked with a suite of statements and was not restricted to expressions. ChrisA

On 21Nov2013 14:34, Masklinn <masklinn@masklinn.net> wrote:
That's a feature. The last thing I want in a traceback is a recitation of the line: ..foo = 1 Hmm. Setting .foo on... what? Personally, because this is so easy to do with a context manager or even just a simple temporary variable, I'm -1 on the whole cascading idea. Cheers, -- Cameron Simpson <cs@zip.com.au> The wireless music box has no imaginable commercial value. Who would pay for a message sent to nobody in particular? --David Sarnoff's associates in response to his urgings for investment in the radio in the 1920s.

On 11/21/2013 5:55 AM, Perešíni Peter wrote:
I far prefer t=<exp> t.setX(5) t.y=6 The proposal adds no new functionality.
can make this much more powerful in Python (as opposed to Dart) because Python recognizes scope by indentation
Not true. It recognizes compound statement suites by indentation, following a header line ending with ':'. Name scopes are indicated by class and def statements, and somewhat by comprehensions.
This does not look like Python at all ;-). -- Terry Jan Reedy

I like the basic idea. I agree with the sentiment that a colon should be used to introduce it. Let me throw out some monkey wrenches. The following uses :: as the syntax to begin the suite, that is: obj:: ..a = 1 is equivalent to temp = obj temp.a = 1 I'm assuming the .. must be followed by a statement (rewritten as above) except in the case where the line ends with :: in which case it must be an expression as in the example: gnuplot.newPlot():: ..newPlot():: ..adjustPlot('x') ### ..adjustPlot('y') is temp1 = gnuplot.newPlot() temp2 = temp1.newPlot() temp2.adjustPlot('x') ### temp1.adjustPlot('y') Is there any way on the line marked ### to reference temp1? Can I use .. in the middle of a statement/expression? obj:: x = ..x y = ..y + ..z ..f(..a, ..b) equivalent to temp = obj x = temp.x y = temp.y + temp.z temp.f(temp.a, temp.b) Is field/method access the only time where this is useful? Can I do this? obj:: .['a'] = 1 .['b'] = 2 equivalent to temp = obj temp['a'] = 1 temp['b'] = 2 or this? obj:: .(1, 2) equivalent to temp = obj obj(1, 2) What about this very common code: self.x = x self.y = y self.z = z + 1 it would be nice to have a shorthand for this that didn't require writing x and y twice, but this doesn't do it for me and I don't have a better suggestion: self:: .. = x .. = y ..z = z + 1 --- Bruce I'm hiring: http://www.cadencemd.com/info/jobs Latest blog post: Alice's Puzzle Page http://www.vroospeak.com Learn how hackers think: http://j.mp/gruyere-security

For what it's worth, if this idea ever gets to the bikeshedding phase, I think the following looks pretty nice in my editor: gnuplot.newPlot():: |set("xrange [0:5]") |set("yrange [0:20]") |newPlot():: |addSeries("Linear", [1,2,3]) |addSeries("Quadratic", [1,4,6]) |run() -- Andy Henshaw

Dumb names are dumb because they are misleading, not because they're short.
p for momentum, pressure or price is not misleading at all. It's standard terminology in their fields, and when you're writing heavy algebraic code, writing "momentum" all over the place obscures the logic of your equations. They're no dumber than `i` as an iterator variable, which suffers the exact same problem =( I'm not complaining that the names are misleading, I'm saying they pollute the namespace and cause collisions, unless they're misleading *because* of their collidiness, in which case I'm accidentally dumb many times a day.
The proposed syntax has more to do with "then" rather than "there", that is, "the last object used", not "the object at location 1234".
I mean "there" meaning "there in the code", not "there in the computer". It's the lexically-scoped "there", the "there" you're looking at when you're looking at the source code of the program. I don't care what the memory location is.
Out of the infinity of possible errors a tired person might make, why single out the risk of misusing a named temp variable?
Funny you say that. I hardly ever pay attention to the line number in
- It's hard to spot "yeah, f = p * a looks right" (p in this scope means price). You often basically have to do a local dataflow analysis on your function to spot the problematic scopes. - Lots of the other errors you brought up can be found by linters, but I don't know of any linter smart enough to identify misuse of a temp variable. If it's harder for linters to spot, it's an indication that it's harder for humans to spot too. - The interpreter won't throw an exception to tell you you made a mistake, since it's not a SyntaxError or (often) a TypeError/ValueError - Often temp variables with the same name often have similar contents, meaning the program will work fine but silently produce incorrect results = data corruption yay I mean, tired people making SyntaxErrors and crashing their process (the example you gave), or ValueErrors/TypeErrors and crashing production services, aren't that big a deal v.s. silently corrupting large quantities of data by sneakily giving wrong answers. tracebacks I guess we're different. You don't need to keep repeating to me "code is read more times than it's written" just because I read code differently than you =(
You may need to parse backward all the way to the beginning of the expression (suite?) to figure out what it refers to.
But with block of code all referring to (setting attributes, calling methods on) an actual name (e.g. p), you may need to: - Parse *the entire file and all its imports* to figure out where it comes from! - Look closely to make sure the intermediate values of `p` aren''t being passed around where they shouldn't - Make sure nobody accidentally sets `p` in between (e.g. [abs(p) for p in prices] in one of the right-hand-sides) - Trace p up the block to see how it's being used; is it only having attributes assigned to? Having methods called on it? Is it being passed in as a function parameter and the return value being used to replace itself? With a cascading syntax, you don't need to do any of these things. Yeah, you could argue that the name should tell you exactly what it does. No, I don't think that's realistic in practice.
True, but that is easy enough to get by defining a function.
We'll just have to disagree about the "enough" part. I guess I'm far lazier than you are =D. Furthermore, the non-locality of a function call ("where is this function defined") combined with the visibility ("is this function only being called here, or at multiple callsites") is a disadvantage when trying to read code. I'm not wedding for the current ".." syntax; I agree it looks terrible too, including when python's own RestructuredText does it. In general though, the fact that cascading is so limited compared to "just assigning a temporary var" is something I consider a good thing. When I see a cascading block/expression/whatever, I know immediately what the dataflow looks like: start |/ / / / |/ / / |/ / |/ end With a bunch of inputs coming from the top, joining up with the one constant implicit "this" on the right. There are no other places where data is combined. On the other hand, when I see a long block with temporary variables, maybe the dataflow looks like that, but it could also look like this: start |\ /| / |/|\|/ |\|/| |/|\ \ end With a bunch of variables coming in the top, a bunch of variables going out the bottom, and a jumble of ad-hoc recombination of everything in the middle. I then have to construct the dataflow graph in my head before I can figure out what changes will affect which downstream values, whereas in the cascading case such truths are self evident. You could always say "yeah, but that code is sucky spaghetti, it should be broken up into functions" but you can't say "i never write that", because in the end we all contribute to the communal pool of sucky spaghetti. What would be nice are tools to not just express intent (e.g. the identity context-manager) but enforce it, and I think a cascading operator is one of those useful tools. No matter how hard a deadline you're under, you can't just insert extra edges in the cascading-block's dataflow graph without breaking it up. Someone looking at the cascading-block has a hard-guarantee that the section of code he's looking at obeys certain strict (and reasonably intuitive) properties, and doesn't need to spend time tracing dataflow graphs in his head. That's why I consider them easier to read. On Fri, Nov 22, 2013 at 7:44 AM, Chris Angelico <rosuav@gmail.com> wrote:

On Thu, Nov 21, 2013 at 11:55:46AM +0100, Perešíni Peter wrote:
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. In my opinion, we shouldn't encourage classes that require manual instantiation like this: obj = MyClass() obj.foo = "spam" obj.setbar("eggs") obj.make_it_work() # now obj is fully instantiated and ready to be used... not even with your suggested syntax: obj = MyClass() ..foo = "spam" ..setbar("eggs") ..make_it_work() Helping people do the wrong thing is not, in my opinion, an advantage. The use-case of DSLs is perhaps more interesting, but a simple example would go a long way to support the idea.
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() 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 suggest it only looks weird because you've put the opening bracket in the wrong place. I'd write it like this: template = (MyObject() .setX(1) .setY(2) ) a a variation of such, which apart from an extra set of parentheses is virtually exactly the same as your suggestion: template = MyObject() ..setX(1) ..setY(2) without the addition of a new double-dot syntax. To the extent that this suggested cascading dot operator is just used for method chaining, I'm against it because we can already easily chain methods with no new syntax provided the object supports chaining. To the extent that it adds chaining to those objects which don't support it, for example those with mutator methods that return None, that's a more interesting proposal: mylist = list(some_function(a, b, c)) ..sort() ..reverse() ..pop() # output of pop is discarded ..sort() # still operating on the list But I'm going to suggest that this is still not a good idea, since it goes against the Zen "Explicit is better than implicit". As I understand it, this cascade relies on there being an implicit "last object used", and that can be a bit problematic. 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? y = spam() x = eggs() del y ..method() Does that call x.method()? And what do this do? 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"? You're going to need to specify exactly where and under what circumstances the implicit object is set, and where you can use this implicit object cascading operator.
b) method chaining cannot take advantage of multiple scoping, e.g. the only way to write Gnuplot example is
Since I'm not sure I understand your Gnuplot example above, I'm not going to further comment on it here.
c) method chaining needs specialized API, e.g. each method needs to return self
This is a good argument in support of your proposal.
That's a good argument in support of your proposal, but not a terribly powerful argument, since the above is trivially written as: obj = MyObject() obj.x = 1 obj.y = 2 and I for one prefer to see the object being operated on explicitly rather than implicitly. -- Steven

On 11/21/2013 9:16 PM, Steven D'Aprano wrote:
I had the same thought.
which I would want to at least partially be equivalent to something like p = gnuplot.newPlot(xrange=(0,5), yrange = (0,20) q = p.newplot(series = (("linear", [1,2,3]), ("quadratic", [1,4,6]))) p.run() -- Terry Jan Reedy

Thanks Steve for very useful comments
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.
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
I would assume the same
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 think this should be a syntax error. (as it anyway defeats the purpose of making things clear)

Steven D'Aprano:
Perešíni Peter: 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@gmail.com> wrote:

On Fri, Nov 22, 2013 at 10:16 AM, Bruce Leban <bruce@leapyear.org> wrote:
Yes, exactly. As usual I did not write my thoughts clearly. By the last line I really meant "last object on the preceding line ending with :: having the smaller indent than the current line" the return. I am not sure if things like return list(range(10)) ..reverse() ..pop(0) should be allowed or not - having something being executed after the return statement line might be a bit confusing

On Fri, Nov 22, 2013 at 10:32:13AM +0100, Perešíni Peter wrote:
If Python gains this syntax, I don't see why that shouldn't be allowed. That some of the lines of code occur physically after the return keyword is no more a problem than here: return (spam() + eggs() + toast() + milk() + cookies() ) In your case, the suite list(range(10)) ..reverse() ..pop(0) is evaluated first, and then returned. -- Steven

The biggest problem with most of the variants of this idea suggested so far is that they refer to things that don't exist--in particular, they treat the assignment statement, the assignment target, and/or the suite as something with a value. None of them have values, or could have values, without some radical change like a way to treat a suite of statements as an expression. So, in the interests of having something to debate, here's an attempt at a concrete, implementable syntax and semantics without any of these problems that I think offers everything that people want that's actually possible: compound_expression_stmt ::= expression_list ":" suite compound_assignment_stmt ::= (target_list "=")+ (expression_list | yield_expression) ":" suite The only difference to simple expression statements and assignment statements, as defined in 7.1 and 7.2, is that the suite is executed immediately after evaluating the expression list (or yield expression), before assignment of the value (of the expression list or yield expression, not some other value) to the target lists or writing of the repr to the interactive output. dot_attribute_ref ::= ".." identifier A dot attribute reference in the suite of a compound expression or assignment statement is evaluated exactly like a simple attribute reference, as defined in 6.3.1 and in 7.2, except that the value of the expression list or yield expression is used as the value of the primary. I believe a dot attribute reference outside of could raise a SyntaxError at parse time. If not, it raises a ValueError at runtime, because the dot-value of the current suite is None. I believe this handles all of the examples given so far. It can be nested in the obvious way, without needing any new semantics. Assignment statements with expression lists of more than one expression are useless (you can't mutate a tuple), but not illegal. It could be extended to allow dot-subscripting and dot-slicing trivially, if those are desirable. And it's a very simple change to the syntax and semantics of the language, that doesn't have any radical undesirable consequences. I still don't really like the idea, but it's nice to know that it is actually doable. I'm maybe -.5 instead of -1 if it doesn't require statements having values and magic guessing of which statements in a suite replace a value and which don't and so on.

The biggest problem with most of the variants of this idea suggested so far is that they refer to things that don't exist--in particular, they treat the assignment statement, the assignment target, and/or the suite as something with a value. None of them have values, or could have values, without some radical change like a way to treat a suite of statements as an expression. So, in the interests of having something to debate, here's an attempt at a concrete, implementable syntax and semantics without any of these problems that I think offers everything that people want that's actually possible: compound_expression_stmt ::= expression_list ":" suite compound_assignment_stmt ::= (target_list "=")+ (expression_list | yield_expression) ":" suite The only difference to simple expression statements and assignment statements, as defined in 7.1 and 7.2, is that the suite is executed immediately after evaluating the expression list (or yield expression), before assignment of the value (of the expression list or yield expression, not some other value) to the target lists or writing of the repr to the interactive output. dot_attribute_ref ::= ".." identifier A dot attribute reference in the suite of a compound expression or assignment statement is evaluated exactly like a simple attribute reference, as defined in 6.3.1 and in 7.2, except that the value of the expression list or yield expression is used as the value of the primary. I believe a dot attribute reference outside of could raise a SyntaxError at parse time. If not, it raises a ValueError at runtime, because the dot-value of the current suite is None. I believe this handles all of the examples given so far. It can be nested in the obvious way, without needing any new semantics. Assignment statements with expression lists of more than one expression are useless (you can't mutate a tuple), but not illegal. It could be extended to allow dot-subscripting and dot-slicing trivially, if those are desirable. And it's a very simple change to the syntax and semantics of the language, that doesn't have any radical undesirable consequences. I still don't really like the idea, but it's nice to know that it is actually doable. I'm maybe -.5 instead of -1 if it doesn't require statements having values and magic guessing of which statements in a suite replace a value and which don't and so on.

Perešíni Peter writes:
This particular application just screams for positional parameters, though: tree = Tree( Node( Node(value=3), Node(), value=5), Node(color="green")) (YMMV about positioning of the parens, to me this is the one that says "TREE!! and here are the Nodes" most clearly.)

On 2013-11-23, at 12:17 , Stephen J. Turnbull <stephen@xemacs.org> wrote:
Doesn’t work on third-party libraries on which you don’t have control[0] though, and when there are many customisation points having subsequent methods can make for a nicer API than 50 constructor parameters[1]. [0] such as the standard library’s, unless I am mistaken neither minidom nor elementtree allow complete element configuration — including subtree — in a single expression. The third-party lxml does to an extent, if one uses the builder APIs rather than the ET ones. [1] or the existing API may make it difficult to impossible to add such an extension without breaking

On 21 November 2013 20:55, Perešíni Peter <ppershing@gmail.com> wrote:
If you just want structural grouping of some code, you can already define an appropriate context manager: @contextlib.contextmanager def value(x) yield x with value(gnuplot.newPlot()) as p: p.set("xrange [0:5]") p.set("yrange [0:20]") with value(p.newPlot()) as n: n.addSeries("Linear", [1,2,3]) n.addSeries("Quadratic", [1,4,6]) p.run() It doesn't define a new scope, but it can sometimes help break up a large block of initialisation code (although often a helper function or two may be a better way to do that). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 2013-11-21, at 13:40 , Nick Coghlan <ncoghlan@gmail.com> wrote:
And it requires naming things. An other drawback is that it is a statement, where the cascading operator yields an expression (at least in Smalltalk it did) (well of course more or less everything was an expression in smalltalk so that helped). I really liked message cascading when I played with it in Smalltalk, but: 1. I find Dart’s cascading syntax rather noisy, and examples on The Internets seem to show it regularly used in single-line expression which I find a detriment to readability: document.body.children.add(new ButtonElement()..id='awesome'..text='Click Me!’;); 2. Unless I missed it, the original suggestion failed to specify what the overall expression returns. In Smalltalk, it returns the value of the last message of the cascade, and Smalltalk has a `yourself` message which returns, well, its self. IIRC, Python has no such message “built in”. 3. It encourages and facilitates APIs based on object mutation and incompletely initialised objects, the former being a slight dislike and the latter being something I loathe. 4. I doubt OP’s (a) would be fixed, it’s not an issue of attribute deref, so a cascading operator would have the exact same behaviour.

On Thu, Nov 21, 2013 at 2:34 PM, Masklinn <masklinn@masklinn.net> wrote:
Exactly, point of the proposal is to avoid repetitive naming
Agree, this example is extra bad and unreadable. If we want cascading (and especially nested cascading), I would force cascading operator to be the first token on a new (and indented) line as in my examples
It would return the result of the expression before cascading, e.g. o = MyObject() ..set(x) ..set(y) is a syntactic sugar for tmp = MyObject() tmp.set(x) tmp.set(y) o = tmp Note that we need a get the priority right though, e.g. cascading operator takes precedence over assignment in order to avoid surprises 3. It encourages and facilitates APIs based on object mutation and
incompletely initialised objects, the former being a slight dislike and the latter being something I loathe.
Agree. It may lead to a bit more sloppier API design than usual.

On Fri, Nov 22, 2013 at 1:08 AM, Perešíni Peter <ppershing@gmail.com> wrote:
In that case, why have a separate cascade operator? o = MyObject() .set(x) .set(y) There is one problem with this syntax, though (whether it's a separate operator or not): it makes parsing a little harder. The previous statement looks complete, and then there's an indented block. Trying to type this at the interactive interpreter will be awkward - there'll need to be a way to tell it "Hey, there's more coming, don't finish yet" even though there's nothing on that opening line that tells it so. Would it be worth putting a colon at the end, as per if/while/etc? o = MyObject(): .set(x) .set(y) The same considerations apply to editors that auto-indent, too; making it clear that there's more to come is, imho, a Good Thing. ChrisA

On 2013-11-21, at 15:19 , Chris Angelico <rosuav@gmail.com> wrote:
The primary issue with it is that the API must be crafted specifically for chaining: everything must be done with methods, and methods must return their `self`. Not only does this mean mutator methods which could return something else can’t, it goes against the usual Python grain (at least that of the builtins and standard library) where mutator methods generally return None. Cascading adds “chaining” to all (mutable) objects without having to alter them or build the API specifically to that end. Or duplicate setattr & setitem via additional methods. But yes, as noted if the situation of “infix operators line breaks” is not changed, it will also affect chaining (not that I think it’s a big deal to put a chain in parens).

On Fri, Nov 22, 2013 at 1:56 AM, Masklinn <masklinn@masklinn.net> wrote:
I get that. But what I'm saying is that this is needing some clear definitions in terms of indentation and beginnings of lines anyway, so it clearly cannot conflict with method chaining. It can simply use the dot, so it'll look like normal method invocation, but with an indent meaning "same as the previous" - like how a BIND file is often laid out: @ IN SOA .... blah blah .... IN NS ns1.blah.blah IN NS ns2.blah.blah IN MX 10 mail This would have things look pretty much the same: foo.bar(): .quux() .asdf() .qwer() ChrisA

I really like the colon to show that the indentation is going to change -- it is Pythonic and consistent with the language plus helps both interactive console and parsers/editors to expect the indentation. However, I would still make cascading operator different from dot just to be more explicit (to avoid confusion of the programmers that do not know about this feature yet -- double dot will indicate that this is a new syntax) On Thu, Nov 21, 2013 at 4:00 PM, Chris Angelico <rosuav@gmail.com> wrote:

On Fri, Nov 22, 2013 at 3:17 AM, Perešíni Peter <ppershing@gmail.com> wrote:
Good point. I tend to prefer using less syntactic elements rather than more; when there's no reason to distinguish, why distinguish? (Compare C++ with its two different object-member operators, dot for objects and arrow for pointer-to-object. There's no sensible meaning for pointer-dot-token, so why not merge the two operators?) I do see the value here in distinguishing, though. The colon makes parsing unambiguous, as a leading dot has no meaning. I'd say -0 on new syntax (double dot), it might be valuable for clarity but I don't know how necessary it is. ChrisA

IMO this: x = MyString \ .replace(blah, blub) \ .lower() And this: x = MyString: .replace(blah, blub) .lower() ...look way too similar and might introduce really bad bugs. So i am +1 on introducing more new syntax for this new feature. "Perešíni Peter" <ppershing@gmail.com> wrote:

On Nov 21, 2013, at 5:34, Masklinn <masklinn@masklinn.net> wrote:
Is it really that hard to name a plot "p"? Is typing "p.", or reading it, more work than ".."? The FAQ already suggests the "just give it a short name" answer (http://docs.python.org/3.3/faq/design.html#why-doesn-t-python-have-a-with-st...). Nick's suggestion does that plus an indent for readability. Do we actually need more than that?
Cascading has to be a statement. It has statements inside it. It uses indentation. If it's an expression it will have all of the same ambiguity problems that a naive multiline lambda proposal does, and break the simplicity of the language. (That also means it can't be an operator. It has to be special syntax, like =.) So if this is any part of the argument for the proposal, I'm -1.

Is it really that hard to name a plot "p"? Is typing "p.", or reading it, more work than ".."?
Yes, reading `..` can be *considerably* less work than reading `p.`. - With `..` you know exactly where the thing is coming from (the preceding lines) whereas with `p.`, `p` could come from anywhere - when you're eyes are tired you better look closely to make sure you didn't accidentally write `b` which may mean something else in this scope and cause your program to silently malfunction. - You also better make sure you didn't have a variable somewhere else called `p` for Pressure in one of your equations which you just stomped over, or `p` for Momentum, or Price, or Probability. It's not inconceivable that you'd want to plot these things! Generally, having fewer things in the local namespace is good hygiene, and helps prevent name collisions. This is separate from the issue of the proposed syntax (which I don't really like) and whether it's useful enough to warrant special syntax (not sure) On Thu, Nov 21, 2013 at 9:26 AM, Andrew Barnert <abarnert@yahoo.com> wrote:

On Thu, Nov 21, 2013 at 09:48:56AM -0800, Haoyi Li wrote:
If this argument were good, it would be an argument against naming in general. But *naming things* is one of the simplest, most powerful techniques for understanding that human beings have in their mental toolbox, so much so that people tend to name things which don't even exist -- "cold", "dark", "death", "silence" etc. all of which are merely the absense of something rather than something. But I digress. Explicitly named variables are nearly always better than implicit, unnamed variables. It's either to talk about "do something with foo" than "do something with the thing that you last used". In order to reason about such an anonymous implicit block, I reckon that most people will mentally translate it into a named variable. When you have an implicit operand: ..method() # implicitly operates on some unnamed operand that's rather like having an (invisible) placeholder variable: (it)..method() where "it" is implicitly defined elsewhere. I've used such a language, Hypertalk, where you can write code like this: get the number of items of field "Counter" put it + 2 into field "Result" Kind of cool, right? I'm not entirely against the idea. But when you have longer blocks of code, it soon becomes painful to keep track of the implicit "it", and named variables become much more sensible. What is a shortcut for *writing* code becomes a longcut for *reading* and *comprehending* code. If you allow multiple levels of double-dot implicit variables, it becomes much harder to track what the implicit invisible variable is at any such time. The indentation helps, I agree, but even so, I expect it will be rather like dealing with one of those conversations where nobody is ever named directly: "Then he said that she told him that she went to the party he threw last weekend and saw her there, and he tells me that he's upset that he tried to hit on her and he reckons that she wasn't doing enough to discourage him, but I spoke to her and she reckons it was just a bit of harmless flirting and he's just being unreasonable, absolutely nothing happened between her and him and if he keeps on like this there's be nothing happening between her and him either..." Consider also Python tracebacks, you'll see something like this: Traceback (most recent call last): File "spam.py", line 123, in <module> main() File "spam.py", line 97, in main func() File "spam.py", line 26, in func ..draw() TypeError: draw() takes at least 1 argument (0 given) In general, I'd much prefer to see the last two lines with an explicitly named variable: my_artist.draw() TypeError: draw() takes at least 1 argument (0 given) but of course that's not available with the .. proposed syntax.
"People might use the wrong variable name if they're coding while tired, or drunk, or simply careless" is not a good argument against using explicit variable names. People might type + when they mean -, or .. when they mean a single dot, or a dot when they mean >, or any error at all. I find it ironic that you are defending syntax that looks like a speck of dust on the monitor, or a dead pixel, on behalf of people who have tired eyes.
That's an argument against using dumb variable names, not an argument for using variable names.
This at least I agree with. -- Steven

But *naming things* is one of the simplest, most powerful techniques for understanding that human beings have in their mental toolbox
On a philosophical level, there's another simple and powerful technique: putting things somewhere. Many things are defined entirely by their location and only incidentally by their name. The city center isn't defined by the thing called Main Street, but by some vague notion of "there".
Consider also Python tracebacks, you'll see something like this:
Here's somewhere where I think we disagree. When I see tracebacks, I don't care what the snippet that caused it looks like in the traceback. I care about the line number and file name (I don't know how long you spend looking at tracebacks before going to the file/line). This is my point, that often the location alone is enough, and the explicit name doesn't help all that much.
I expect it will be rather like dealing with one of those conversations where nobody is ever named directly:
Have you tried reading an essay/paper which doesn't use he/she/it? I have, and it melts the eyes. Explicitly naming everything instead of using "it" sometimes does *not* make things more clear!
People might type + when they mean -, or .. when they mean a single dot, or a dot when they mean >, or any error at all.
Just because any error has a non-zero change of happening doesn't mean they're all equally probably. In particular, confusing variable names with some language-blessed syntax is almost unheard of. Nobody accidentally shadows *self* in python, or *this* in java, or "_" in scala. Yes, they can, but it just doesn't happen. n != 0, m != 0 doesn't mean n == m
That's an argument against using dumb variable names, not an argument for using variable names.
We're talking in circles though: - You should name all your variables because naming them short isn't harder than not-naming them - We should name them long, because short names are dumb - We should name them short, because long names are verbose and obscure the logic. - GOTO 1 People keep saying "you should do this" and "you should do that", and of course both the suggestions can solve all problems, except that you can't possibly apply both suggestions at the same time -.- On Thu, Nov 21, 2013 at 7:45 PM, Steven D'Aprano <steve@pearwood.info>wrote:

On Fri, Nov 22, 2013 at 3:44 PM, Haoyi Li <haoyi.sg@gmail.com> wrote:
- You should name all your variables because naming them short isn't harder than not-naming them
Converse to this: Sometimes it's a LOT clearer to show that you're working twice with the same object than to show exactly what object you're working with each time. Consider it a case of DRY. Chaining methods shows that you're doing three things to one object; naming the object each time separates them. Both have their uses. ChrisA

On Thu, Nov 21, 2013 at 08:44:14PM -0800, Haoyi Li wrote:
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".
Funny you say that. I hardly ever pay attention to the line number in tracebacks. Most of the time, seeing the immediate call and the relevant line of code is enough for me to zero in on the relevant section ("ah, that failure is in the foo function...."). Almost the only time I care about the line number is when debugging code that I'm not familiar with. But that's okay. I'm not proposing that we strip line numbers just because they're of minimal use to me. I understand that they're of use to some people, and even of me sometimes. So line numbers help in debugging. So does printing the actual line of source code. In the interactive interpreter, where it is not available, I often miss it. Consider: gunslinger.draw() # looks obviously correct artist.draw() # looks obviously incorrect, a drawing needs a subject but: ..draw() # no idea One of the costs, and in my opinion a severe cost, of this proposed syntax is that it encourages a style of writing code which is optimized for writing instead of reading. We write code much more often than we write it.
You've deleted the context of my objection. You objected to the obvious solution of a temporary variable name (say, p) on the basis that when the coder is tired, he might write: p = MyClass() p.spam() b.eggs() by mistake. True, but he also might write: p = MyClass( ..spam() .eggs() # and somewhere further down the code base ) The "tired programmer" objection can be applied to *anything*. I've written . instead of , when tired, **args instead of *args, length(mylist) instead of len(mylist), and just about every error a programmer can make. Out of the infinity of possible errors a tired person might make, why single out the risk of misusing a named temp variable?
Nobody accidentally shadows *self* in python,
I've seen plenty of code that shadows self.
No. It depends on the name. "for i in range(100)" is fine, i as an integer variable is fine. i = MyClass() is not. Dumb names are dumb because they are misleading, not because they're short.
- We should name them short, because long names are verbose and obscure the logic.
I've certainly never made that argument. It is possible to have names which are too long, but that's a style issue. Don't call something the_variable_holding_an_integer_value when "myint" or even "i" will do. -- Steven

Haoyi Li writes:
Is it really that hard to name a plot "p"? Is typing "p.", or reading it, more work than ".."?
Yes, reading `..` can be *considerably* less work than reading `p.`.
Unless you're Tim Peters, in which case ".." looks like grit on your screen. Surely we wouldn't want *that*! Seriously, as far as I can see it's also possible that reading ".." could be much /more? work than reading "p.", especially if ".." can be nested. You may need to parse backward all the way to the beginning of the expression (suite?) to figure out what it refers to.
Well, that's *really* bad style. Such programmers can't be helped. They'll find a way to abuse "..", too. For example, people often recommend reserving something like "_" (or perhaps all single letter identifiers) only in this kind of context where it's used repeatedly in each of a suite of contiguous lines. With such a convention (especially once it becomes habit), the kind of problem you describe is just not going to arise.
Generally, having fewer things in the local namespace is good hygiene, and helps prevent name collisions.
True, but that is easy enough to get by defining a function. Steve

On Thu, Nov 21, 2013 at 09:48:56AM -0800, Haoyi Li wrote:
I stumbled across this and thought it was relevent and amusing: http://www.johndcook.com/blog/2008/02/23/everything-begins-with-p/ -- Steven

On 2013-11-21, at 18:26 , Andrew Barnert <abarnert@yahoo.com> wrote:
Is it really that hard to name a plot "p"? Is typing “p.", or reading it, more work than ".."?
What’s the point of anything then? p = gnuplot.newPlot() p.set(‘xrange [0:5]’) p.set(‘xrange [0:20]’) n = p.newPlot() n.addSeries(“Linear”, [1, 2, 3]) n.addSeries(“Quadratic”, [1, 4, 6]) p.run() is terser and adds no more pressure to the local namespace, the only loss is the pseudo-nested formatting and that’s not really a core goal of cascading.
So if this is any part of the argument for the proposal, I'm -1.
As far as there’s any interest to cascading it’s that it’s terser than sequencing calls with explicit receivers, and that it’s an expression allowing inline initialisation sequences and not requiring creating a binding.

On 22 Nov 2013 05:11, "Masklinn" <masklinn@masklinn.net> wrote:
On 2013-11-21, at 18:26 , Andrew Barnert <abarnert@yahoo.com> wrote:
Is it really that hard to name a plot "p"? Is typing “p.", or reading
it, more work than ".."?
If a statement local namespace is the goal, then see PEPs 403 and 3150. Given the problems with those, this far more limited suggestion has even less chance of approval. Cheers, Nick.

On Nov 21, 2013, at 11:10, Masklinn <masklinn@masklinn.net> wrote:
Are you actually using the word "expression" in it's usual programming-language meaning here? As in, you really do want an expression, not a statement, that has indentation and statements and the like inside of it?

On Fri, Nov 22, 2013 at 10:09 AM, Masklinn <masklinn@masklinn.net> wrote:
An expression, in programming languages, has a value - you can use it as a function parameter, etc. In Python, assignment is NOT an expression, although it kinda looks like one (having two operands and a symbol between them, chained assignment aside). The original proposal definitely worked with a suite of statements and was not restricted to expressions. ChrisA

On 21Nov2013 14:34, Masklinn <masklinn@masklinn.net> wrote:
That's a feature. The last thing I want in a traceback is a recitation of the line: ..foo = 1 Hmm. Setting .foo on... what? Personally, because this is so easy to do with a context manager or even just a simple temporary variable, I'm -1 on the whole cascading idea. Cheers, -- Cameron Simpson <cs@zip.com.au> The wireless music box has no imaginable commercial value. Who would pay for a message sent to nobody in particular? --David Sarnoff's associates in response to his urgings for investment in the radio in the 1920s.

On 11/21/2013 5:55 AM, Perešíni Peter wrote:
I far prefer t=<exp> t.setX(5) t.y=6 The proposal adds no new functionality.
can make this much more powerful in Python (as opposed to Dart) because Python recognizes scope by indentation
Not true. It recognizes compound statement suites by indentation, following a header line ending with ':'. Name scopes are indicated by class and def statements, and somewhat by comprehensions.
This does not look like Python at all ;-). -- Terry Jan Reedy

I like the basic idea. I agree with the sentiment that a colon should be used to introduce it. Let me throw out some monkey wrenches. The following uses :: as the syntax to begin the suite, that is: obj:: ..a = 1 is equivalent to temp = obj temp.a = 1 I'm assuming the .. must be followed by a statement (rewritten as above) except in the case where the line ends with :: in which case it must be an expression as in the example: gnuplot.newPlot():: ..newPlot():: ..adjustPlot('x') ### ..adjustPlot('y') is temp1 = gnuplot.newPlot() temp2 = temp1.newPlot() temp2.adjustPlot('x') ### temp1.adjustPlot('y') Is there any way on the line marked ### to reference temp1? Can I use .. in the middle of a statement/expression? obj:: x = ..x y = ..y + ..z ..f(..a, ..b) equivalent to temp = obj x = temp.x y = temp.y + temp.z temp.f(temp.a, temp.b) Is field/method access the only time where this is useful? Can I do this? obj:: .['a'] = 1 .['b'] = 2 equivalent to temp = obj temp['a'] = 1 temp['b'] = 2 or this? obj:: .(1, 2) equivalent to temp = obj obj(1, 2) What about this very common code: self.x = x self.y = y self.z = z + 1 it would be nice to have a shorthand for this that didn't require writing x and y twice, but this doesn't do it for me and I don't have a better suggestion: self:: .. = x .. = y ..z = z + 1 --- Bruce I'm hiring: http://www.cadencemd.com/info/jobs Latest blog post: Alice's Puzzle Page http://www.vroospeak.com Learn how hackers think: http://j.mp/gruyere-security

For what it's worth, if this idea ever gets to the bikeshedding phase, I think the following looks pretty nice in my editor: gnuplot.newPlot():: |set("xrange [0:5]") |set("yrange [0:20]") |newPlot():: |addSeries("Linear", [1,2,3]) |addSeries("Quadratic", [1,4,6]) |run() -- Andy Henshaw

Dumb names are dumb because they are misleading, not because they're short.
p for momentum, pressure or price is not misleading at all. It's standard terminology in their fields, and when you're writing heavy algebraic code, writing "momentum" all over the place obscures the logic of your equations. They're no dumber than `i` as an iterator variable, which suffers the exact same problem =( I'm not complaining that the names are misleading, I'm saying they pollute the namespace and cause collisions, unless they're misleading *because* of their collidiness, in which case I'm accidentally dumb many times a day.
The proposed syntax has more to do with "then" rather than "there", that is, "the last object used", not "the object at location 1234".
I mean "there" meaning "there in the code", not "there in the computer". It's the lexically-scoped "there", the "there" you're looking at when you're looking at the source code of the program. I don't care what the memory location is.
Out of the infinity of possible errors a tired person might make, why single out the risk of misusing a named temp variable?
Funny you say that. I hardly ever pay attention to the line number in
- It's hard to spot "yeah, f = p * a looks right" (p in this scope means price). You often basically have to do a local dataflow analysis on your function to spot the problematic scopes. - Lots of the other errors you brought up can be found by linters, but I don't know of any linter smart enough to identify misuse of a temp variable. If it's harder for linters to spot, it's an indication that it's harder for humans to spot too. - The interpreter won't throw an exception to tell you you made a mistake, since it's not a SyntaxError or (often) a TypeError/ValueError - Often temp variables with the same name often have similar contents, meaning the program will work fine but silently produce incorrect results = data corruption yay I mean, tired people making SyntaxErrors and crashing their process (the example you gave), or ValueErrors/TypeErrors and crashing production services, aren't that big a deal v.s. silently corrupting large quantities of data by sneakily giving wrong answers. tracebacks I guess we're different. You don't need to keep repeating to me "code is read more times than it's written" just because I read code differently than you =(
You may need to parse backward all the way to the beginning of the expression (suite?) to figure out what it refers to.
But with block of code all referring to (setting attributes, calling methods on) an actual name (e.g. p), you may need to: - Parse *the entire file and all its imports* to figure out where it comes from! - Look closely to make sure the intermediate values of `p` aren''t being passed around where they shouldn't - Make sure nobody accidentally sets `p` in between (e.g. [abs(p) for p in prices] in one of the right-hand-sides) - Trace p up the block to see how it's being used; is it only having attributes assigned to? Having methods called on it? Is it being passed in as a function parameter and the return value being used to replace itself? With a cascading syntax, you don't need to do any of these things. Yeah, you could argue that the name should tell you exactly what it does. No, I don't think that's realistic in practice.
True, but that is easy enough to get by defining a function.
We'll just have to disagree about the "enough" part. I guess I'm far lazier than you are =D. Furthermore, the non-locality of a function call ("where is this function defined") combined with the visibility ("is this function only being called here, or at multiple callsites") is a disadvantage when trying to read code. I'm not wedding for the current ".." syntax; I agree it looks terrible too, including when python's own RestructuredText does it. In general though, the fact that cascading is so limited compared to "just assigning a temporary var" is something I consider a good thing. When I see a cascading block/expression/whatever, I know immediately what the dataflow looks like: start |/ / / / |/ / / |/ / |/ end With a bunch of inputs coming from the top, joining up with the one constant implicit "this" on the right. There are no other places where data is combined. On the other hand, when I see a long block with temporary variables, maybe the dataflow looks like that, but it could also look like this: start |\ /| / |/|\|/ |\|/| |/|\ \ end With a bunch of variables coming in the top, a bunch of variables going out the bottom, and a jumble of ad-hoc recombination of everything in the middle. I then have to construct the dataflow graph in my head before I can figure out what changes will affect which downstream values, whereas in the cascading case such truths are self evident. You could always say "yeah, but that code is sucky spaghetti, it should be broken up into functions" but you can't say "i never write that", because in the end we all contribute to the communal pool of sucky spaghetti. What would be nice are tools to not just express intent (e.g. the identity context-manager) but enforce it, and I think a cascading operator is one of those useful tools. No matter how hard a deadline you're under, you can't just insert extra edges in the cascading-block's dataflow graph without breaking it up. Someone looking at the cascading-block has a hard-guarantee that the section of code he's looking at obeys certain strict (and reasonably intuitive) properties, and doesn't need to spend time tracing dataflow graphs in his head. That's why I consider them easier to read. On Fri, Nov 22, 2013 at 7:44 AM, Chris Angelico <rosuav@gmail.com> wrote:

On Thu, Nov 21, 2013 at 11:55:46AM +0100, Perešíni Peter wrote:
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. In my opinion, we shouldn't encourage classes that require manual instantiation like this: obj = MyClass() obj.foo = "spam" obj.setbar("eggs") obj.make_it_work() # now obj is fully instantiated and ready to be used... not even with your suggested syntax: obj = MyClass() ..foo = "spam" ..setbar("eggs") ..make_it_work() Helping people do the wrong thing is not, in my opinion, an advantage. The use-case of DSLs is perhaps more interesting, but a simple example would go a long way to support the idea.
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() 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 suggest it only looks weird because you've put the opening bracket in the wrong place. I'd write it like this: template = (MyObject() .setX(1) .setY(2) ) a a variation of such, which apart from an extra set of parentheses is virtually exactly the same as your suggestion: template = MyObject() ..setX(1) ..setY(2) without the addition of a new double-dot syntax. To the extent that this suggested cascading dot operator is just used for method chaining, I'm against it because we can already easily chain methods with no new syntax provided the object supports chaining. To the extent that it adds chaining to those objects which don't support it, for example those with mutator methods that return None, that's a more interesting proposal: mylist = list(some_function(a, b, c)) ..sort() ..reverse() ..pop() # output of pop is discarded ..sort() # still operating on the list But I'm going to suggest that this is still not a good idea, since it goes against the Zen "Explicit is better than implicit". As I understand it, this cascade relies on there being an implicit "last object used", and that can be a bit problematic. 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? y = spam() x = eggs() del y ..method() Does that call x.method()? And what do this do? 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"? You're going to need to specify exactly where and under what circumstances the implicit object is set, and where you can use this implicit object cascading operator.
b) method chaining cannot take advantage of multiple scoping, e.g. the only way to write Gnuplot example is
Since I'm not sure I understand your Gnuplot example above, I'm not going to further comment on it here.
c) method chaining needs specialized API, e.g. each method needs to return self
This is a good argument in support of your proposal.
That's a good argument in support of your proposal, but not a terribly powerful argument, since the above is trivially written as: obj = MyObject() obj.x = 1 obj.y = 2 and I for one prefer to see the object being operated on explicitly rather than implicitly. -- Steven

On 11/21/2013 9:16 PM, Steven D'Aprano wrote:
I had the same thought.
which I would want to at least partially be equivalent to something like p = gnuplot.newPlot(xrange=(0,5), yrange = (0,20) q = p.newplot(series = (("linear", [1,2,3]), ("quadratic", [1,4,6]))) p.run() -- Terry Jan Reedy

Thanks Steve for very useful comments
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.
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
I would assume the same
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 think this should be a syntax error. (as it anyway defeats the purpose of making things clear)

Steven D'Aprano:
Perešíni Peter: 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@gmail.com> wrote:

On Fri, Nov 22, 2013 at 10:16 AM, Bruce Leban <bruce@leapyear.org> wrote:
Yes, exactly. As usual I did not write my thoughts clearly. By the last line I really meant "last object on the preceding line ending with :: having the smaller indent than the current line" the return. I am not sure if things like return list(range(10)) ..reverse() ..pop(0) should be allowed or not - having something being executed after the return statement line might be a bit confusing

On Fri, Nov 22, 2013 at 10:32:13AM +0100, Perešíni Peter wrote:
If Python gains this syntax, I don't see why that shouldn't be allowed. That some of the lines of code occur physically after the return keyword is no more a problem than here: return (spam() + eggs() + toast() + milk() + cookies() ) In your case, the suite list(range(10)) ..reverse() ..pop(0) is evaluated first, and then returned. -- Steven

The biggest problem with most of the variants of this idea suggested so far is that they refer to things that don't exist--in particular, they treat the assignment statement, the assignment target, and/or the suite as something with a value. None of them have values, or could have values, without some radical change like a way to treat a suite of statements as an expression. So, in the interests of having something to debate, here's an attempt at a concrete, implementable syntax and semantics without any of these problems that I think offers everything that people want that's actually possible: compound_expression_stmt ::= expression_list ":" suite compound_assignment_stmt ::= (target_list "=")+ (expression_list | yield_expression) ":" suite The only difference to simple expression statements and assignment statements, as defined in 7.1 and 7.2, is that the suite is executed immediately after evaluating the expression list (or yield expression), before assignment of the value (of the expression list or yield expression, not some other value) to the target lists or writing of the repr to the interactive output. dot_attribute_ref ::= ".." identifier A dot attribute reference in the suite of a compound expression or assignment statement is evaluated exactly like a simple attribute reference, as defined in 6.3.1 and in 7.2, except that the value of the expression list or yield expression is used as the value of the primary. I believe a dot attribute reference outside of could raise a SyntaxError at parse time. If not, it raises a ValueError at runtime, because the dot-value of the current suite is None. I believe this handles all of the examples given so far. It can be nested in the obvious way, without needing any new semantics. Assignment statements with expression lists of more than one expression are useless (you can't mutate a tuple), but not illegal. It could be extended to allow dot-subscripting and dot-slicing trivially, if those are desirable. And it's a very simple change to the syntax and semantics of the language, that doesn't have any radical undesirable consequences. I still don't really like the idea, but it's nice to know that it is actually doable. I'm maybe -.5 instead of -1 if it doesn't require statements having values and magic guessing of which statements in a suite replace a value and which don't and so on.

The biggest problem with most of the variants of this idea suggested so far is that they refer to things that don't exist--in particular, they treat the assignment statement, the assignment target, and/or the suite as something with a value. None of them have values, or could have values, without some radical change like a way to treat a suite of statements as an expression. So, in the interests of having something to debate, here's an attempt at a concrete, implementable syntax and semantics without any of these problems that I think offers everything that people want that's actually possible: compound_expression_stmt ::= expression_list ":" suite compound_assignment_stmt ::= (target_list "=")+ (expression_list | yield_expression) ":" suite The only difference to simple expression statements and assignment statements, as defined in 7.1 and 7.2, is that the suite is executed immediately after evaluating the expression list (or yield expression), before assignment of the value (of the expression list or yield expression, not some other value) to the target lists or writing of the repr to the interactive output. dot_attribute_ref ::= ".." identifier A dot attribute reference in the suite of a compound expression or assignment statement is evaluated exactly like a simple attribute reference, as defined in 6.3.1 and in 7.2, except that the value of the expression list or yield expression is used as the value of the primary. I believe a dot attribute reference outside of could raise a SyntaxError at parse time. If not, it raises a ValueError at runtime, because the dot-value of the current suite is None. I believe this handles all of the examples given so far. It can be nested in the obvious way, without needing any new semantics. Assignment statements with expression lists of more than one expression are useless (you can't mutate a tuple), but not illegal. It could be extended to allow dot-subscripting and dot-slicing trivially, if those are desirable. And it's a very simple change to the syntax and semantics of the language, that doesn't have any radical undesirable consequences. I still don't really like the idea, but it's nice to know that it is actually doable. I'm maybe -.5 instead of -1 if it doesn't require statements having values and magic guessing of which statements in a suite replace a value and which don't and so on.

Perešíni Peter writes:
This particular application just screams for positional parameters, though: tree = Tree( Node( Node(value=3), Node(), value=5), Node(color="green")) (YMMV about positioning of the parens, to me this is the one that says "TREE!! and here are the Nodes" most clearly.)

On 2013-11-23, at 12:17 , Stephen J. Turnbull <stephen@xemacs.org> wrote:
Doesn’t work on third-party libraries on which you don’t have control[0] though, and when there are many customisation points having subsequent methods can make for a nicer API than 50 constructor parameters[1]. [0] such as the standard library’s, unless I am mistaken neither minidom nor elementtree allow complete element configuration — including subtree — in a single expression. The third-party lxml does to an extent, if one uses the builder APIs rather than the ET ones. [1] or the existing API may make it difficult to impossible to add such an extension without breaking
participants (13)
-
Andrew Barnert
-
Bruce Leban
-
Cameron Simpson
-
Chris Angelico
-
Haoyi Li
-
Henshaw, Andy
-
Markus Unterwaditzer
-
Masklinn
-
Nick Coghlan
-
Perešíni Peter
-
Stephen J. Turnbull
-
Steven D'Aprano
-
Terry Reedy