A short list of things I don’t like about Python
jessenoller.com
jessenoller.com
- No real metaprogramming is a big problem for me. I want decorators to be able to manipulate the AST (I'm playing around with a way to do this: http://pastie.org/490836 ) at the very least, and preferably real macros that can add new syntax.
- Anonymous functions. I want real anonymous functions in Python, not lambda or anything like it. After a long time of hacking Nemerle code, I go back to Python and cry when I write lambdas.
- Tail call recursion. Another functional programming thing that I'd absolutely love to see, just because it's so incredibly useful.
Sadly, I know none of these will likely be implemented, as they're just not the Python way, but it doesn't change the fact that I love Python and would love to see these changes.
I can see the argument for tail recursion - except that people who really love tail recursion seem to over use it (all good things in moderation). Still, I might like to see it in core; as for the other two, well - I don't even like lambdas, even as implemented in python (I think it might be a taste thing) and metaprogramming in the wrong hands is painful at best.
An example may be worthwhile. Consider SCons. Know anybody who uses it? There were a number of issues going on there (one of which was the general lameness of the Cons infrastructure--I'm sorry, I use the fact that make is a dependency computation engine, all the damned time, and ant and Cons and every other lame-ass "make replacement" that doesn't do this drives me up the wall) but IMHO the decision to make the syntax raw, executable Python code did not help.
Now, granted, I consider myself a Lisp hacker, so maybe you'll just write this off entirely. But the key thing I have with Python, the key complaint I have, is that there are fundamental domain assumptions baked into the language that are sometimes appropriate (numpy is an enormous success, for example) and sometimes aren't (SCons, HTML template system du jour, configuration files, etc.), and I can't change them.
Edit: an example came to me as I thought about this. So how's this as an explanation: in Java and Python, you can have as many nouns as you want. You can have as many verbs as you like. Adverbs are hard. Pronouns are almost impossible. And syntax? Forget it. It makes for an interesting contrast with mathematical notation, which takes time to learn and is inconsistent and only governed by social constraints but, at its best, is so good at communicating information that it fades into the background, just as = or 0 do.
In C, C++, Java, and almost every commonly used language, parsing is usually done by writing a parser in a different language (yacc, ANTLR, whatever) which is then compiled to source code which is then compiled again. In a language with good metaprogramming facilities, the parser is written in the language; macros in Lisp-descendants, parser combinators in Haskell (itself probably about as flexible syntactically as you can get without actually having macros), whatever the hell Forth people call it, etc. This has, at least in theory (a lot of parser combinators in Haskell are impossibly naive, but that's an implementation detail or so I claim), no demerits over the C approach, but it integrates better with the language.
This is not just a difference in taste, either. As a different example, Python had the problem wherein regexp strings were getting impossibly difficult to write. They added raw strings to the language to get around this. Schemers had the same problem, but one of them (Shivers) wrote a regexp language with escapes to the underlying Scheme implementation that compiled to regexps but made it possible to read them like they were any other kind of code, instead of weird terse utterances. You can still do the weird terse utterances if you like, but you can also write regexps like they were any other kind of Scheme code.
When I was doing web programming in 2000, I hacked an SQL generation library in Python. It felt awkward to use and was verbose but I deemed it better than being paranoid about SQL injection attacks. If I had good metaprogramming facilities, I might have been able to implement the database comprehensions they have in C# now (the LINQ facilities). I probably wouldn't have decided to do so, because even in 2000 I had some taste, but I could have and experienced all the benefits the C# people claim to have.
Another thing that came up during that same project was that I was trying to do what we now call continuation-based web frameworks in a language with no explicit continuation apparatus. This is similar to what PG described doing in Viaweb, but I didn't have a metaprogramming setup to help me out with it. I did the CPS conversion manually, and lemme tell you it was a royal pain in the arse and contributed significantly to the difficulty in implementing that setup. If I had good metaprogramming frameworks I could CPS convert the code automatically, as PG did.
Would you like more? :)
> In a language with good metaprogramming facilities, the parser is written in the language ... it integrates better with the language.
Your point was about how a good language makes it easy to parse another language into your desired langauge. I recently noticed the converse is true too. A good language makes it easy to output code in another language
In Clojure (and I suspect other lisps), it is quite common to see clojure literals used to generate code in other languages. i.e. in compojure, a webapp framework, the html generation code is entirely clojure literals:
(html [:body [:div [:a {:href "www.google.com"} "click me"]]])
generates:
<body><div><a href="www.google.com">click me</a></div></body>There are plenty of examples of this for html generation, SQL generation, and in one CL library, even C source code generation.
Perhaps I should be looking into ruby more? or a python parser?
I'm working on a similar technique for python (using lists and function lookups but it's still more verbose than the lisp style ones) ('p','this is a paragraph') vs (p, "this is a paragraph") as a small example
You mean like PyPy?
Parsing: a recursive-descent parser is straight-forward to implement and suffices for moderate parsing needs.
Regexps: there is re.VERBOSE -- you get multi-line regexps with comments.
Databases: SQLAlchemy got to where it is with only a lot of operator overloading. I can't say how painful it was to write because I've only used, not read SQLAlchemy.
Continuations: well... you can write the interaction as a generator if you can live without yielding in subroutines until PEP380 is implemented. Not ideal.
Parsing: hand writing lexers is an enormous PIA and the problem with a hand written recursive descent parser is if you want to change the grammar for whatever reason it is more involved than if you had a grammar generator.
Regexps: the problem with re.VERBOSE is that you're still writing essentially assembly language for a state machine, in the end, and it doesn't solve problems like "I want to search for this user input string, interpolate it into here and escape it for me please." Or even more interesting ones like "I want to compose these regexps, because I'm having to write a lexer by hand". You can use a separate escape function, and generally build up this layer of essentially scar tissue around the fact that this feature of your language is not really well integrated into the language, but that has its own issues. Better to solve it cleanly IMHO.
Databases: I have no experience with SQLAlchemy; it didn't exist when I did the DB project.
Continuations: I could probably have gotten away with using coroutines there. But Stackless was still an experiment, and not a particularly promising one, and generators didn't exist in the language at the time. I still think CPS-conversion would have been cleaner.
When your language is programmable, you have to accept that static analysis, even a simple grep for function calls, is essentially impossible if you don't absolutely trust your fellow programmers not to do something too clever. I think that's why Lisps are considered lone wolf languages.
What does that statement say more about, the Python community or the readability of functional programming?
Hard for whom? People unskilled in functional programming?
People skilled only in the English language find Greek "a bit hard", too. This truth is so well established that it forms the heart of a colloquial expression, one used to confess a speaker's inability to understand a subject in general: "It's all Greek to me!" And yet I trust that you haven't fallen into the trap of thinking that this expression, no matter how much it is repeated by English-language speakers, reflects poorly on the Greek language, itself.
The ancient Greeks, after all, didn't seem to have any difficulty using their language to great effect, and with remarkable clarity.
Also, LPEG (http://www.inf.puc-rio.br/~roberto/lpeg/lpeg.html) rules.
Edit: That's what I meant, that you can move code over to C. That Lua itself is faster than the other major scripting languages is just a nice perk. :) (Also, there's LuaJIT, but only on i386.)
I don't think the two are necessarily related: Tcl has a great C API, but is middle of the pack in terms of its own speed. Of course a good C API is an essential 'out' for scripting languages, so that you can write stuff in C where needed. Indeed, the idea behind Tcl was to not care much about Tcl's speed, because performance critical stuff would be done in C anyway.
(Speaking of which, there's also ConcurrentLua (http://concurrentlua.luaforge.net/), which adds Erlang-style message-passing concurrency.)
(Yes, I know that in principle, IEEE 754 Doubles can represent all 52-bit integers precisely (give or take a few bits), but still I don't feel comfortable).
* lbc: http://www.tecgraf.puc-rio.br/~lhf/ftp/lua/#lbc
* GMP wrapper: http://members.chello.nl/~w.couwenberg/
and there are more linked here, under "Math": http://lua-users.org/wiki/LibrariesAndBindingsLua uses doubles by default, but you can change the numeric type quite easily via luaconf.h (http://www.lua.org/manual/5.1/manual.html#lua_Number), if you need to work on a platform for which doubles are too large, if you'd prefer to use 64-bit longs, etc. Lua is designed to be small enough that you can just include the whole language in your source tree as a library, if you need a custom version.
Think of it as a 200k .dll/.so that gives your C program a scripting interface / config file parser, a generational garbage collector (for the Lua portion), and a great string and hash table library. (The standalone Lua interpreter is just a thin wrapper to add a REPL to the C API.) Lua code also works well as a data serialization format; it's very similar to JSON, and the Lua compiler has been tuned specifically for rapidly compiling data dumped as Lua literals.
I've thought of a way to do this in Smalltalk.
http://news.ycombinator.com/item?id=622773
Using #perform: style calls as decorators is ugly, though. Instead of that, one could utilize a MethodWrapper instead.
http://www.refactory.com/Software/MethodWrappers/
But instead of executing code before & after the method, you'd be executing an alternative version of it instead.
Various Lisps, for example, produce the well-known "when all you have is an AST, everything looks like a macro" syndrome; Scheme inculcates a desire to use tail recursion even when counting on one's fingers; etc.
This is, incidentally, precisely the same criticism proponents of functional programming typically level at proponents of, say, object-oriented programming. And I've no doubt that, if the popularities of those paradigms were reversed, comments like this one would be the usual snipe from OOP fans at their "unenlightened" functional brethren and sistren.
At any rate, I'm very far from being convinced that this is a healthy thing for either the programmers affected, or for the state of the art in general.
Heck, even the ability to root your imports, like import .email would bring in the root-level email library, would be pretty nice. I'm sure I've seen the idea shot down before, but I can't recall what the reasoning was.
"Flat is better than nested" is a rule of thumb, not a maxim. I agree that the python standard library could use a bit more nesting, but I think that going to the same lengths as java wouldn't fit with python too well.
I've never had to step carefully to avoid namespace clashes in python - I'm curious as to when and/or how you run into this problem. The only way I can think of that would make my code run into it a lot would be if I was doing a lot of "from foo import *", which generally isn't a good idea. "import foo", or "import foo as bar" serves the same purpose.
It seems to me that having a nested namespace for the standard library, but not to the point that it's a tree, is roughly analogous to a lot of other design decisions in Python - for example, there not being any concept of private or protected variables, at least on the high level.
Remembering that a)python has shell scripting (and even interactive shell) as a design influence, and b)is often developed from within vim/emacs where mouse aversion is strong enough to influenc the convention for the language.
I don't mean huge cumbersone Java or C++-esque typing, just the simple ability to declare to the compiler (or to humans trying to read my code) - 'object foo is a Foo, please give me a hand and tell me if you can deterministically figure out that it is not, or if it is doing something that Foos are never allowed to do'.
assert(type(x == "float"))
should be a very clear declaration to others reading the code, and assert is also not limited to type checking.I like static typing in some circumstances, but adding it to Python would be a major change to the language. I'd rather just use OCaml when static typing is really important, and save scripting languages for the niches in which they excel.
Would this necessarily be any less true of static type annotations, though? And as for them being stripped out, yes - it's for making debugging easier, not performance.
* loops leaking their control variables: I get why this is useful sometimes, but it particularly bites with list comprehensions, especially as generator expressions don't leak. Apparently the list-comprehension part of this goes away in Python 3 (http://mail.python.org/pipermail/python-3000/2008-April/0131...).
* How mutable default argument values behave. Now, this one's even mentioned in the tutorial - http://docs.python.org/tutorial/controlflow.html#default-arg... - so I've really got no excuse, but that hasn't stopped me getting it wrong before!
def f(a, d=None):
if d is None:
d = {}
which, okay, it's a minor deal, and I know why it happens, but it's the kind of thing I forget you need to do!Not that the chances are any good, and I can understand why - it would be decidedly un-Pythonic. But it's just mildly frustrating at times when I find myself writing the same coupe of lines of code twenty times within a file, because scoping rules insist they have to be there (reading from locals() is often where it hits me))
If you're getting enough boilerplate writing Python to want something horrendously evil like C++ macros, you're doing something wrong. Not that there isn't a valid argument for macros for Python in general.
To be clear though, Guido has said that he'd be open to the concept of Macros if someone were to come up with a good proposal for them. As far as I know, no one has stepped up the plate though.
There is an immensely rich universe of languages you can chose from today. If you want Lisp-ish lambdas and macros, you should, by all means, go with Lisp. If you want to build your own DSL, you may as well chose Ruby. If you want pythonic indentation-as-structure, you should go with Python and, if you love the "do this if that" thing, you should consider Perl.
Having said that, I would love if Python threads had better support for multi-processors. It's not like changing syntax and turning Python into something it isn't, it's just properly implementing something that should be there from day 1.
I think my rather inadequately thought out response was because most of the comments here in HN were about changing Python into something else. I am truly sorry I implied your article was about turning Python into something unnatural and I most certainly owe you an apology.
A more interesting question is, "In what circumstances is Python an excellent fit, and OCaml a poor one?", and vice versa.
print "hello"
x = 1 +
Note the syntax error. Running it, we see: $ python test.py
File "test.py", line 2
x = 1 +
^
SyntaxError: invalid syntax
If syntax errors were runtime errors, we would see the effect of the print statement. This is not a top-level effect: the same is true even if a syntax error is under a function.