There is a number of reasons that make Lisp macros so much more powerful than functions in any other language.
Consider you want to implement printf that is compatible with C printf.
As the function implementor, you want something like printf("%d", 5+2, anExpensiveFunctionThatProducesAString()) do the right thing and print "7" on the terminal, but you get many more options and mechanisms you can use when implementing it in Lisp vs other languages.
Every other language:
* every time printf is called, all of the arguments have already been evaluated. There is no way to avoid calling the expensive function when the format indicates the result is not needed.
* every time the function runs you have to parse the format string. You could try and be smart and cache parsing results but this will quickly make it very expensive and complex and might cause problems when format strings are generated dynamically.
* your function cannot make use of the fact that both the format string and the argument never change
Lisp macro:
* you can decide to only evaluate arguments when they are needed,
* you can notice that the format string is static and output code IN PLACE of the macro based on this information. This means you have the option to make parsing happen only at compile time if it is possible.
* you can analyse the arguments further and decide to output code that does princ('7') IN PLACE of the macro. The resulting code will have no operations to parse or analyse anything and will always execute quickly and efficiently.
In general this is the way I think about Lisp macros vs functions in other languages:
"Lisp macros allow you to run logic at compile time that can create code dynamically that does exactly what you need it to do, no more, no less, based on macro arguments and/or unevaluated macro arguments and/or the environment."
Lisp macros are ultimate in building abstractions. Where in other languages you have to contend with the language always "sticking out" of your abstractions -- there is only so much you can do to hide the underlying language, in Common Lisp you can always hide the language completely. Implementing your abstraction is taking a data structure (can be for example an XML file) and running an arbitrary logic to generate another data structure (a function) that is then outputted in place of the macro. If the XML describes your binary file structure, the macro can take those descriptions and generate perfect parsing code that does exactly what is needed without any extra stack frames, conditionals, syntax artifacts, etc.
Consider you want to implement printf that is compatible with C printf.
As the function implementor, you want something like printf("%d", 5+2, anExpensiveFunctionThatProducesAString()) do the right thing and print "7" on the terminal, but you get many more options and mechanisms you can use when implementing it in Lisp vs other languages.
Every other language:
* every time printf is called, all of the arguments have already been evaluated. There is no way to avoid calling the expensive function when the format indicates the result is not needed.
* every time the function runs you have to parse the format string. You could try and be smart and cache parsing results but this will quickly make it very expensive and complex and might cause problems when format strings are generated dynamically.
* your function cannot make use of the fact that both the format string and the argument never change
Lisp macro:
* you can decide to only evaluate arguments when they are needed,
* you can notice that the format string is static and output code IN PLACE of the macro based on this information. This means you have the option to make parsing happen only at compile time if it is possible.
* you can analyse the arguments further and decide to output code that does princ('7') IN PLACE of the macro. The resulting code will have no operations to parse or analyse anything and will always execute quickly and efficiently.
In general this is the way I think about Lisp macros vs functions in other languages:
"Lisp macros allow you to run logic at compile time that can create code dynamically that does exactly what you need it to do, no more, no less, based on macro arguments and/or unevaluated macro arguments and/or the environment."
Lisp macros are ultimate in building abstractions. Where in other languages you have to contend with the language always "sticking out" of your abstractions -- there is only so much you can do to hide the underlying language, in Common Lisp you can always hide the language completely. Implementing your abstraction is taking a data structure (can be for example an XML file) and running an arbitrary logic to generate another data structure (a function) that is then outputted in place of the macro. If the XML describes your binary file structure, the macro can take those descriptions and generate perfect parsing code that does exactly what is needed without any extra stack frames, conditionals, syntax artifacts, etc.