4 ms·
> Macros allow you to easily improve your syntax, but offer less guidance for improving your semantics. This IMO is the biggest problem with macro usage today
by sedachv 15y ago
> Macros allow you to easily improve your syntax, but offer less guidance for improving your semantics.
This IMO is the biggest problem with macro usage today and needs to be overcome. I've started a campaign against the "macros are syntactic sugar, and I have a sweet tooth!" camp (http://carcaddar.blogspot.com/2010/08/input-needed.html http://carcaddar.blogspot.com/2010/08/input-needed.html). I recommend reading this excellent parody blog post (written at the height of PG-inspired Lisp mania) for background: http://classic-web.archive.org/web/20070706135848/brucio.blogspot.com/2005/03/concision-is-equivalent-to.html http://classic-web.archive.org/web/20070706135848/brucio.blo...
> Macros almost never call macroexpand recursively, except inside of code bodies.
This is by design. The expansion of a macro needs to be opaque - otherwise where is the abstraction? When you macroexpand, you're opening the black box. You can only rely on macroexpanding macros that you have some control over.
> This means that macros like defclass usually cannot be extended in an incremental or modular fashion.
This is because it's the same problem as extending an arbitrary grammar. You can't compose two grammars and expect the result to be non-ambiguous.
> Instead, you need to declare a def-your-class macro that can't usefully compose with any other def-her-class macro that you might encounter.
This is completely, totally wrong. Things like def-your-class is the reason I started my education campaign. You should never write a def-your-class macro. Macros are the wrong way to solve this problem, which is why CLOS and the CLOS metaobject protocol include all the facilities you could want for extending defclass in a composable way (Art of the Metaobject Protocol is a comprehensive, but confusing, reference).
- ekidd 15y agoYou can only rely on macroexpanding macros that you have some control over. I think we're looking at this from exactly opposite directions. I'm not talking about code-walking pre-existng macros, but rather providing hooks for other people to extend yours: ;; Completely hypothetical package management-system. (define-module foo (require 'bar) (file "foo.ss") (file "foo-tests.ss" :in-mode 'test)) There's a lot of macros like this in the Lisp world, and they define non-extensible DSLs. For example, you can't define a macro tested-file to eliminate the duplication above: (define-module foo (require 'bar) ;; Doesn't work: (tested-file "foo")) This could be supported if define-module internally called macroexpand-1 on unrecognized child forms until it saw either require or file. Then you could define a tested-file macro to extend define-module. Note that the actual advisability of this approach varies greatly between Lisp and Scheme dialects. Contrast the following ActiveRecord example: class Person < ActiveRecord::Base belongs_to :organization state_machine :initial => :starting do state :starting state :online state :stopping state :offline end end Here, we have a third-party state machine library extending ActiveRecord. This is just one of several places where many Lisp macros tend to be broken or inflexible. Of course, macros are a very useful tool. But they have limitations, and other languages also provide interesting mechanisms for "writing programs that write programs", with different tradeoffs. You should never write a def-your-class macro. Well, even if the CLOS metaobject protocol does solve this problem, it doesn't fix defsystem, or any of the other thousand non-extensible defblah forms in the Lisp community.
- swannodette 15y agoThe amount of OO runtime metaprogramming crap out there far outweighs the amount of macro metaprogramming crap from simple fact that hardly anyone knows anything about macro metaprogramming. I've seen only a few non-extensible defblah and a metric ton of non-extensible OO bullshit in major projects. I agree that you have some valid points but your overall argument holds little water. In good Lisps you have access to both runtime and compile time abstractions. Used wisely they will always trump a system that can only lean on runtime abstractions. I'll also note that in my experience runtime metaprogramming is horribly painful. And many, many, many others have pointed out this fact. There are numerous cases where structural transformation is far simpler to reason about.
- ekidd 15y agoOh, I absolutely agree that OO languages do gross things, too. If somebody gets me started on AbstractRequestProcessorFactoryFactory, or inappropriate use of instance_eval, I can keep going for hours. :-) Overall, macros are a very useful tool. But there's a school of thought that sees them as the ultimate, universal abstraction. I'm no longer convinced by that argument. However, I do agree with your remark, elsewhere in this discussion, that The Reasoned Schemer is an extraordinary fine example of what macros can be used for. It's one of my all-time favorite programming books.
- sedachv 15y ago> This could be supported if define-module internally called macroexpand-1 on unrecognized child forms until it saw either require or file. Then you could define a tested-file macro to extend define-module. If tested-file expanded to just file, it means that the only thing it can do is have side-effects at compile-time. Those side-effects won't be preserved in compiled files. So tested-file has to expand to something that includes file and the extra code, and define-module has to specify what that expansion can look like and how it will be handled. The design of this protocol can't be automated, because code-walking the expansion of tested-file is potentially Turing-complete. So now you have two problems: 1. every define-foo macro needs a protocol to specify how it can be customized 2. this only works at compile-time The need for this kind of customization is where the meta-object protocol arose from. Read up on Gregor Kiczales' work on open implementations (http://www2.parc.com/csl/groups/sda/projects/oi/ieee-software/ http://www2.parc.com/csl/groups/sda/projects/oi/ieee-softwar...). Putting in the required hooks into the interpreter (object system) using a restricted protocol provides a common system that all customizations can share (no need to design your own define-foo protocol) and is available at all stages (compile and run-time).