Python for Lisp Programmers (2000)
norvig.com
norvig.com
It has been a pretty frustrating experience, lots of the tools from functional languages are there, however Python as a language is extremely inconsistent. All these little quirks take up a significant chunk of cognitive overhead. Python also liberally add different syntax in places where it doesn’t seem to add much value (eg lambdas cannot destructure tuples, and don’t need to use return). Python has really showed me the value of keeping a simple syntax, and having value semantics everywhere). Mutation just isn’t a great way to write code outside of leetcode.
I find the mutability of values and object orientation of 3rd party libraries to be among the most alienating aspects of Python, but overall it’s too useful for me to ignore, so I live with it.
I just don’t find layering on functional capabilities to an OO language to be nearly as useful as a true FP solution. As you said, too much cognitive overhead.
In [1]: def foo(a, (b, c)):
...: pass
File "<ipython-input-1-8f113877c7ef>", line 1
def foo(a, (b, c)):
^
SyntaxError: invalid syntaxPEP 3113 -- Removal of Tuple Parameter Unpacking:
def add((a, (b, stack))):
return a + b, stack
def uncons(((head, tail), stack)):
return tail, (head, stack)
&c...Knowing these other languages and with others like them out there (F#, Racket, Clojure, OCaml, Elixir), I have no practical or intellectual need for Python.
And Clojure beautifully solves many pain points of Java and Javascript, and even C#. I haven't tried clj-python mentioned, but I hope someday soon, running Clojure code interoperable with Python in production becomes a reality.
The hosted nature of the language is a brilliant idea. Clojure eliminates many small annoyances I hate in other languages - syntactical, semantical, and operational.
I wish more programmers have given a heartfelt try to Clojure instead of whining that "Lisp is unreadable" and "the parentheses are awful".
You can use them from any language with native FFI bindings capability.
Python seems to be in this weird middle area where it's not performant, but it's still more low level[0] than languages like Haskell and OCaml. If you don't need performance, you should go as high level as practically possible!
[0] - By low level, I mean things like having to manually write loops, deal with state, etc. More succinctly: "A programming language is low level when its programs require attention to the irrelevant."
I learned it in the 00's in highschool and it was a very nice small scripting language that you could just write dumb code which worked and you wrote C for where you needed performance.
For some reason people have pushed it everywhere and lost the spark it used to have.
Using LISP to create DSP Languages is so powerful but under used.
Both have been improved on - in particular (1), with `typing` module, type annotations, mypy and so on. With (2), Python's speed has increased somewhat since, but then again, you won't be doing large-scale math without numpy, and for non-vectorizeable numeric computations you'd probably use something like jit-compiled numba or the like.
I believe that the overwhelming factor of why something does or does not become popular is simply chaotic luck.
In an alternate history where Python was designed exactly the same, but a butterfly at Guido's desk flapped his wings slightly differently, Python would have been obscure.
This seems to me a bit like saying, "That color isn't cerulean, it's blue." Describing the specific instances of serendipity that have led to Python's continuing success doesn't imply that it wasn't dumb luck. It's just a way of saying, "Here's an interesting bit of dumb luck."
Python's "easy to use" is just a lie, https://github.com/satwikkansal/wtfpython
The common lisp CFFI module is nothing short of Python's.
But, I know, people hate to put their `(` in front of their function names, and people hate to omit `,` between there function arguments.
There are many design errors in Python, of course. But most "wtfpython" things don't actually affect people using the language -- they're engineered to identify funny compiler optimizations and floating-point peculiarities. The real problems of a language aren't usually the kind of thing demonstrated in a couple-line snippet. Though there are exceptions.
Most languages are easier than C++, and many of them are faster than Python.
Plenty do though. I've passed that page onto co-workers a lot, back when the list was smaller, and almost every time they've found something on the page that explains a bit of weirdness with Python that they'd just dismissed as buggy behavior and avoided touching.
The ones I remember them mentioning:
https://github.com/satwikkansal/wtfpython#-mutating-the-immu...
https://github.com/satwikkansal/wtfpython#-beware-of-default...
And I multiple times I've had to explain the difference between references / shallow copy / deep copy to bootcampers, which falls under https://github.com/satwikkansal/wtfpython#-a-tic-tac-toe-whe...
Shallow/deep copies is an unavoidable part of having mutable data. The main alternative way to handle mutation is a fancy type system like Rust's, which is not an acceptable tradeoff for new programmers.
It was nothing of the sort; the claims would have landed on deaf ears if Perl didn't genuinely suffer maintainability problems due to that approach.
> dubious claim's of sigils (`@$`) hurt readability
AIUI the (admittedly limited) scientific data that exists supports that.
> But once Python had the ecosystem going, it brought back `@`, `{}`, numerous `_`, fanciful `:=`. And, of course, there always is more than one way to do it in Python.
You present this as some kind of bait-and-switch, but it's nothing of the sort; no-one's happy about the use of @ or := (I don't know what you're talking about regarding {} or _), but they were least-bad compromises for things that were felt to be needed. Multiple ways to do something is still seen as a bad thing - "There should be one-- and preferably only one --obvious way to do it" is the standard Python phrase, acknowledging that having only one way is an aspiration that can't always be fulfilled.
> Python's "easy to use" is just a lie
Nonsense. Like every language it's accumulated some warts, but it's still the language beginners find easiest to learn and teachers find easiest to teach.
> But, I know, people hate to put their `(` in front of their function names, and people hate to omit `,` between there function arguments.
Wow, way to conform to the Lisp stereotypes. If the problems of Lisp were actually that superficial, don't you think there'd be someone who'd produce a Lisp with a more familiar syntax and reap the popularity gains? Is every single Lisper really too haughty to make a trivial syntax change?
That's great if you're using Python for numerics, and color within the lines. But: a general purpose programming language is used for general purposes. Even for numerics, composability suffers if the language itself is slow, since crossing between FFI packages must be done very carefully to prevent paying the cost of the interpreter.
The fact is that Common Lisp, Julia, Swift, and LuaJIT (just to name a few), are substantially faster than Python, and it's not hard to come up with situations where this will matter. Worse, you might stumble into such a situation after committing to Python.
Python has much to recommend it. But its slowness has been mitigated, not addressed, in the years since Norvig wrote this article.
It disqualifies it for instance for window management scripts for which it's latency is too high.
If one attempt to send E.W.M.H. to the window manager using Python scripts, then one is faced with that.
This is even more pronounced when used with mutable lists. (setf (cadr lst) new-element) doing what you'd expect is quite useful. Obviously, this can be abused, but it's a really nice tool in the box.
Ooof, sounds like a lot of effort. Is anyone doing that in Python?
The most crazy thing I've seen along these lines (but it leads to really nice syntax) is PonyORM (https://ponyorm.org/), which allows you to write Python generator expressions like this:
select(c for c in Customer if sum(c.orders.price) > 1000)
And Pony will translate it to SQL like this: SELECT "c"."id"
FROM "customer" "c"
LEFT JOIN "order" "order-1"
ON "c"."id" = "order-1"."customer"
GROUP BY "c"."id"
HAVING coalesce(SUM("order-1"."total_price"), 0) > 1000
But how it does this is pretty funky. Python doesn't actually provide the AST for already-compiled code, just the bytecode. So Pony decompiles the bytecode back to an AST, and then converts the Python AST into SQL. More here (from the PonyORM author): https://stackoverflow.com/a/16118756/68707Thankfully we don't have to use query syntax with LINQ, we can just use the extension methods on IEnumerable, which is what I typically do.
They don't argue more or less than in, say, C, but they argue.