8 ms·
While you can manipulate Python's AST, you can't do it in a way that looks like adding new syntax. In Clojure you can write a list comprehension as `(for [x (r
by duckerude 7y ago
While you can manipulate Python's AST, you can't do it in a way that looks like adding new syntax.
In Clojure you can write a list comprehension as `(for [x (range 10)] x)`. In Python you can write it as `[x for x in range(10)]`.
Imagine these didn't exist, and you wanted to implement them yourself.
In Clojure this is possible. Its list comprehensions are implemented as a Clojure macro: https://github.com/clojure/clojure/blob/b98ba84/src/clj/clojure/core.clj#L4590 https://github.com/clojure/clojure/blob/b98ba84/src/clj/cloj...
In Python there are several problems. The first is that you can't easily pull the decorator trick on arbitrary expressions. Not even with a lambda function, at least without doing a lot of work - inspect.getsource doesn't handle those properly.
But let's lower the bar, and replicate `l = [x for x in range(10)]` instead. You can do that with a decorator and an ordinary function.
Then the problem becomes that you can only use existing syntax in the function body. Syntax that already means something else. If you put an ordinary list comprehension in the function body then you get a syntax error when the file is parsed, long before the decorator has a chance to do anything. So you have to make something up yourself.
You might end up with something like this:
@list_comp
def l():
x: range(10)
x
Or, of course, this:
l = list(map(lambda x: x, range(10)))
Neither of these are as convenient or readable as real list comprehensions. But if you want those, you're at the whims of the language. You can't just add them yourself.
I don't know how big of a deal this actually is. I've never used Clojure, and I've barely used other Lisps. Expressing what I want in Python is easy enough in practice (but maybe I just don't know any better?). But there's certainly something significant there that Python can't do.
- zcid 7y agoI heard somebody make a similar argument one time in regards to Common Lisp. He said that with Java you would have to wait several years sometimes to get a feature (assuming the language developers actually cared about the feature), but with Lisp, you could just implement the feature yourself. I've always felt this is one of the strongest arguments for using a lisp-style language. Edit: this is possible too in languages like Elixer and Ruby using macros, but eventually the readability suffers and debugging complex macros in those languages can be a nightmare.
- deleted 7y ago[deleted]
- dmix 7y agoI haven't done much metaprogramming stuff, is this just the nature of using macros or can it be solved with careful coding?
- ddragon 7y agoMacros are really powerful constructs that allows you to even change the syntax of the language, which means when they are used you can't trust your previous knowledge of how the language should behave. If you do it correctly you can make a Domain Specific Language that makes your code as readable as the documentation in domains the language was not designed to directly solve (or just generate code for you to avoid boilerplate that would be more unreadable by itself). If you do it incorrectly, you can end up with code you cannot trust or even understand (and language design is a skill that not everyone has in the first place). About debugging, it is somewhat different from normal debugging since macros are a second layer of abstraction. You have a compile-time phase in which the macros are resolved (receiving code and returning code until there is no macro left) and a runtime in which the final generated code is run. If you only consider the runtime, then you're looking at code that is not the same as you wrote, which makes it indeed hard to see mistakes. But if you separate the concerns of compile and run time you can evaluate both separately most of the time. Also, Ruby does not have macros. It has a very simple syntax in which everything is an object and all function calls are actually messages that can be interpreted in runtime which allows you to extend the syntax very easily by controlling the dispatch rules. What causes trouble is that classes are open and can be extended freely during runtime, so you cannot know that an object you're calling is not modified by any part of the code (including any library), a practice called monkey patching and usually considered bad practice since it makes code hard to reason about.