In brief, Lisp's S-expressions (the parentheses) are a straightforward instantiation of the abstract syntax trees to which all other languages parse.
You can manipulate that tree easily and directly in Lisp, whereas other languages (that are not variations of Lisp) hide parts of it, to varying degrees.
All other languages are either equivalent to Lisp, perhaps with a different superficial syntax (there are very few of these; one is Mathematica) or expose only a strict subset of this capability (most other non-Lisp languages.)
In particular, Lisp makes it trivial to do (real) metaprogramming -- programs that write other programs, including the introduction of entirely novel syntactic constructs -- via macros. Lisp macros can create new language syntax that controls when and how code is evaluated, perhaps rewriting it.
Non-Lisp languages can't do that fully, only subsets of it. Ruby metaprogramming, for example, is clever syntactic sugar. You can give methods clever names and use clever calling conventions so that they read more similarly to English, which is called "metaprogramming" in Ruby; but you don't have universal control of the parsers from the level of ordinary Ruby code, and you can't completely control when and how code evaluation happens.
Now, how practical or useful is all that? Opinions vary, from hardcore "you should invent a full DSL with unique syntax for each particular application" Lispers, all the way to those who say macros are a fun but dangerous toy with limited practical value.
Thanks! As someone who hates reading metaprogrammed ruby code, I imagine since Lisp gives you even more power, trying to understand what's happening in code is even harder. I know some people enjoy writing this style of code, but I personally do not
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.
The issue is that many Lisp programmers are .. not great at choosing and naming abstractions. Even when they are, the goals of the program will shift over time, leading to leaky abstractions and macros with names and semantics that no longer exactly match.
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.
That's neat, better than ruby in that regard. Still hate the () syntax especially when you have a bunch of nested functions, I bet you get used to it though
Most use an IDE such as Emacs (paredit-mode and rainbow-parens) that handles automatic paren matching for you, so it's really a non-issue after the first week. The editor takes care of indentation, matching (so you don't have any dangling parens ever), and optionally colors matching pairs in the same color so you can easily tell what level they're at.
After a few years, I turned off rainbow-parens-mode indefinitely. I realized that I no longer see parentheses and instead I see the structure and order, or in the case of badly-written code - chaos and anarchy.
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.
> especially when you have a bunch of nested functions
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.
> trying to understand what's happening in code is even harder
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
You can manipulate that tree easily and directly in Lisp, whereas other languages (that are not variations of Lisp) hide parts of it, to varying degrees.
All other languages are either equivalent to Lisp, perhaps with a different superficial syntax (there are very few of these; one is Mathematica) or expose only a strict subset of this capability (most other non-Lisp languages.)
In particular, Lisp makes it trivial to do (real) metaprogramming -- programs that write other programs, including the introduction of entirely novel syntactic constructs -- via macros. Lisp macros can create new language syntax that controls when and how code is evaluated, perhaps rewriting it.
Non-Lisp languages can't do that fully, only subsets of it. Ruby metaprogramming, for example, is clever syntactic sugar. You can give methods clever names and use clever calling conventions so that they read more similarly to English, which is called "metaprogramming" in Ruby; but you don't have universal control of the parsers from the level of ordinary Ruby code, and you can't completely control when and how code evaluation happens.
Now, how practical or useful is all that? Opinions vary, from hardcore "you should invent a full DSL with unique syntax for each particular application" Lispers, all the way to those who say macros are a fun but dangerous toy with limited practical value.