Lisp Macros have the fullowing use:
1) Basic text Search-and-Replace
2) Building a lexical context
3) SETF Accessor
4) Conditional Evaluation
5) Binding things to the TopLevel
While technically every macro is just a search-and-replace texteditor, many example macros are used the same as c-Macros.
They are used to abbreviate syntax, but have no deeper functionality. Example of this include arc = for setf or syntatic sugar to avoid typing ' or the f function or the graham if. Another example would be to change the first,second, third etc. buildin function to n1 n2 n3 etc.
I think these macros are kind a useless. First you often solve a problem you only have in lisp. Most languages dont require to quote data or use kinda long names. While that may save you a few letters I dont think that is the point. Programming languages should represent deeper and more abstract concepts and not try to make their code especially short from a pure letter standpoint. Otherwise you might end up with a unreadable language like Perl.
For example the Graham-if can be written in Python:
Code: Select all
if condition1:
action1
elif condition2:
action2
etc.
While this code has the useless "if" and ":" it isnt inconvinient to write. I actually like the clear structure of the python code compared to the g-if where the semantic is dependent on the position in the code. I dont want to count if a certain codeblock is in 5th or 6th position and therefore is a conditional or an action. And I expecially dont want to match the parenthisis in my head to decipher if codeblock X is the last thing in the block (and therefore the default-action) or actually just the 7. but not last thing in the g-if (and therefore the condition3). It doesnt matter how much typing you do, if the cognitive overhead is small and you gain readability.
Another point is that these macros dont use the unique "code is data" and "direct access to the parse tree" Lisp is famous for. They can be imitated by such simple things like C macros or text autocompletion.
The (f body) -> (lambda (x y z) body) is neat and good (especially in golfing tournaments) but can be textcompleted easily.
The third point on the list seems to be the Lisp way of creating setter functions.
Lets get to the second and fourth point where I changed my mind about Lisp macros.
I think the crucial point here is that these macros really need a body or complex expression to work out. If you have a simple expression that evaluates to a datastructure (list, string, number, struct etc.) it is possible to replace every macro with a function. Think about it: If a macro/function takes x arguments, which all evaluate themselves, and maps this input to an output, it is impossible to gain any advanatge with a macro. Mapping data inputs to data outputs is the basic definition of a function. Even sideeffects just expand this definition. A function takes several data inputs, produces sideeffects and returns data outputs. (Note: You might gain performance with macros instead of functions)
On the other hand macros might be useful if at least one part of the macro is a complex expression or body, a "code input". To make things simple on my already stressed mind I go with exactly one body/code argument. While technical it is possible to take 2 or more code inputs I think it strains the human mind to do so. So, that leaves us with a bunch of data arguments like strings, numbers or lists and one code block which is passed to the macro as raw, code input and is declared the &body argument.
Now we have the following options:
We create a lexical enviroment in which the codeblock is executed. Simple examples include the with-open-file macro. In this macro a string and a symbol is passed as data argument and a code block is executed with a file-access as lexical enviroment. The code can use the enviroment provided by the macro to access the file.
Unfortunatly to be really useful such a macro must provide additional functionality. If the macro only provides a binding of a certain variable it is useless because other languages can just use assignment to imitate the Lisp macro. A (witha 7 body) macro that just binds the number 7 to the variable a is useless because a=7;{codeblock} has the same effect. The witha macro is just a let macro with a as the default parameter.
The let macro has the power to bind a variable only for a certain amount of time (the scope of the let/macro) but other programming languages dont seem to miss that possibility. Instad they just define a new variable in the local scope and forget the variable as soon as the scope changes. So the Let macro as the basic context creating macro is just the Lisp way of assining variables.
But the context creating macros have more possibilities than standard assignment. For one they can execute enter and exit code. So a file/database/something can be opened/locked/entered, the codeblock executed and the needed close/unlock/cleanup done automatically.
I am not sure how useful lexical binding really are. Complex assignments can be substitutes with a=function (datainput);{codeblock}. The enter/exit trick can be emulated in Python with the "with x as variable:" construct.
The other option is to use conditional evaluation. And again other languages can substitute basic use of conditional evaluation with their if/case statements or similar control structures. Multiple datainput or codeinput doesnt change the ability of other languages to keep up. Even if a macro in Lisp can combine the datainputs to a complex predicate, other languages can use if (function (datainput1, datainpu2,etc.)==True do Codeinput1. The only interesting uses of this macro-class is in combination with other macro-classes.
The last point is binding things to the TopLevel. A macro can define a function (or other macro) at the top level. And again it is trivially easy for other languages to keep up with the possibilities of Lisp if tasks are easy. a=something binds a in the toplevel, b=lambda x:2*x binds a function to the toplevel. But while other languages need an explicit assignment, Lisp can bind symbols which are determined during runtime. A good example which illustrates this point is this one: You have an external configuration file with the structure { Database1="Hello.dat";Database2="DataisHere.dat";DataZum="DatRer.dat" } and want to bind access functions to all these databases to the top level. It is possible to create a Lisp macro that defines the functions Database1get, Database2get, DataZumget all in one go. Other languages at least need to write Database1get = funcfromconfigfile (1), Database2get=funcfromconfigfile(2), DataZumget=funcfromconfigfile (3). Another example which is illustrated in "Lisp: A gentle introduction" is a finite state automaton where the nodes are represented by closures. These closures/node fucntions are bound to the top level and can be easily accessed. So while other programming languages need to write programms which access data, Lisp can write functions which are bound to the data since their creation.
There are Python hacks with the exec command that can simulate this toplevel binding but they arent that well integrated into the language. The "Right Way (tm)" to implement the above example would be to load the configuration file into a classobject, write a generic access function which takes a databasename / number as input instaead of defining functions only for a certain database. This means writing function which access data instead of writing functions which are bound to the data in the moment of their creation.
I think the basic pattern which we have seen here is that other programming languages can easily replace simple macros both in convinience and function. Also most of the singular uses of macros arent really that useful, rare or can be implemented with other constructs. I think the mainpower of macros is a combined use of lexical context, conditional expression and toplevel binding. The a-if macro demonstrates this: It first checks if the argument is nil (conditional evaluation) and if it is true it bind the symbol "it" while a function body is executed (lexical context). The Defspell from the textbased adventure use a combination of toplevel-binding and conditional evaluation. It bind a new game-action to the toplevel, but only executes it if certain conditions are met.
That was a long post and still I dont understand macros and/or see a general usefullness, but at least it is a start.