开发者

Examples of what Lisp's macros can be used for

开发者 https://www.devze.com 2022-12-25 15:02 出处:网络
I\'ve heard that Lisp\'s macro system is very powerful. However, I find it difficult to find some practical examples of what they can be used for; things that would be difficult to achieve without the

I've heard that Lisp's macro system is very powerful. However, I find it difficult to find some practical examples of what they can be used for; things that would be difficult to achieve without them.

Ca开发者_运维问答n anyone give some examples?


Source code transformations. All kinds. Examples:

  • New control flow statements: You need a WHILE statement? Your language doesn't have one? Why wait for the benevolent dictator to maybe add one next year. Write it yourself. In five minutes.

  • Shorter code: You need twenty class declarations that almost look identical - only a limited amount of places are different. Write a macro form that takes the differences as parameter and generates the source code for you. Want to change it later? Change the macro in one place.

  • Replacements in the source tree: You want to add code into the source tree? A variable really should be a function call? Wrap a macro around the code that 'walks' the source and changes the places where it finds the variable.

  • Postfix syntax: You want to write your code in postfix form? Use a macro that rewrites the code to the normal form (prefix in Lisp).

  • Compile-time effects: You need to run some code in the compiler environment to inform the development environment about definitions? Macros can generate code that runs at compile time.

  • Code simplifications/optimizations at compile-time: You want to simplify some code at compile time? Use a macro that does the simplification - that way you can shift work from runtime to compile time, based on the source forms.

  • Code generation from descriptions/configurations: You need to write a complex mix of classes. For example your window has a class, subpanes have classes, there are space constraints between panes, you have a command loop, a menu and a whole bunch of other things. Write a macro that captures the description of your window and its components and creates the classes and the commands that drive the application - from the description.

  • Syntax improvements: Some language syntax looks not very convenient? Write a macro that makes it more convenient for you, the application writer.

  • Domain specific languages: You need a language that is nearer to the domain of your application? Create the necessary language forms with a bunch of macros.

Meta-linguistic abstraction

The basic idea: everything that is on the linguistic level (new forms, new syntax, form transformations, simplification, IDE support, ...) can now be programmed by the developer piece by piece - no separate macro processing stage.


Pick any "code generation tool". Read their examples. That's what it can do.

Except you don't need to use a different programming language, put any macro-expansion code where the macro is used, run a separate command to build, or have extra text files sitting on your hard disk that are only of value to your compiler.

For example, I believe reading the Cog example should be enough to make any Lisp programmer cry.


Anything you'd normally want to have done in a pre-processor?

One macro I wrote, is for defining state-machines for driving game objects. It's easier to read the code (using the macro) than it is to read the generated code:

(def-ai ray-ai
  (ground
   (let* ((o (object))
          (r (range o)))
     (loop for p in *players*
           if (line-of-sight-p o p r)
           do (progn
                (setf (target o) p)
                (transit seek)))))
  (seek
   (let* ((o (object))
          (target (target o))
          (r (range o))
          (losp (line-of-sight-p o target r)))
     (when losp
       (let ((dir (find-direction o target)))
         (setf (movement o) (object-speed o dir))))
     (unless losp
       (transit ground)))))

Than it is to read:

