Any/all return a Boolean, so the chain would stop there.
I also personally think
any(x % 5 in range(y))
Is more clear than range(y).any(lambda x: x % 5)Even if you go far out of your way to format it similarly, it still forces you to do a lot of mental work to see the inner most starting point and then deduce what the sequence of operations that happens is backwards, eg:
foo(
min(
map(lambda x: ...,
filter(lambda: y: ....,
baz(bar)
)
)
)
(and of course, the python linters are typically configured to hate this so you can't realistically write it this way even if you want to)But that's more of a math thing than an everyday coding thing, where dot chaining usually reads nicer.
Mixed is definitely the worst, like you said.
incidentally in your example, though data does flow from top to bottom, control does not, assuming the filter and map methods are lazy as they are in python; it ping-pongs back and forth up and down the sequence in a somewhat irregular manner, sometimes reaching as far as .min() before going back up, and other times turning around at .filter(...)
i wonder if you could implement the ide functionality you want with a 'wrap' menu of popular functions that are applicable to the thing to the left of your cursor, so when you had
filter(some_filter, bar.baz())|
(with | representing your cursor) you could select `map` or `min` or whatever from the wrap dropdown and get min(filter(some_filter, bar.baz()))|
for any given cursor position in python there are potentially multiple expressions ending there, in cases like "y: %s" % y|
but maybe that's not such a hard problem to solveYou mean Ruby? :P
(All Ruby iteratables mixin Enumerable, which is baaaaaaaasically inheritance.)
Lifetimes and async are a massive pain in rust. But the trait system is a work of art.
trait MyIterHelpers: Iterator {
fn dance(&self) {
println!("wheee");
}
}
// And tell rust that all Iterators are also MyIterHelpers.
impl<I: Iterator> MyIterHelpers for I {}
The one caveat is that using it in a different context will need a use crate::MyIterHelpers; line, so the namespace isn't polluted.Not automatic, but you could use a decorator + the protocol as type annotation, I think
This is already implemented in IntelliJ for Java - they call it "Postfix Completion". For example you can type ".cast" after an expression to wrap what's before the cursor in a cast expression, so type "a + b.cast", then pick cast to "float", and pick how large a preceding expression you want to cast, and you can end up with "(float)(a + b)" and go from there. They have postfix completion that can extract expressions into variables, create if-statements and switch-statements from expressions, and so many more things that I wish I had when doing non-trivial Python coding in my IDE of choice (which is not by Jetbrains)...
Scala was really nice for this syntax when I used it for Spark.
min(map(some_op, filter(some_filter, bar.baz()))).foo()
An alternative is also to use a generator comprehension that's identical to the inner part (in effect): min(some_op(item) for item in bar.baz() if some_filter(item)).foo()
Which could still be pulled out to a pair of lines for clarity: items = (some_op(item) for item in bar.baz() if some_filter(item)) # or some better name given a context
min(items).foo()I’ll wrap the whole thing in a named function as a way of describing what I’m doing and make it a closure if it’s used only once:
def f(bar):
def smallest_baz():
bazs = (
some_op(b)
for b in bar.baz()
if some_filter(b)
)
return min(bazs)
return smallest_baz().foo() class WrappedList:
_fns = [map, filter, min, max, all, any, len, list]
def __init__(self, it):
self.it = it
def __getattr__(self, name):
for fn in self._fns:
if name == fn.__name__:
def m(*args, **kwargs):
result = fn(*args, self.it, **kwargs)
if hasattr(result, '__iter__'):
return self.__class__(result)
else:
return result
return m
def unwrap(self):
return self.it
This allows you to do stuff like WrappedList([1, 2, 3, 4]).filter(lambda x: x % 2 == 0).map(lambda x: x * 3).list().unwrap() # [6, 12]
WrappedList([1, 2, 3, 4]).map(lambda x: x >= 5).any() # False
Deciding whether or not this is something you should do, rather than just something you can do, is left as an exercise for the reader. self._fns = {fn.__name__: fn for fn in [...]}
This may not be faster since the list is so short, but worth checking into [x * 3 for x in [1, 2, 3, 4] if x % 2 == 0]
with that said, I still love the general concept of chaining, and I use that style a lot where it is already convenient and popular - in pandas code.I suppose we really ought to blame Euler for introducing the f(x) notation 300 years ago... Very practical when the function is the entity you want to focus on, often less useful in (procedural) programming, where we typically start with the data and think in terms of a series of steps.
Some languages like D and Nim have "UFCS", uniform function call syntax, where all functions can be called as methods on any variable. Basically, it decouples the implicit association between method dispatch and namespacing/scoping semantics. Rust also has something they call UFCS, but it only goes one way (you can desugar methods as normal functions, but you can't ... resugar? arbitrary functions as methods). Python couldn't implement this without breaking a lot of stuff due to its semantics, but it is definitely a feature I'd like to see more of.
Learn a better editor, and this will stop being a problem.
If you complain about doing this, this is because you don't know how to perform the basic functions necessary to write code. Heuristically, this is because you are either using a bad editor or didn't learn how to use a decent one.
I.e. your complaint is more comparable to Amazon reviews coming from people who don't know how to use the product and then write something asinine, like that one about a loo brush that feels too rough when used in the capacity of toilet paper (though I believe that one was actually a joke inspired by similarly stupid but less funny reviews).
To expand on this: examples of navigating by structure include moving by token / expression / definition. Examples of moving by syntax would be the search or "jedi" navigation (i.e. navigation where you enter a special mode requiring from you to type characters that iteratively refine your search results). Finally, simply moving up / down / left right by certain number of characters is the "screen geography" way.
There's no way to tell which method is better, because they apply better in different situations, however the "screen geography" method usually ends up being the worst, because it's the most labor-intensive and requires from the author to dedicate a lot of attention to achieve precision (i.e. move exactly N spaces to the left and then exactly M spaces down is very easy to get wrong, also, with larger N and M becomes really tedious).
Navigation by word is only slightly better than navigation by character, and often falls into the "screen geography" kind of navigation. It's easy to learn, it's quite universal and doesn't require understanding of the structure of the program or mastering better techniques (eg. "jedi jump"). That's not to say that it should be excluded from the arsenal -- quite the opposite, but a master programmer (in the sense of someone who writes programs masterfully) would be the one who's less reliant on this kind of navigation.
That never existed. Or if it did, it was long before any trace exists, and there's trace from quite a way back when e.g. the first commit in which I can find the len() builtin (https://github.com/python/cpython/commit/c636014c430620325f8...) also has calls to file.read and list.append, and the first python-level methods are created just a few commits later (https://github.com/python/cpython/commit/336f2816cd3599b0347...). Though there may be missing commits, this is 30 in, back when Python was an internal CWI thing (although nearly a year in, according to the official timelines of the early days).
This was years before magic methods were even added (https://github.com/python/cpython/commit/04691fc1c1bb737c0db...).
So no, I don't think it's a "holdover from" anything. Rather seems like it's GvR's sensibilities.
https://mail.python.org/pipermail/python-3000/2006-November/...
min(some_op(item) for item in bar.baz() if some_filter(item)).foo()
Or decomposed a little for clarity: processed_items = (some_op(item) for item in bar.baz() if some_filter(item))
min(processed_items).foo()
This is pretty readable – a natural language description of the first line is “do some_op for each item in bar.baz that matches some_filter”, which corresponds 1:1 with the code.The chaining example in OP’s first example is way better
I work in both python & js. The python reads like natural language:
Processed items is a set of some transformation of each item in bar.baz() where something is true for that item.
then Foo the smallest in that list.
It reads like english.JS-y stuff doesn't read like natural language, but I do think its more concise and fits the IDE function discovery workflow better.
Both models can be made into horrid messes or elegant solutions. Both are highly readable.
Now I like the python one because I find it natural to attach contextual "whys" or "because" comments to them.
Processed items is a set of some transformation of each item in bar.baz() where something is true for that item.
then Foo the smallest item.
# because foo is a slow function and we don't want to foo every bar and bazThe reason they are bad is that intermediate results are never named (and thus are never explained). In simple situations, it's possible to infer from the context what the author's intention was, but in more complicated cases, if you want to understand someone's code, especially if it's written in the way you did, you'd have to "disassemble" it into simpler operations, name the variables (after investigating or guessing the purpose of each operation) and then try to come up with the full picture of what's going on.
Also, as a style suggestion: avoid using backslashes. In your situation, you could just put dots at the end of the line and it will be enough to not need the backslashes. It adds noise to your code, i.e. characters that add no meaning, just sort of a "scaffolding" to hold your code together.
Naming intermediates is fine (and encouraged) if there are actually meaningful names to be given. But sometimes the expression itself is the shortest meaningful name for the expression.
Re backslashes, you can also just wrap the expression in parentheses.
same with give me `min(of_this)` instead of `of_this_want.min()`
The Rust x.min(y) to me is so asymmetric. min(x, y) conveys the symmetry of the operation much better, x and y are both just elements. (And the latter is how it can be used in Python. In Rust, you can call Ord::min(x, y) to get the symmetry back, but it is less favoured right now for some reason.)
from functools import partial
class Pipeable(object):
def __init__(self, fn):
self.fn = fn
def __ror__(self, lhs):
return self.fn(lhs)
def pipeable(fn):
return lambda *args: Pipeable(partial(fn, *args))
filter = pipeable(filter)
map = pipeable(map)
list = pipeable(list)
sum = pipeable(sum)
min = pipeable(min)
max = pipeable(max)
any = pipeable(any)
# Usage:
range(1, 100) | filter(lambda x: x < 50) | max()
# 49
[1, 2, 3, 4] | filter(lambda x: x % 2 == 0) | map(lambda x: x * 3) | list()
# [6, 12]
[1, 2, 3, 4] | map(lambda x: x >= 5) | any()
# False minimum = +Inf
for b in bar.naz():
if not some_filter(b):
continue
b = some_op(b)
minimum = min(minimum, b)
foo(minimum)
Yes, plain old procedural python. data flow from top to bottom.
it allows `print` debugging, very usefull to debug some_filter and some_op are broken.