For example look at this built-in Lisp macro example, which iterates from 0 to 9 and writes the numbers.
CL-USER> (dotimes (i 10)
(write i))
0123456789
NIL
Common Lisp has tools like a pretty printer for code (-> PPRINT) and a tool for expanding a macro form: CL-USER> (let ((*print-right-margin* 30))
(pprint (macroexpand '(dotimes (i 10) (write i)))))
(BLOCK NIL
(LET ((I 0))
(DECLARE
(TYPE UNSIGNED-BYTE I))
(DECLARE (IGNORABLE I))
(TAGBODY
(GO #:G267)
#:G266
(TAGBODY (WRITE I))
(PSETQ I (1+ I))
#:G267
(UNLESS (>= I 10)
(GO #:G266))
(RETURN-FROM NIL
(PROGN NIL)))))
From above one can see that the code gets transformed in very low-level Lisp code, with a GO operator (-> a GoTo). It's a low-level loop expressed by loop made from a simple goto operator. One can look at the code and try to figure out what it is doing. One is not guessing what the expanded code looks like, one can generate the expanded code and see what it looks like.In such cases, there's a lot of work required to essentially learn the entire new mini programming language built within Lisp.
That said, there are cases where nothing else but a macro will do. These tend to be abstractions related to the language itself more thain DSLs. Where to draw the line is an art rather than a science, and it can work well -- or go horribly wrong -- on both sides.
This is a fairly typical look: https://ericscrivner.me/wp-content/uploads/2015/06/rainbow-d...
It is sad that the usual first reaction of people seeing Lisp code is distaste. I myself wasted years of my life because of that. I had many opportunities to learn Lisp, but I dismissed them multiple times until I tried Clojurescript. Stupid me.
In non-lispy langs like JS, deeply nested statements could lead to callback hell or pyramid of doom, making the code harder to read, understand, and debug.
In Lisps, nested expressions are common and idiomatic due to the Lisp's code-as-data philosophy. In Lisps, code is composed of expressions that can be composed together, with each expression potentially being an evaluation of other sub-expressions. This doesn't lead to a pyramid of doom, since the emphasis often is on data transformation rather than orchestration of side effects. Nested expressions allow you to build complex behaviors from simpler ones in a very direct and composable manner, thereby making code concise and easy to reason about.
Deeply nested statements in Javascript often hurt readability and maintainability, while nested expressions e.g., in Clojure typically enhance them due to their emphasis on code/data composition and transformation. It is a difference of chaotic complexity versus structured and intended composition.
It's different in Lisp because you basically program a thing from the inside out, meaning that you don't just read the code from top to bottom. Instead, you constantly evaluate expressions and expand macros while going through it. It's practically like playing a game, and there's a good amount of fun. People often dismiss Lisps (Clojure, CL, Fennel, Racket, etc.) after reading Lisp code without any connected REPL and often don't even grasp what makes it so awesome. It's like disliking food after seeing it for the first time in a cooking show on TV. Good talk of relevance: "Stop Writing Dead Programs by Jack Rusher" https://www.youtube.com/watch?v=8Ab3ArE8W3s