Elisp 10: macro

Time:2021-5-3

Last chapter:library

In the previous chapter, we implemented the library newbie.el, which only defines one function. In fact, this function can be defined as a macro instead of a function, and it can make the calling code more efficient. Because calling a function is like taking a bus to a station, while calling a macro is like taking your own private car. It’s not a very accurate metaphor, so it’s just a metaphor.

Ding Yihong

First, define a macro that can do nothing,

(defmacro foo ())

Formally, defining a macro seems to be similar to defining a functiondefunIt was replaced bydefmacro

Calling a macro is similar to calling a function, for example, calling the macro defined above that can do nothingfoo;

(foo)

For this macro call, elisp evaluates tonil. WhynilWhat about it? When the elisp interpreter encounters a macro call statement, it will replace it with the definition of the macro, which is the expansion of the macro. above(foo)Statement is replaced with

It’s nothing. Nothing. It’s justnil

If it is to letfooWhat’s the definition of, for example

(defmacro foo ()
  t)

Then the expansion result of the macro call statement ist

Macros can also have parameters like functions, such as

(defmacro foo (x)
  x)

Macro call(foo "Hello world!")As a result, it is"Hello world!"

Construct programs like data

The definition of macro shows a very important feature of LISP language. In a program, you can construct a program like data. for example