(progn
 (defclass ray-ai (ai) nil (:default-initargs :current 'ground))
 (defmethod gen-act ((ai ray-ai) (state (eql 'ground)))
            (macrolet ((transit (state)
                         (list 'setf (list 'current 'ai) (list 'quote state))))
              (flet ((object ()
                       (object ai)))
                (let* ((o (object)) (r (range o)))
                  (loop for p in *players*
                        if (line-of-sight-p o p r)
                        do (progn (setf (target o) p) (transit seek)))))))
  (defmethod gen-act ((ai ray-ai) (state (eql 'seek)))
            (macrolet ((transit (state)
                         (list 'setf (list 'current 'ai) (list 'quote state))))
              (flet ((object ()
                       (object ai)))
                (let* ((o (object))
                       (target (target o))
                       (r (range o))
                       (losp (line-of-sight-p o target r)))
                  (when losp
                    (let ((dir (find-direction o target)))
                      (setf (movement o) (object-speed o dir))))
                  (unless losp (transit ground)))))))

By encapsulating the whole state-machine generation in a macro, I can also ensure that I only refer to defined states and warn if that is not the case.


With macros you can define your own syntax, thus you extend Lisp and make it
suited for the programs you write.

Check out the, very good, online book Practical Common Lisp, for practical examples.

7. Macros: Standard Control Constructs
8. Macros: Defining Your Own


Besides extending the language's syntax to allow you to express yourself more clearly, it also gives you control over evaluation. Try writing your own if in your language of choice so that you can actually write my_if something my_then print "success" my_else print "failure" and not have both print statements get evaluated. In any strict language without a sufficiently powerful macro system, this is impossible. No Common Lisp programmers would find the task too challenging, though. Ditto for for-loops, foreach loops, etc. You can't express these things in C because they require special evaluation semantics (people actually tried to introduce foreach into Objective-C, but it didn't work well), but they are almost trivial in Common Lisp because of its macros.


R, the standard statistics programming language, has macros (R manual, chapter 6). You can use this to implement the function lm(), which analyzes data based on a model that you specify as code.

Here's how it works: lm(Y ~ aX + b, data) will try to find a and b parameters that best fit your data. The cool part is, you can substitute any linear equation for aX + b and it will still work. It's a brilliant feature to make statistics computation easier, and it only works so elegantly because lm() can analyze the equation it's given, which is exactly what Lisp macros do.


Just a guess -- Domain Specific Languages.


Macros are essential in providing access to language features. For instance, in TXR Lisp, I have a single function called sys:capture-cont for capturing a delimited continuation. But this is awkward to use by itself. So there are macros wrapped around it, such as suspend, or obtain and yield which provide alternative models for resumable, suspended execution. They are implemented here.

Another example is the complex macro defstruct which provides syntax for defining a structure type. It compiles its arguments into lambda-s and other material which is passed to the function make-struct-type. If programs used make-struct-type directly for defining OOP structures, they would be ugly:

1> (macroexpand '(defstruct foo bar x y (z 9) (:init (self) (setf self.x 42))))
(sys:make-struct-type 'foo 'bar '()
                      '(x y z) ()
                      (lambda (#:g0101)
                        (let ((#:g0102 (struct-type #:g0101)))
                          (unless (static-slot-p #:g0102 'z)
                            (slotset #:g0101 'z
                                     9)))
                        (let ((self #:g0101))
                          (setf (qref self x)
                           42)))
                      ())

Yikes! There is a lot going on that has to be right. For instance, we don't just stick a 9 into slot z because (due to inheritance) we could actually be the base structure of a derived structure, and in the derived structure, z could be a static slot (shared by instances). We would be clobbering the value set up for z in the derived class.

In ANSI Common Lisp, a nice example of a macro is loop, which provides an entire sub-language for parallel iteration. A single loop invocation can express an entire complicated algorithm.

Macros let us think independently about the syntax we would like in a language feature, and the underlying functions or special operators required to implement it. Whatever choices we make in these two, macros will bridge them for us. I don't have to worry that make-struct is ugly to use, so I can focus on the technical aspects; I know that the macro can look the same regardless of how I make various trade-offs. I made the design decision that all struct initialization is going to be done by some functions registered to the type. Okay, that means that my macro has to take all the initializations in the slot-defining syntax, and compile the anonymous functions, where the slot initialization is done by code generated in the bodies.

Macros are compilers for bits of syntax, for which functions and special operators are the target language.

Sometimes people (non-Lisp people, usually) criticize macros in this way: macros don't add any capabilities, only syntactic sugar.

Firstly, syntactic sugar is a capability.

Secondly, you also have to consider macros from a "total hacker perspective": combining macros with implementation-level work. If I'm adding features to a Lisp dialect, such as structures or continuations, I am actually extending the power. The involvement of macros in that enterprise is essential. Even though macros aren't the source of the new power (it doesn't emanate from the macros themselves), they help tame and harness it, giving it expression.

If you don't have sys:capture-cont, you can't just hack up its behavior with a suspend macro. But if you don't have macros, then you have to do something awfully inconvenient to provide access to a new feature that isn't a library function, namely hard-coding some new phrase structure rules into a parser.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号