Lecture Notes: Macros

Source: Internet
Author: User

Lecture Notes: Macros
 

Lisp functions take Lisp values as input and return Lisp values. they are executed at run-time. lisp macros take Lisp code as input, and return Lisp code. they are executed at compiler pre-processor time, just like in C. the resultant code gets executed at run-time. almost all the errors that result from using macros can be traced to a misunderstanding of this fact.

 

1.Basic Idea: Macros take unevaluated Lisp code and return a Lisp form. This form shoshould be code that calculates the proper value. Example:

 

 

(defmacro Square (X)   '(* ,X ,X))

This means that wherever the pre-processor sees (Square XXX) to replaces it with (* XXX). The resultant code is what the compiler sees.

 

2.Debugging technique:macroexpand-1

When designing a function, you can type a call to the function into the Lisp Listener (prompt), and see if it returns the correct value. however, when you type a macro call into the Lisp Listener, two things happen: first, the macro is expanded into its resultant code, and then that code is evaluated. it is more useful during debugging to be able to examine the results of these two steps individually. the functionmacroexpand-1Returns the result of stage one of this process:

 

 

(macroexpand-1 '(Square 9)) ==> (* 9 9)

If in doubt,macroexpand-1It out.

3.Purpose: To control evaluation of the arguments.

Since macros are so much harder to use than functions, a good rule of thumb is:Don't usedefmacro if defun will work fine. So, for example, there wocould be no reason to try to use a macroSquare: A function wocould be much easier to write and test. in Lisp, unlike in C, there is no need to use macros to avoid the very small runtime overhead of a function call: there is a separate method for that (the inline proclamation) that lets you do this without switching to a different syntax. what macros can do that functions cannot is to control when the arguments get evaluated. functions evaluate all of their arguments before entering the body of the function. macros don't evaluate any of their arguments at preprocessor time unless you tell it to, so it can expand into code that might not evaluate all of the arguments. for example, suppose thatcondWas in the language,ifWasn't, and you wanted to write a versionifUsingcond.

 

 

(defun Iff-Wrong (Test Then &optional Else)  (cond    (Test Then)    (t    Else)))

The problem with this is that it always evaluates all of its arguments, while the semanticsifDictate that exactly one ofThenAndElseArguments gets evaluated. For example:

 

 

(let ((Test 'A))  (Iff-Wrong (numberp Test)             (sqrt Test)             Sorry, SQRT only defined for numbers))

Will crash, since it tries to take(sqrt 'A).A correct version, with behavior identical to the built-inif(Could t that the realifOnly has one required arg, not two), wocould be:

 

 

(defmacro Iff (Test Then &optional Else)  A replacement for IF, takes 2 or 3 arguments. If the first evaluates to  non-NIL, evaluate and return the second. Otherwise evaluate and return  the third (which defaults to NIL)  '(cond     (,Test ,Then)     (t     ,Else)) )

A similar example wocould be NAND (Not AND), which returns true if at least one of the arguments is false, but, like the built-inand, Does short-circuit evaluation whereby once it has the answer it returns immediately without evaluating later arguments.

 

 

(defmacro Nand (&rest Args)  '(not (and ,@Args)))
4.Bugs:

 

(A) Trying to evaluate arguments at run-time

 

(B) Evaluating arguments too usually times

 

(C) Variable name clashes.

 

 

(A) Trying to evaluate arguments at run-time

 

Macros are expanded at compiler pre-processor time. thus, the values of the arguments are generally not available, and code that tries to make use of them will not work. i. e. consider the following definition Square, Which tries to replace (Square 4)With 16Instead of (* 4 4).

 

 

(defmacro Square (X)  (* X X))
This wocould indeed work (Square 4), But wocould crash (Square X), Since XIs probably a variable whose value is not known until run-time. Since macros do sometimes make use of variables and functions at expansion time, and to simplify debugging in general, It is stronugly recommended that all macro definitions and any variables and functions that they use at expansion time (as opposed to code they actually expand) be placed in a separate file that is loaded before any files containing code that makes use of the macros.

 

 

(B) Evaluating arguments too usually times

 

Let's take another look at our first definition of the Square macro.

 

 

(defmacro Square (X) '(* ,X ,X))

This looks OK on first blush. However, try macroexpand-1'Ing a form, and you notice that it evaluates its arguments twice:

 

 

(macroexpand-1 '(Square (Foo 2))) ==> (* (Foo 2) (Foo 2))
FooGets called twice, but it shoshould only be called once. Not only is this inefficient, but coshould return the wrong value if FooDoes not always return the same value. I. e. consider Next-Integer, Which returns 1 the first time called, then 2, then 3. (Square (Next-Integer))Wocould return N * (N + 1), not N 2, Plus wocould advance N by 2. Similarly, (Square (random 10))Wocould not necessarily generate a perfect square! With Lisp you have the full power of the language available at preprocessor time (unlike in C), so you can use ordinary Lisp constructs to solve this problem. In this case, letCan be used to store the result in a local variable to prevent multiple evaluation. There is no general solution to this type of problem in C.

 

 

(defmacro Square2 (X)  '(let ((Temp ,X))     (* Temp Temp)))(macroexpand-1 '(Square2 (Foo 2)))==> (let ((Temp (Foo 2)))         (* Temp Temp))
This is what we want.

 

(C) Variable name clashes. 

 

When using letTo suppress multiple evaluation, one needs to be sure that there is no conflict between the local variable chosen and any existing variable names. The above version Square2Is perfectly safe, but consider instead the following macro, which takes two numbers and squares the sum of them:

 

(defmacro Square-Sum (X Y)  '(let* ((First ,X)          (Second ,Y)          (Sum (+ First Second)))     (* Sum Sum)) )
This looks pretty good, even after macroexpansion:

 

(macroexpand-1 '(Square-Sum 3 4))==> (LET* ((FIRST 3)          (SECOND 4)          (SUM (+ FIRST SECOND)))     (* SUM SUM))
Which gives the proper result. However, this version has a subtle problem. The local variables we chose wocould conflict with existing local variable names if a variable named FirstAlready existed. E. g.

 

(macroexpand-1 '(Square-Sum 1 First)) ==> (LET* ((FIRST 1)          (SECOND FIRST)          (SUM (+ FIRST SECOND)))     (* SUM SUM))

The problem here is that(SECOND FIRST)Gets the value of the newLocalVariableFIRST, Not the one you passed in. Thus

 

(let ((First 9)) (Square-Sum 1 First)) 
Returns 4, not 100! Solutions to this type of problem are quite complicated, and involve using gensymTo generate a local variable name that is guaranteed to be unique.

Moral: even seemingly simple macros are hard to get right, so don't use macros unless they really add something. BothSquareAndSquare-SumAre inappropriate uses of macros.

 

(defmacro Square-Sum2 (X Y)  (let ((First (gensym FIRST-))        (Second (gensym SECOND-))        (Sum (gensym SUM-)))    '(let* ((,First ,X)            (,Second ,Y)            (,Sum (+ ,First ,Second)))       (* ,Sum ,Sum))))
Now

 

(macroexpand-1 '(Square-Sum2 1 First))==> (LET* ((#:FIRST-590 1)          (#:SECOND-591 FIRST)          (#:SUM-592 (+ #:FIRST-590 #:SECOND-591)))     (* #:SUM-592 #:SUM-592))
This expansion has no dependence on any local variable names in the macro definition itself, and since the generated ones are guaranteed to be unique, is safe from name collisions.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.