RE: [Python-Dev] defmacro (was: Anonymous blocks)
Jim Jewett writes:
As best I can tell, the anonymous blocks are used to take care of boilerplate code without changing the scope -- exactly what macros are used for.
Folks, I think that Jim is onto something here. I've been following this conversation, and it sounds to me as if we are stumbling about in the dark, trying to feel our way toward something very useful and powerful. I think Jim is right, what we're feeling our way toward is macros. The problem, of course, is that Guido (and others!) are on record as being opposed to adding macros to Python. (Even "good" macros... think lisp, not cpp.) I am not quite sure that I am convinced by the argument, but let me see if I can present it: Allowing macros in Python would enable individual programmers or groups to easily invent their own "syntax". Eventually, there would develop a large number of different Python "dialects" (as some claim has happened in the Lisp community) each dependent on macros the others lack. The most important casualty would be Python's great *readability*. (If this is a strawman argument, i.e. if you know of a better reason for keeping macros OUT of Python please speak up. Like I said, I've never been completely convinced of it myself.) I think it would be useful if we approached it like this: either what we want is the full power of macros (in which case the syntax we choose should be guided by that choice), or we want LESS than the full power of macros. If we want less, then HOW less? In other words, rather than hearing what we'd like to be able to DO with blocks, I'd like to hear what we want to PROHIBIT DOING with blocks. I think this might be a fruitful way of thinking about the problem which might make it easier to evaluate syntax suggestions. And if the answer is that we want to prohibit nothing, then the right solution is macros. -- Michael Chermside
I've been following this conversation, and it sounds to me as if we are stumbling about in the dark, trying to feel our way toward something very useful and powerful. I think Jim is right, what we're feeling our way toward is macros.
The problem, of course, is that Guido (and others!) are on record as being opposed to adding macros to Python. (Even "good" macros... think lisp, not cpp.) I am not quite sure that I am convinced by the argument, but let me see if I can present it:
Allowing macros in Python would enable individual programmers or groups to easily invent their own "syntax". Eventually, there would develop a large number of different Python "dialects" (as some claim has happened in the Lisp community) each dependent on macros the others lack. The most important casualty would be Python's great *readability*.
(If this is a strawman argument, i.e. if you know of a better reason for keeping macros OUT of Python please speak up. Like I said, I've never been completely convinced of it myself.)
Nor am I; though I am also not completely unconvinced! The argument as presented here is probably to generic; taken literally, it would argue against having functions and classes as well... My problem with macros is actually more practical: Python's compiler is too dumb. I am assuming that we want to be able to import macros from other modules, and I am assuming that macros are expanded by the compiler, not at run time; but the compiler doesn't follow imports (that happens at run time) so there's no mechanism to tell the compiler about the new syntax. And macros that don't introduce new syntax don't seem very interesting (compared to what we can do already).
I think it would be useful if we approached it like this: either what we want is the full power of macros (in which case the syntax we choose should be guided by that choice), or we want LESS than the full power of macros. If we want less, then HOW less?
In other words, rather than hearing what we'd like to be able to DO with blocks, I'd like to hear what we want to PROHIBIT DOING with blocks. I think this might be a fruitful way of thinking about the problem which might make it easier to evaluate syntax suggestions. And if the answer is that we want to prohibit nothing, then the right solution is macros.
I'm personally at a loss understanding your question here. Perhaps you could try answering it for yourself? -- --Guido van Rossum (home page: http://www.python.org/~guido/)
Michael Chermside wrote:
Jim Jewett writes:
As best I can tell, the anonymous blocks are used to take care of boilerplate code without changing the scope -- exactly what macros are used for.
Folks, I think that Jim is onto something here.
I've been following this conversation, and it sounds to me as if we are stumbling about in the dark, trying to feel our way toward something very useful and powerful. I think Jim is right, what we're feeling our way toward is macros.
The problem, of course, is that Guido (and others!) are on record as being opposed to adding macros to Python. (Even "good" macros... think lisp, not cpp.) I am not quite sure that I am convinced by the argument, but let me see if I can present it:
Allowing macros in Python would enable individual programmers or groups to easily invent their own "syntax". Eventually, there would develop a large number of different Python "dialects" (as some claim has happened in the Lisp community) each dependent on macros the others lack. The most important casualty would be Python's great *readability*.
(If this is a strawman argument, i.e. if you know of a better reason for keeping macros OUT of Python please speak up. Like I said, I've never been completely convinced of it myself.)
The typical argument in defense of macros is that macros are just like functions, you go to the definition and see what they does. But depending on how much variation they offer over the normal grammar even eye parsing them may be difficult. They make it easy to mix to code that is evaluated immediately and code that will be evalutated, maybe even repeatedely, later, each macro having its own rules about this. In most cases the only way to discern this and know what is what is indeed looking at the macro definition. You can get flame wars about whether introducing slightly different variations of if is warranted. <.5 wink> My personal impression is that average macro definitions (I'm thinking about Common Lisp or Dylan and similar) are much less readable that average function definitions. Reading On Lisp may give an idea about this. That means that introducing macros in Python, because of the importance that readability has in Python, would need a serious design effort to make the macro definitions themself readable. I think that's a challenging design problem. Also agree about the technical issues that Guido cited about referencing and when macros definition enter in effect etc.
On 4/25/05, Michael Chermside
I've been following this conversation, and it sounds to me as if we are stumbling about in the dark, trying to feel our way toward something very useful and powerful. I think Jim is right, what we're feeling our way toward is macros.
I think the key difference with macros is that they act at compile time, not at run time. There is no intention here to provide any form of compile-time processing, and that makes all the difference. What I feel is the key concept here is that of "injecting" code into a template form (try...finally, or try..except..else, or whatever) [1]. This is "traditionally" handled by macros, and I see it as a *good* sign, that the discussion has centred around runtime mechanisms rather than compile-time ones. [1] Specifically, cases where functions aren't enough. If I try to characterise precisely what those cases are, all I can come up with is "when the code being injected needs to run in the current scope, not in the scope of a template function". Is that right? Paul.
Paul Moore wrote:
I think the key difference with macros is that they act at compile time, not at run time. There is no intention here to provide any form of compile-time processing, and that makes all the difference.
What I feel is the key concept here is that of "injecting" code into a template form (try...finally, or try..except..else, or whatever) [1]. This is "traditionally" handled by macros, and I see it as a *good* sign, that the discussion has centred around runtime mechanisms rather than compile-time ones.
[1] Specifically, cases where functions aren't enough. If I try to characterise precisely what those cases are, all I can come up with is "when the code being injected needs to run in the current scope, not in the scope of a template function". Is that right?
That doesn't hold if the code being injected is a single Python expression, since you can put an expression in a lambda and code the template as a function. I would say you need a block template when the code being injected consists of one or more statements that need to run in the current scope. Shane
Michael Chermside wrote:
Jim Jewett writes:
As best I can tell, the anonymous blocks are used to take care of boilerplate code without changing the scope -- exactly what macros are used for.
Folks, I think that Jim is onto something here.
I've been following this conversation, and it sounds to me as if we are stumbling about in the dark, trying to feel our way toward something very useful and powerful. I think Jim is right, what we're feeling our way toward is macros.
I am very excited about the discussion of blocks. I think they can potentially address two things that are sticky to express in python right now. The first is to compress the common try/finally use cases around resource usage as with files and database commits. The second is language extensibility, which makes us think of what macros did for Lisp. Language extensibility has two motivations. First and foremost is to allow the programmer to express his or her *intent*. The second motivation is to reuse code and thereby increase productivity. Since methods already allow us to reuse code, our motivation is to increase expressivity. What blocks offer is to make Python's suites something a programmer can work with. Much like using a metaclass putting control of class details into the programmer's hands. Or decorators allowing us to modify method semantics. If the uses of decorators tells us anything, I'm pretty sure there are more potential uses of blocks than we could shake many sticks at. ;) So, the question comes back to what are blocks in the language extensibility case? To me, they would be something very like a code object returned from the compile method. To this we would need to attach the globals and locals where the block was from. Then we could use the normal exec statement to invoke the block whenever needed. Perhaps we could add a new mode 'block' to allow the ControlFlow exceptions mentioned elsewhere in the thread. We still need to find a way to pass arguments to the block so we are not tempted to insert them in locals and have them magically appear in the namespace. ;) Personally, I'm rather attached to "as (x, y):" introducing the block. To conclude, I mocked up some potential examples for your entertainment. ;) Thanks for your time and consideration! -Shane Holloway Interfaces:: def interface(interfaceName, *bases, ***aBlockSuite): blockGlobals = aBlockSuite.globals().copy() blockGlobals.update(aBlockSuite.locals()) blockLocals = {} exec aBlock in blockGlobals, blockLocals return iterfaceType(interfaceName, bases, blockLocals) IFoo = interface('IFoo'): def isFoo(self): pass IBar = interface('IBar'): def isBar(self): pass IBaz = interface('IBaz', IFoo, IBar): def isBaz(self): pass Event Suites:: def eventSinksFor(events, ***aBlockSuite): blockGlobals = aBlockSuite.globals().copy() blockGlobals.update(aBlockSuite.locals()) blockLocals = {} exec aBlock in blockGlobals, blockLocals for name, value in blockLocals.iteritems(): if aBlockSuite.locals().get(name) is value: continue if callable(value): events.addEventFor(name, value) def debugScene(scene): eventSinksFor(scene.events): def onMove(pos): print "pos:", pos def onButton(which, state): print "button:", which, state def onKey(which, state): print "key:", which, state
On Mon, Apr 25, 2005, Shane Holloway (IEEE) wrote:
Interfaces::
def interface(interfaceName, *bases, ***aBlockSuite): blockGlobals = aBlockSuite.globals().copy() blockGlobals.update(aBlockSuite.locals()) blockLocals = {}
exec aBlock in blockGlobals, blockLocals
return iterfaceType(interfaceName, bases, blockLocals)
IFoo = interface('IFoo'): def isFoo(self): pass
Where does ``aBlock`` come from? -- Aahz (aahz@pythoncraft.com) <*> http://www.pythoncraft.com/ "It's 106 miles to Chicago. We have a full tank of gas, a half-pack of cigarettes, it's dark, and we're wearing sunglasses." "Hit it."
Aahz wrote:
On Mon, Apr 25, 2005, Shane Holloway (IEEE) wrote:
Interfaces::
def interface(interfaceName, *bases, ***aBlockSuite): blockGlobals = aBlockSuite.globals().copy() blockGlobals.update(aBlockSuite.locals()) blockLocals = {}
exec aBlock in blockGlobals, blockLocals
return iterfaceType(interfaceName, bases, blockLocals)
IFoo = interface('IFoo'): def isFoo(self): pass
Where does ``aBlock`` come from?
Sorry! I renamed ``aBlock`` to ``aBlockSuite``, but missed a few. ;)
Shane Holloway (IEEE) wrote:
So, the question comes back to what are blocks in the language extensibility case? To me, they would be something very like a code object returned from the compile method. To this we would need to attach the globals and locals where the block was from. Then we could use the normal exec statement to invoke the block whenever needed.
There's no need for all that. They're just callable objects. -- Greg Ewing, Computer Science Dept, +--------------------------------------+ University of Canterbury, | A citizen of NewZealandCorp, a | Christchurch, New Zealand | wholly-owned subsidiary of USA Inc. | greg.ewing@canterbury.ac.nz +--------------------------------------+
Michael Chermside wrote:
In other words, rather than hearing what we'd like to be able to DO with blocks, I'd like to hear what we want to PROHIBIT DOING with blocks. I think this might be a fruitful way of thinking about the problem which might make it easier to evaluate syntax suggestions. And if the answer is that we want to prohibit nothing, then the right solution is macros.
One thing we don't need, I believe, is arbitrary transformation of code objects. That's actually already possible, thanks to Python's compiler module, although the method isn't clean yet. Zope uses the compiler module to sandbox partially-trusted Python code. For example, it redirects all print statements and replaces operations that change an attribute with a call to a function that checks access before setting the attribute. Also, we don't need any of these macros, AFAICT: http://gauss.gwydiondylan.org/books/drm/drm_86.html Shane
Michael Chermside wrote:
I've been following this conversation, and it sounds to me as if we are stumbling about in the dark, trying to feel our way toward something very useful and powerful. I think Jim is right, what we're feeling our way toward is macros.
I considered saying something like that about 3 posts ago, but I was afraid of getting stoned for heresy...
... Eventually, there would develop a large number of different Python "dialects" (as some claim has happened in the Lisp community) each dependent on macros the others lack. The most important casualty would be Python's great *readability*.
In other words, rather than hearing what we'd like to be able to DO with blocks, I'd like to hear what we want to PROHIBIT DOING with blocks.
From that quote, it would seem what we want to do is prohibit anything that would make code less readable. Or prohibit anything that would permit creating a new dialect. Or something. -- Greg Ewing, Computer Science Dept, +--------------------------------------+ University of Canterbury, | A citizen of NewZealandCorp, a | Christchurch, New Zealand | wholly-owned subsidiary of USA Inc. | greg.ewing@canterbury.ac.nz +--------------------------------------+
Michael Chermside wrote:
if the answer is that we want to prohibit nothing, then the right solution is macros.
I'm not sure about that. Smalltalk manages to provide very reasonable-looking user-defined control structures without using compile-time macros, just normal runtime evaluation together with block arguments. It does this by starting out with a fairly minimal and very flexible syntax. This raises the question of why people feel the need for macros in Lisp or Scheme, which have an even more minimal and flexible syntax. I think part of the reason is that the syntax for passing an unevaluated block is too obtrusive. In Scheme you can define a function (not macro) that is used like this: (with-file "foo/blarg" (lambda (f) (do-something-with f))) But there is a natural tendency to want to be able to cut out the lambda cruft and just write something like: (with-file "foo/blarg" (f) (do-something-with f)) and for that you need a macro. The equivalent in Smalltalk would be something like File open: "foo/blarg" do: [:f f something] which doesn't look too bad (compared to the rest of the language!) because the block-passing syntax is fairly unobtrusive. So in summary, I don't think you necessarily *need* macros to get nice-looking user-defined control structures. It depends on other features of the language. -- Greg Ewing, Computer Science Dept, +--------------------------------------+ University of Canterbury, | A citizen of NewZealandCorp, a | Christchurch, New Zealand | wholly-owned subsidiary of USA Inc. | greg.ewing@canterbury.ac.nz +--------------------------------------+
"Greg" == Greg Ewing
writes:
Greg> This raises the question of why people feel the need for Greg> macros in Lisp or Scheme, which have an even more minimal Greg> and flexible syntax. I think part of the reason is that the Greg> syntax for passing an unevaluated block is too obtrusive. Greg> [... T]here is a natural tendency to want to be able to cut Greg> out the lambda cruft.... This doesn't feel right to me. By that argument, people would want to "improve" (mapcar (lambda (x) (car x)) list-of-lists) to (mapcar list-of-lists (x) (car x)) Have you ever heard someone complain about that lambda, though? My feeling is that the reason for macros in Lisps is that people want control structures to look like control structures, not like function calls whose actual arguments "just happen" to be anonymous function objects. In this context, the lambda does not merely bind f, it also excludes a lot of other possibilities. I mean when I see (with-locked-file "foo/blarg" (lambda (f) (do-something-with f))) I go "What's this? Oh, here the file is obviously important, and there we have a function of one formal argument with no actual arguments, so it must be that we're processing the file with the function." This emphasizes the application of this function to that file too much for my taste, and I will assume that the behavior of the block is self-contained---it had better not depend on free variables. But with (with-locked-file (f "foo/blarg") (do-something-with-as-modified-by f x)) there's no particular need for the block to exclusively concentrate on handling f, and there's nothing disconcerting about the presence of x. N.B. for non-Lispers: in Common Lisp idiom the list (f "foo/blarg") may be treated as two arguments, but associating f with "foo/blarg" in some way. I think in this context it is much more readable. -- School of Systems and Information Engineering http://turnbull.sk.tsukuba.ac.jp University of Tsukuba Tennodai 1-1-1 Tsukuba 305-8573 JAPAN Ask not how you can "do" free software business; ask what your business can "do for" free software.
This doesn't feel right to me. By that argument, people would want to "improve"
(mapcar (lambda (x) (car x)) list-of-lists)
to
(mapcar list-of-lists (x) (car x))
Have you ever heard someone complain about that lambda, though?
Welllll.... Shouldn't you have written (mapcar car list-of-lists) or am I missing something painfully obvious?
"Andrew" == Andrew Koenig
writes:
Andrew> Welllll.... Shouldn't you have written Andrew> (mapcar car list-of-lists) Andrew> or am I missing something painfully obvious? Greg should have written (with-file "foo/blarg" 'do-something-with) too. I guess I should have used do-something-with, too. -- School of Systems and Information Engineering http://turnbull.sk.tsukuba.ac.jp University of Tsukuba Tennodai 1-1-1 Tsukuba 305-8573 JAPAN Ask not how you can "do" free software business; ask what your business can "do for" free software.
Stephen J. Turnbull wrote:
This doesn't feel right to me. By that argument, people would want to "improve"
(mapcar (lambda (x) (car x)) list-of-lists)
to
(mapcar list-of-lists (x) (car x))
I didn't claim that people would feel compelled to eliminate all uses of lambda; only that, in those cases where they *do* feel so compelled, they might not if lambda weren't such a long word. I was just trying to understand why Smalltalkers seem to get on fine without macros, whereas Lispers feel they are needed. I think Smalltalk's lightweight block-passing syntax has a lot to do with it. -- Greg Ewing, Computer Science Dept, +--------------------------------------+ University of Canterbury, | A citizen of NewZealandCorp, a | Christchurch, New Zealand | wholly-owned subsidiary of USA Inc. | greg.ewing@canterbury.ac.nz +--------------------------------------+
"Greg" == Greg Ewing
writes:
Greg> I didn't claim that people would feel compelled to eliminate Greg> all uses of lambda; only that, in those cases where they Greg> *do* feel so compelled, they might not if lambda weren't Greg> such a long word. Sure, I understood that. It's just that my feeling is that lambda can't "just quote a suite", it brings lots of other semantic baggage with it. Anyway, with dynamic scope, we can eliminate lambda, can't we? Just pass the suites as quoted lists of forms, compute the macro expansion, and eval it. So it seems to me that the central issue us scoping, not preventing evaluation of the suites. In Lisp, macros are a way of temporarily enabling certain amounts of dynamic scoping for all variables, without declaring them "special". It is very convenient that they don't evaluate their arguments, but that is syntactic sugar, AFAICT. In other words, it's the same idea as the "collapse" keyword that was proposed, but with different rules about what gets collapsed, when. -- School of Systems and Information Engineering http://turnbull.sk.tsukuba.ac.jp University of Tsukuba Tennodai 1-1-1 Tsukuba 305-8573 JAPAN Ask not how you can "do" free software business; ask what your business can "do for" free software.
participants (10)
-
Aahz
-
Andrew Koenig
-
Greg Ewing
-
Guido van Rossum
-
Michael Chermside
-
Paul Moore
-
Samuele Pedroni
-
Shane Hathaway
-
Shane Holloway (IEEE)
-
Stephen J. Turnbull