Plus, its whitespace-based syntax was used as an argument to not evolve the language, being one reason for why they haven't added proper anonymous functions.
I can't comment on LISP's parens much, for now it doesn't bother me, but Python's whitespace did and I tried liking it for about 3 years.
Concerning whitespace, serious (large) projects have a very specific style guide, which includes prescriptions on whitespace. Python doesn't add any extra restrictions to that and in other languages a mismatch between whitespace and braces is a frequent source of bugs, so significant whitespace avoids that as well.
Python has at least 3 features that are not needed in languages that have proper support for anonymous functions and that are more expression oriented:
1. for comprehensions
2. the with statement
3. decorators
You cannot work efficiently with higher-order functions until you have anonymous multi-line functions, period - also, Python's single-line lambdas would be a lot more useful if Python wouldn't have been so statement oriented, unfortunately in practice they are useless.
In regards to your points:
a) no, I don't buy that
b) ordering matters, we read code as we are reading text, top-down, left to right
How do you define working "efficiently with higher-order functions"? Given that Python fully supports higher-order functions, I am really curious what you could mean. I didn't downvote you, but it may have to do with your pointed assertion here, without anything in the way of an argument.
As to "ordering matters"; sure, but as the functions a nontrivial program calls are inevitably described as a graph, they must necessarily be defined in some arbitrary linear order anyway.
some_collection.where(x -> x.name == 'foo').sort_by(x -> x.age)
These functions are tiny and trivial. They don't need names, and if you were to give them names, the extra weight becomes burdensome. Not just in syntax duplication, but the redundancy of the name as a comment on the trivial function body. nameIsFoo = x -> x.name == 'foo'
getAge = x -> x.age
some_collection.where(nameIsFoo).sort_by(getAge)
Note that giving the functions names has also changed the source order of the function bodies. Now you need to do a mental cross-reference to follow, instead of being able to read the definitions inline.I wrote an elegant parser definition library, used for parsing specialized output of ... never mind.
It had hierarchic specifications (regular expressions etc) of parsing states, along with anonymous functions which stored away parsed data and sent the parser among different states.
New juniors could after a few hours use this to quickly parse complex text documents.
I cursed a lot while trying to port this to Python. :-( A dozen named sub-ten lines functions specified by names and referenced inside a parser structure? Just didn't work.
Edit: The point is, there are use cases where real lambdas are useful (except from map etc). It is just weird to argue otherwise.
I can live without multiline anonymous functions - but I'd make use of them if they did exist.
Scala sample:
cache.get[String]("name").flatMap {
case Some(value) => value
case None =>
database.query("names").head.flatMap {
case Some(id, value) =>
cache.set("name", value, 10.minutes)
.map(_ => value)
case None =>
Future.successful("Anonymous")
}
}
BTW, in this sample, for comprehensions are not that useful. But if you're using Scala-Async, you can write that in a style resembling blocking I/O: async {
val cached = await(cache.get[String]("name"))
cached match {
case Some(value) => value
case None =>
val fromDB = await(database.query("names").head)
fromDB match {
case Some(id, value) =>
await(cache.set("name", value, 10.minutes))
value
case None =>
"Anonymous"
}
}
}
In both cases multi-line anonymous functions are leveraged.> 1. for comprehensions
Off the top of my head Scala, Erlang, and Haskell -- all of which are "more expression oriented" than Python (and all of which have robust support for anonymous functions including multiline anonymous functions -- the latter despite, like Python, having indentation-sensitive syntax), all have comprehension syntaxes like Python's for comprehensions.
So, I'm not entirely buying the idea either that "being more expression oriented" or "having proper support for anonymous functions" eliminates the utility of comprehension syntax.
And yes, if Python makes it easier to work with higher-order functions and such combinators / operators become the norm, then we'll talk about 2 non-orthogonal and conflicting features.
Python is the only language I know that added for comprehensions before proper support for anonymous functions. All the other languages I worked with (including Clojure, to be on topic) had anonymous functions before the syntactic sugar built on top. Clearly Python has a problem here.
Wait, first you claimed that Python wouldn't need comprehensions if it had better anon function support, and now you've claimed that Python has a problem because it added for comprehensions before anon function support, even though languages with anon function support still find the need for for comprehensions.
You seem to be convinced that Python is wrong, but not really committed to any consistency in the reason that Python is wrong.
You can live without for comprehensions if you have good anonymous functions support. If Python adds better anonymous functions support, its for comprehensions will be conflicting and much less useful than in languages that had good anonymous functions support from the beginning.
Either way, Python will never get multi-line anonymous functions support, since it's first of all considered to be un-pythonic. So we're having this discussion for nothing - I fell out of love with Python some time ago, if you still like it than good for you.
You can live without either, as decades of C programmers have demonstrated. Both are beneficial, as theeir widespread popularity in newer language attests. Neither is a perfect substitute for the other, as the fact that they tend to be both present in many newer languages, rather than being exclusive.
> its for comprehensions will be conflicting and much less useful than in languages that had good anonymous functions support from the beginning.
I don't see the "conflict" asserted here. I've used Ruby fairly heavily -- which has, in the relevant sense, far better anonymous function support than Python (even though it has different quirks) -- and certainly as nice as Ruby blocks are, a clean comprehension syntax is pretty much the main thing I find myself wishing I had sometimes in Ruby that Python has.
And, sure, Python's comprehensions may be less general than, e.g., Scala's, but switching them to be monadic rather than iterator based doesn't require better anonymous function support, it just requires changing which protocol they depend on.
Reduced visual clutter and better flow in reading code.
> For documentation purposes it's a) better to give something a name, b) have a multi-line function in a separate place instead of inline in the form of a lambda.
I disagree: if the only place its ever used is in a particular call to a higher order function, it reduces the difficulty of reading the code -- if its not a large multiline function -- for it to be directly in the call as a lambda. Having it named is useful (1) if it needs to be referenced more than once (DRY), or (2) if it is large enough that it breaks up the flow too much for it to be included in one place (which is a somewhat subjective cut-off, but for me 1 line is far below it.)
EDIT: That being said, I'm mostly fine with Python the way it is -- while I sometimes wish a way to fit multi-line lambdas without disrupting the rest of the language could be found, I'm not sure I can see a good way for it to work, and its not really essential.
def foo():
do_this()
and_this()
later I change it.. def foo():
if something():
do_this()
and_this()
oops... and_this() should have been in the if block and I won't find out till I run it.(imagine a much bigger more complex example of the above function)
In large pieces of code this can be easy do. If you are forced to use parenthesis it's much more difficult to make this error. One could argue that experience prevents you from doing this but I have sadly found this not to be the case in practice.
By "parentheses" you mean delimiters, which create visible boundaries to defined areas in code. Parentheses are an example of delimiters, but not all delimiters are parentheses.
Bash has if ... then ... else ... fi
Ruby has if ... else ... end
C/C++/Java have (logical test) { controlled area }, nested to any practical depth.
And so forth. Python doesn't.
> One could argue that experience prevents you from doing this but I have sadly found this not to be the case in practice.
This is an argument against complex functions that do a lot, as opposed to breaking program logic up into smaller blocks that are easier to understand and control. The old argument against this practice was that a large function that did everything was faster than the same logic broken into smaller blocks. A modern compiler will generally prevent this from happening.
> This is an argument against complex functions that do a lot
Functions large and complex enough to make this problem significant seem to be the reality I have to deal with when programming in the large. It's only my opinion but a language feature that improves my real world experience at no cost is a bonus.
I wouldn't say "no one." I read comments regularly from people, usually students, who get into trouble with whitespace in Python, especially if they mix tabs and spaces in the same source file.
Though to be fair, there would be some people that use python day-to-day that don't like significant whitespace. I'd bet there'd be at least a few lispers that don't enjoy wrangling parens.
http://legacy.python.org/dev/peps/pep-0008/#tabs-or-spaces
Spaces are the preferred indentation method.
Tabs should be used solely to remain consistent with code that is already indented with tabs.
Python 3 disallows mixing the use of tabs and spaces for indentation.
Python 2 code indented with a mixture of tabs and spaces should be converted to using spaces exclusively.
When invoking the Python 2 command line interpreter with the -t option, it issues warnings about code that illegally mixes tabs and spaces. When using -tt these warnings become errors. These options are highly recommended!