Practical clojure-macros and metaprogramming

Source: Internet
Author: User
Document directory
  • Code vs. Data
  • Homoiconicity, code = Data
  • Working with macros
  • Debugging macros
  • Code Templating
  • When to use macros
  • Using macros, for example
What is metaprogramming?

Metaprogramming is the use of code to modify or create other code.
It is primarily a developer tool and acts as a force multiplier, allowing large amounts of predictable code to be generated from just a few statements in the host language (or "metalanguage "). it is extremely useful for automating repetitive, boilerplate code.

What is metaprogramming first?
Normal programming uses code to modify data, while metaprogramming uses code to create and modify code)
Why do we need metaprogramming?
All languages design the most basic and abstract keywords, or schema, which can be directly used by these programmers.
However, after all, the schema of a language is the most basic, so it is impossible to cover all aspects.
Programmers often encounter many such requirements during development,
For example, if a piece of code appears repeatedly, it can be used as a model boilerplate, but you do not want to or cannot use functions (to avoid the consumption of function calls, to avoid parameters being eval in advance, or for other reasons)
For example, in a specific field, the demand for DSL
In this way, the programmer hopes to modify and add the schema of the language, that is, to create and modify the code (CODE) using code, which is called metaprogramming.

It can be seen that meta-programming is an advanced function, not all programmers need to use it, or they want to use it.
After all, it just makes the code more clean and easier to maintain... It is not useless.
However, for senior programmers, in addition to the implementation of functions and the pursuit of beauul ul, various languages also support metaprogramming more or less.

Most programming versions support some form of metaprogramming.

C has a Preprocessor pre-defined, macro
C ++ has templates
Java has annotations and Aspect-Oriented Programming extensions
Scripting versions ages have "eval" statements.

Most versions ages have some sort of API that can be used to introspect or modify the core language features (such as classes and methods ). as a last resort, any language can be used to build source code using string manipulation and then feed it to a compiler.

 

Code vs. Data

For most ages, treating code as data or data as code is a more or less a cumbersome process

One common strategy is to treat code as a textual string.

Another strategy is to provide a set of APIs that expose the concepts of a programming language as objects within the language, allowing the programmer to make CILS such as createclass () or addmethod (), to build code structures programmatically.

In most languages, code and data are completely different. Therefore, to implement metaprogramming, you need to process code as data, which is quite troublesome.
If you do not want to use the code as a string and hand it over to the complier after receiving the code, you can check whether it is troublesome or error-prone.
It is easier to implement a series of APIs to encapsulate and generate code.

 

Homoiconicity, code = Data

Clojure (and other lisps) provide a third way of handling the code/Data distinction: there is no distinction. In clojure, all code is data and all data is code.
Clojure is a very simple task.
Other languages have a lot of complex syntaxes, so it is very complicated to modify and generate code.
However, clojure is actually known as having no syntax. All the code and data are list and there is no difference. Isn't it easy?

(println "Hello, world")  ;code
'(println "Hello, world")  ;data


 

Macros

Macros are the primary means of metaprogramming in clojure.

A clojure macro is a construct which can be used to transform or replace code before it is compiled. syntactically, they look a lot like functions, but with several crucial distinctions:

• Macros shouldn't return values directly, but a form.

• Arguments to macros are passed in without being evaluated.

• Macros are evaluated only at compile-time.

When you use a macro in your code, what you are really telling clojure to do isReplace your macro expression with the expression returned by the macro

This is a powerful means of invalid action, and is very useful for implementing control structures or eliminating boilerplate or "wrapper" code.

Macros is used for metaprogramming in clojure. For its features, you just need to remember that it is only executed during compilation and replaced by macro with the form, instead of going to eval when the normal function is executed

Example: Define macros, triple-do, and execute three times

(Triple-Do (println "hello "))

Be compiled as this expression:

(Do (println "hello "))

It can be seen that macro can simplify the code and reduce the consumption of function calls.

In complie, the compiler will replace macro and Code. There is no difference in the final Execution Code.

 

Working with macros

Using defmacro to define macro is actually to define a function and register into macro, so the parameter is the same as defn.

However, this is a special function that will only be executed by complier and return a valid form. The compiler will replace macro with the returned form.

To create a macro, useDefmacroMacro.

This defines a function and registers it as a macro with the clojure compiler.

From then on, when the compiler encounters the macro, it will call the function and use the return value instead of the original expression.

Defmacro takes basically the same arguments as defn:

A name, an optional documentation string, a vector of arguments, and a body.