(defmacro foo ()
  (list '+ 1 2 3))

The elisp interpreter evaluates the expression in the macro definition. In the above macro definition(list '+ 1 2 3)The result is(+ 1 2 3). Therefore, the macro calls the statement(foo)Will be expanded by the elisp interpreter as(+ 1 2 3)Then the elisp interpreter continues to evaluate the expansion result of the macro, so(foo)The result is 6. By using the macro definition and call processing mechanism of elisp interpreter, you can construct a program like data in a program.

because(list '+ 1 2 3)And'(+ 1 2 3)Therefore, the above macro definition can be simplified as

(defmacro foo ()
  '(+ 1 2 3))

Use quotation marks in macro definition to construct program. It should be noted that quotation marks will shield the processing of parameters by elisp interpreter. for example

(defmacro foo (x y z)
  '(+ x y z))

The definition of this macro is legal, but if you call it like this

(foo 1 2 3)

It doesn’t expand into(+ 1 2 3)Instead, it will be expanded as(+ x y z). When evaluating a macro definition, elisp considers that the'(+ x y z)It’s just a list in a literal sensexyzIs not a parameter value for a macro. Therefore, in the definition of macro, we need to be clear about which are literal data and which are variables or function calls. For the above example, you need to use thelist, i.e

(defmacro foo (x y z)
  (list '+ x y z))

So,(foo 1 2 3)Will be expanded into

(+ 1 2 3)

backquote

Macro definition

(defmacro foo (x y z)
  (list '+ x y z))

And

(defmacro foo (x y z)
  `(+ ,x ,y ,z))

synonymous.

Quotation marks'You can turn a list as a whole into a literal list, and the back quotation marks (usually on the keyboard and~In the same key position) can also make a list become a literal list, but if the previous,Modified symbols, such as macro parameters, are no longer considered literal by the elisp interpreter.

In the list of counter quotes,,@Elements in a list can be promoted to an outer list, such as

`(1 ,@(list 2 3) 4)

and

`(1 ,@'(2 3) 4)

as well as

`(1 ,@`(2 3) 4)

All the results are(1 2 3 4)

With these strange symbols, it is more convenient to construct programs in macro definitions.

print! macro

The following code defines the macro

(defmacro print! (x)
  `(progn
     (princ ,x)
     (princ "\n")))

It can be used instead of in newbie.elprinc\', for example

(print! "Hello world!")

Variable capture

Sometimes, you need to use local variables in the macro definition. for example

(defmacro bar (x y a)
  `(let (z)
     (if (< ,x ,y)
         (setq z ,x)
       (setq z ,y))
     (+ ,a z)))

This macro takes its argumentsxandyThe smaller and the middleaAdd. for example

(bar 2 3 1)

The evaluation result is 3.

barIf the call occurs in some coincidental environment, such as

(let ((z 1))
  (bar 2 3 z))

It evaluates to 4 instead of 3. The reason for this unexpected result is that the macro call statement is expanded as

(let ((z 1))
  (let (z)
    (if (< 2 3)
        (setq z 2)
      (setq z 3))
    (+ z z)))

The reason for this result is that instead of evaluating macro parameters, the elisp interpreter passes them into the definition of the macro and replaces the macro parameters with them.(bar 2 3 z)The third parameter of iszThe elisp interpreter passes this parameter in as isbarAfter the definition of the latter parameteraIt was replaced byz, butbarThere is a local variable in the definition ofzIn the last(+ z z)In the expression, the firstzIt should have been mebarBut the elisp interpreter, in this case, will think it isbarAs a result, the calculation results do not meet my expectations.

Health macro

A macro that can ensure that the local variables in the macro definition are not confused with the external variables in the macro expansion environment is called a health macro. Elisp’s macro is not hygienic. It also provides health macro for scheme language of LISP dialect. In recent years, the rising rust language also supports health macro. However, elisp can use uninternated symbols to simulate health macros.

In the process of program interpretation and execution, elisp interpreter will maintain some tables that store symbols. These symbols are bound to data, function or macro. The symbols that appear in the table are those within the system, while those that do not appear in the table are those outside the system. Using the elisp functionmake-symbolYou can create symbols outside the system. for example

(setq z 3)
(setq other-z (make-symbol "z"))

In the first expressionzIt’s a symbol bound to the number 3, it’s within the system, and it’s notmake-symbolThe symbols created are also calledzBut it’s outside the system. I use a symbol inside the systemother-zIt’s also called binding outside the systemzThe symbol of the. Use thisother-zBinding outside the systemzSymbol, you can make the macro defined in the previous sectionbarBecome hygienic, i.e

(defmacro bar (x y a)
  (let ((other-z (make-symbol "z")))
    `(progn
       (if (< ,x ,y)
           (setq ,other-z ,x)
         (setq ,other-z ,y))
       (+ ,a ,other-z))))

barThe new definition of is no longer afraid of variable capture. have a try,

(let ((other-z 1))
  (bar 2 3 other-z))

In the above callbarAlthough the third parameter andbarLocal variables in definitionsother-zThe same name, but no more variable capture, so the evaluation result of the above code is 3.

RedefinedbarHow to avoid variable capture? To understand all this, we need to have a deep understanding of how elisp evaluates the definition of macro. First, the elisp interpreter evaluates any expression in the macro definition. If you want to prevent it from evaluating an expression, you need to use quotation marks. Expressions decorated with quotation marks are treated as constants by the elisp interpreter. However, by using back quotation marks and commas, we can open up some variations in expressions that elisp regards as constants, which are redefinedbarThe key to avoiding variable capture is that elisp does not evaluate the constant part of the macro definition, but evaluates the variable part of the constant. This is equivalent to that in the macro definition, you can make a piece of code in a “static” state, and some areas of this code can be modified to the desired result by the elisp interpreter.

barIn the definition of, the statement in which variable capture would have occurred is

(+ ,a ,other-z)

becauseother-zIt’s already inletThe beginning of an expression binds it to a symbol outside the systemzSo when the elisp interpreter evaluates a macro definition, it takes all,other-zAs (or evaluated as) a symbol outside the systemz, i.e. etcbarWhen the calling statement is expanded by elisp, the symbolother-zNot anymoreother-zIt’s out of the systemz. staybarAs a local variableother-zIt is impossible to be confused with a variable with the same name. This is how elisp constructs health macros.

In fact, in the abovebarI don’t need to use it at allother-zIt can be defined as followsbar

(defmacro bar (x y a)
  (let ((z (make-symbol "z")))
    `(progn
       (if (< ,x ,y)
           (setq ,z ,x)
         (setq ,z ,y))
       (+ ,a ,z))))

In theletIn expressions, symbols within the systemzSymbols bound outside the systemzAnd then in the following code,,zWill be evaluated as a symbol outside the system by the elisp interpreterzIn this way, the following macro calls the statement

(let ((z 1))
  (bar 2 3 z))

The result is 3.

Outside the system, it is conducive to health construction.

epilogue

This chapter only introduces the most superficial knowledge of elisp macro. Its real use is to define a new syntax for elisp language (this way is often called metaprogramming), rather than definingprint!This kind of thing can be easily implemented with functions.

Next chapter:Dynamic module

Recommended Today

Large scale distributed storage system: Principle Analysis and architecture practice.pdf

Focus on “Java back end technology stack” Reply to “interview” for full interview information Distributed storage system, which stores data in multiple independent devices. Traditional network storage system uses centralized storage server to store all data. Storage server becomes the bottleneck of system performance and the focus of reliability and security, which can not meet […]