How to determine which forms are macros and which functions when viewing Clojure code?

Lisp / Clojure code has consistency in their syntax, and this is a plus, since you do not need to understand different designs. But sometimes it’s easier to understand if you look at a piece of code, simply using a different syntax that is used, for example, this is a case of switching, or is it a pattern matching structure, etc., without actually looking at the text.

I started with Clojure a couple of months ago, and I realized that I couldn’t understand the code without reading the form name and then googling whether it is a macro or function and how it works.

So, it turns out that a piece of Clojure code, regardless of syntax uniformity, is not uniform.

This may seem like a function, but if it's a macro at all, then it may not evaluate all of its arguments.

Is there a naming convention or indentation style that all macros use, so is it easier for someone to understand what is going on?

+6
source share
3 answers

The most useful intuition, in my opinion, comes from understanding the purpose of this operator / Var. Well-designed macros simply could not be written as functions and still offer the same functionality with the same syntax, because if they could, they would actually be written as functions (see the "Well-designed" part above!). 1 So, if you are dealing with a construct that cannot be a regular function, then you know that it is not; otherwise it is possible.

In addition, the usual ways to learn Vars exported by the library tell you if you are dealing with a macro or an up function. This is true for doc ( (doc foo) says that foo is the macro at the top of its output, if that is true), source (since it gives all the code) and M-. (go to the definition in Emacs with nrepl.el or swank-clojure; M-, bounces back). You can expect that the documentation can indicate what a macro is and what not (except that this is not necessarily true for docstrings, since all the usual ways to access docstring already tell you if you are dealing with a macro as described above).

If you discard a piece of code with the intention of forming a rough understanding of what it probably does based on the assumption that the various operators perform the functions suggested by their names, then either (1) the names are quite suggestive and you get an idea which is for the code, so you don’t even have to worry about which operators are macros, or (2) the names are not suggestive enough, so you will need to dive into the documents or source for some operators anyway e, and then the first thing you learn is which ones are registered as macros.

Finally, there is no single naming style for macros, although there are certain conventions specific to specific use cases. For example, with-foo line constructions are usually convenient macros whose purpose is to simplify working with resources like foo ; dofoo -line constructions tend to be macros that take the body of expressions to be executed (how many times and with what additional context setting the macro depends, the most basic member of this do family is actually a special form, not a macro); The deffoo constructs introduce new Vars or type-like objects.

It is worth noting that such models are sometimes violated. For example, most Threading constructs ( -> and Co.) are macros, but xml-> from clojure.data.zip.xml is a function. This makes sense when you consider the provided functionality, which returns us to the goal that the operator is the most useful source of intuition.


1 There may be some exceptions to this rule. One would expect them to be documented. Some projects, of course, are not documented at all (or almost so); here the problem completely goes away, since in any case you need to go to the source to understand everything.

+4
source

There are two attributes that a macro (or sometimes a special form) usually distinguishes from a function:

  • When the form does some kind of binding (i.e. announces new identifiers for later use)
  • When some arguments are evaluated lazily

Examples of the first case are let , letfn , binding and with-local-vars . Strange, defn is defined as a function, but I'm sure it has something to do with the Clojure boot process ( defn is defined before defmacro ).

Examples of the second will be and , or and lazy-seq . In all these constructions, arguments are evaluated lazily, placing them in conditional branches (for example, if ) or moving them inside the function body.

Both of these attributes are actually just macro expressions that control the syntax of Clojure. I don’t think that macros with streams ( -> and ->> ) fit very well into any of these categories, but in nil-safe versions ( -?> And -?>> ) they fall under lazy arguments.

+1
source

As far as I know, there is no forced naming convention.

Functions are generally preferred wherever possible, but macros can sometimes be noticed when they follow the def<something> pattern to create something or with-<resource> to execute something with an open resource.

Because of this, you can find the clojure doc macro. It will tell you whether the form is a macro / function / special form, and also gives it a list of arguments and a doc string (if any). for instance

 (use 'clojure.repl) (doc and) 

The following message will be printed.

clojure.core / and

([] [x] [x and next])

Macro

Computes exprs one at a time, from left to right. If the form returns a boolean false (nil or false) and returns that value and does not evaluate any of the other expressions, otherwise it returns the value of the last expression. (and) returns true.

Some editors (such as emacs) will provide this documentation as a pop-up window or shortcut key, which will greatly speed up its access (and reading).

0
source

Source: https://habr.com/ru/post/950153/


All Articles