As previusly mentioned,The body shoshould evaluate to a valid clojure form. If the form returned by the macro function is syntactically invalid, it will cause an error wherever it is used.

Triple-Do definition,

(defmacro triple-do [form]
    (list 'do form form form))

Note that do isQuoted, So it is added to the resultant list as a symbol, rather than being evaluated in place in the body of the macro.

List is a function, so all parameters are eval first. Therefore, you must add a quote to do to avoid eval. form requires Eval, otherwise the final list is (do Form)

 

Debugging macros

Using macros can be somewhat mind-bending, since you have to keep in mind not only the code you're writing, but the code you're generating. clojure provides two functions that help debug macros as you write them:MacroexpandAndMacroexpand-1.

MacroexpandExpands the given form repeatedly until it is no longer a macro expression.

Macroexpand-1Expands the expression only once.

(Macroexpand '(triple-Do (println "test"); must be enclosed in single quotes to prevent early eval (do (println "test") (println "test ") (println "test ")))

Macroexpand-allWhich, unlike macroexpand or macroexpand-1, does recursively expand all the macros it can find until there are none left.

Code Templating

Manually creating forms to return from macro functions can sometimes be tedious. Worse, with complex macros it can be difficult to determine what the output form will be actually be.

Why does the above definition use list instead of normal STR '(do Form) when macro returns )?

Although using STR is more intuitive, the reason is that you need to use function to eval form the actual code.

However, if you write complex code, it is easier to use Str. How can this problem be solved?

Clojure provides a template system that uses '(syntax quotation marks, backquotes). This is the only difference between' and ', that is, Use unquote symbol (the Tilde ,~) Laieval Value

The templating system is based around the syntax-quote character, a backquote :'.

(defmacro template-triple-do [form]    `(do ~form ~form ~form))

We can compare this with the above list-based implementation method, which has two advantages:

1. STR is easier to understand, especially for complex code.

2. The two methods need to be particularly labeled in different parts. The list needs to be labeled with 'and does not require Eval, while the template needs to use ~ Mark the part that requires eval

This example is a special case. In most code, more eval is not required.

Splicing unquotes

Unquoting Sequences within a syntax-quote doesn't always work out quite as intended. Sometimes, it is desirable to insert the contents of a sequence the templated list, rather than the List itself.

(Defmacro template-infix [Form] '(~ (Second form )~ (First form )~ (Nnext form) (macroexpand '(template-infix (1 + 3) (+ 1 (3); what is actually needed is (+ 1 3)

Let's look at the example. Sometimes, when splicing code, there will be more list brackets.

In this example, nnext returns the list, so we have a pair of parentheses on both sides.

The solution is to use a special identifier to indicate that the value of the List is needed, rather than the value of the List itself.

To insert the contents of a list, use the splicing unquote, denoted~ @.

(defmacro template-infix [form]  `(~(second form) ~(first form) ~@(nnext form)))(macroexpand '(template-infix (1 + 3)))(+ 1 3)

Generating symbols

You always use local variables when writing macro, but you do not know the context in which macro is used. Therefore, it is very likely that the local variable name in macro, after the complier completes code replacement, it will conflict with the local variable name in the context.

Of course, the simplest solution is not to use local variables.

Of course this is not realistic, so the method is to use # As the suffix within the 'range, and complier will automatically introduce the variable name to random characters to ensure no conflict.

Within any syntax-quoted form (forms using the back-tick, '), you can append the # character to the end of any local symbol name, and when the macro is expanded, it will replace the symbol withRandomly generated symbolThat is guaranteed not to conflict with anything, and which will match any other symbol created with auto gensym in the same syntax-quote template.

(defmacro debug-println [expr]  `(let [result# ~expr]    (println (str "Value is: " result#))    result#))(macroexpand '(debug-println (/ 4 3)))(clojure.core/let [result_2349_auto (/ 4 3)]  (clojure.core/println (clojure.core/str "Value is: " result_2349_auto)  result_2349_auto)

 

When to use macros

Macros are extremely powerful and allow you to control and abstract code in ways that wocould not be otherwise possible. however, using them does come at a cost. they operate at a higher level of each action, and so they are significantly more difficult to reason about then normal code. if a problem occurs, it can be much trickier to debug, since there's an extra level of indirection between where the problem actually is, and where the error message originates.

Macros is very powerful and easy to control and abstract code. however, if you do not have a free lunch, the inevitable problem with macros is that the Code is harder to understand and reason. When problem occurs, debugging becomes more difficult. therefore, the best way to use macros is to use it as far as possible. The reason why big moves are cool is that they cannot be put on the shelf... functions can be used for processing with funciton, but the following lists some scenarios that must be used by macros.

Therefore, the best way to use macros is to use them as little as possible. A few macros go a long way. most things you need macros for (including some of the examples in this chapter) cocould also be accomplished with first-class functions. when you can, do that instead, and don't use macros.

That said, there are certain situations where using a macro is the best, easiest, or the only way to accomplish a given task. Usually, they fall into one of the following categories:

Implement control structures:One of the main differences between macros and functions is that the arguments of macros are not evaluated. if you need to write a control structure that might not evaluate some of its parameters, it has to be a macro.

Since function automatically eval parameters, macro must be used to control the structure of the Code logic.

Typical macro application scenarios, such as multi-product code control and switching in C through macro

Wrap DEF or defn:Usually, you only want to call DEF or defn at compile time. calling them programmatically while a program is running is usually a recipe for disaster. so, if you need to wrap their behavior in additional logic, the best place to do it is usually a macro.

Performance: Because they are expanded at compile time, using a macro can be faster than calling a function. usually, this doesn't make much of a difference, but in extremely tight loops, you can sometimes eke out performance by eliminating a function call or two and using macros instead.

Codify reoccurring patterns:Macros can be used to formalize any commonly occurring pattern in your code. in essence, macros are your means of modifying the language itself to suit your needs. macros aren't the only way to do this, but they can sometimes do it in a way that is least invasive to other parts of your code.

Clojure, or the most powerful place in LISP, can easily produce DSL

 

Using macros, for example, implementing a control structure

Consider a control form which takes two expressions and executes only one of them randomly.

This might be used in a game, or in an artificial intelligence implementation.

In this example, the form is randomly executed to change the logic structure of the Code.

(defmacro rand-expr [form1 form2]  `(let [n# (rand-int 2)]    (if (zero? n#) ~form1 ~form2)))
(Rand-expr (println "A") (println "B") B
If function is used, because the eval parameter is automatically used, both A and B are printed first, so macro must be used.
What should I do if I want to support variable parameters?

(Defmacro rand-expr-Multi [& forms]…)

This is a little complicated, so first think about it, macro should return such code

(let [ct (count <number of expressions>))]  (case (rand-int ct)    0 (println "A")    1 (println "B")    2 (println "C")))

The macro is written as follows. here we can see that clojure is powerful for writing macro, that is, code = data. You can directly use seq function interleave instead of just one row of data...

(defmacro rand-expr-multi [& exprs]  `(let [ct# ~(count exprs)]     (case (rand-int ct#)       ~@(interleave (range (count exprs)) exprs))))

Implementing a macro using recursion

Macros can also be applied recursively.

As an example, consider a custom macro, ++, which can be used instead of +, and which automatically replaces multiargument addition expressions with nested binary expressions which perform slightly better in clojure.

(+ + 1 2 3 4 5) ==> (+ 1 (+ 2 (+ 3 (+ 4 5 ))))

++ Is used to facilitate writing. How can we use macro to achieve this?

(defmacro ++ [& exprs]  (if (>= 2 (count exprs)    `(+ ~@exprs)    `(+ ~@(first exprs) (++ ~@(rest exprs)))))

++ The macro recursion is convenient. Macro itself is a special function, so it is normal to support recursion.

Note that macro recursion requires macroexpand-all for debugging.

(macroexpand '(++ 1 2 3 4))(clojure.core/+ 1 (user/++ 2 3 4))(clojure.walk/macroexpand-all '(++ 1 2 3 4))(clojure.core/+ 1 (clojure.core/+ 2 (clojure.core/+ 3 4)))

Using macros to create dsls

One common use of macros is to generate custom dsls. using macros, a few simple, intuitive expressions can generate much more bulky, complex code without exposing it to the user.

The potential use for dsls in clojure is unlimited.

Enclojure
(The web framework for clojure) allows the user to define web application paths and restful APIs using a simple, immediately understandable DSL syntax.

Incanter, Provides a DSL Based on the R programming language that is incredibly succinct and useful for doing statistics and building charts.

The simplest DSL example is to use XML macro to convert clojure data to XML.

(defn xml-helper [form]  (if (not (seq? form))    (str form)    (let [name (first form)           children (rest form)]      (str "<" name ">"            (apply str (map xml-helper children))            "</" name ">"))))(defmacro xml [form]  (xml-helper form))

 

After defining XML macro, you can directly use it for line conversion,

(xml  (book    (authors      (author "Luke")      (author "Stuart"))))

<book><authors><author>Luke</author><author>Stuart</author></authors></book>

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.