i think the macros make life easier, and if well written make the code more maintainable by simplifying the code you're encountering most of the time.
for me the "rope" was the ridiculous complexity of things like the "loop" macro. Coming from a newb (to CL) perspective and wondering "how do i use this thing" ... ugh you could probably write a small book on all the different ways to use that thing. This type of stuff was littered through the language. Instead of small focused functions that were really self explanatory, but limited in capability, you have things that are way too ... i dunno "configurable"? "modifiable"? There were also thinks akin to "method overloading" but done by a drunk person seeing how many "useful" ways they could call this one function.
The core language documentation is extraordinary. Probably better than any language out there.... but I moved on to Chicken Scheme for my personal work. Life was much better afterwards.
>for me the "rope" was the ridiculous complexity of things like the "loop" macro.
You are not forced to use it. I like the LOOP macro, but there are many alternatives, which you can easily load and use.
>Instead of small focused functions that were really self explanatory, but limited in capability, you have things that are way too ... i dunno "configurable"?
So, for example, it looks like you would have liked to use the ITERATE lib instead of the LOOP macro.
Macros are indeed quite the bit of rope, but they're actually not that hard to tame. The problem is that when you forget to use gen-sym (to create unique symbols at macro expansion time) it will often work, but then suddenly do really weird things in the wrong context. My main beef with CL macros is that they're quite cumbersome to write, since there's no shortcuts for things like gen-sym out of the box. This is something that later lisps like Clojure fixed, although they have to provide these fixes by default, since they lack some of the more powerful and dangerous features listed below.
The worst bit of rope I ever personally saw was reader macros. These allowed you to essentially modify the grammar of the language on the fly, inserting functions into the lexer to handle unknown symbols at read time. This is exactly how the quote operator, which allows you to insert literal code without running it, works. Since this happened at run time, any reader macro you registered affected all files that were read later, and thus was build order dependent. If you wanted to use your reader macro in every file, you better load it first and hope it doesn't break any of your libraries.
CLOS, the standard OOP structure of CL was also full of foot guns. It's super impressive that they managed to add an OOP system to an existing language without breaking everything, but the design was .... dangerous. CLOS has some pretty weird rules around method routing, with the methods existing outside Objects and routing be handled by argument specificity. The moment you find yourself extending classes just to win the specificity race with another method you have gone too far and it's time to turn around and go back down whatever road you came up.
For the most part CLOS was fine, but you could design yourself into some really weird corners. The worst part about CLOS was the before, after, and around functions. These allowed you to add functions that wrapped your regular functions, possibly multiple times with order of execution being handled by similar specificity rules. The kicker was that every of these methods was responsible to call the magic "call-next-method" function, which continued the call chain down the stack. If an around method did not do this, the main method would never execute. It is super confusing to manually call a method, only for it to not execute or trigger your debugger, especially since you can register around methods on any method from any package.
Also, CL has a very weak definition of package privacy. Any public symbol, function or otherwise, is accessed externally with the form "package:symbol". But private symbols, those not explicitly exported, can be accessed with "package::symbol". Much like ruby's much maligned "send" method, there's nothing preventing sane programmers from staying way the hell away from private methods. But just like ruby's send method, that is a mighty tempting tool to leave laying around when deadlines loom and pressure is mounting.
Before I finish, a sibling comment mentioned the loop macro. This thing is absolutely insane, I personally stayed way away from it because I never really mastered it, and syntactically is didn't match anything else from CL. Here's a valid example from the "LOOP for Black Belts"[0] page.
(loop for i in *random*
counting (evenp i) into evens
counting (oddp i) into odds
summing i into total
maximizing i into max
minimizing i into min
finally (return (list min max total evens odds)))
While it sure is pretty, it's really jarring when you're working in CL. Where are the parens? How the heck does this parse? What is the runtime complexity of this thing? How do I figure out what magic keywords are available within this loop macro? It's maddening!
And before you ask, no symbols like "counting" are not already functions in the lisp standard. That would be far too easy. God help you if you have a loop call that needs to be modified just enough to make using loop unfeasible.
> Since this happened at run time, any reader macro you registered affected all files that were read later
Common Lisp has readtables, which you can create and change. So you can read certain files with a different readtable, leaving the default intact.
> It is super confusing to manually call a method
This is just the usual super method call in most object-oriented programming languages. If you have method and you want to call the method from the superclass, you have to explicitly call it.
> loop macro
The LOOP macro is not CL specific. CL has inherited it from Lisp Machine Lisp, which got it from Maclisp. At MIT it was even a single source file for several different Lisp dialects. The origin of this macro is actually Xerox' Interlisp - where it was called FOR.
It's not that bad once one masters the basics and the documentation is in the ANSI CL standard.
The upgrade from LOOP is the ITERATE macro, which even more powerful, but with more parentheses:
> Common Lisp has readtables, which you can create and change. So you can read certain files with a different readtable, leaving the default intact.
That a footgun can be occasionally be handled carefully does not erase the fact that it's a footgun. The ability to modify the read table on the fly is for certain super interesting, but IMHO very dangerous. I am glad that the languages I use on a daily basis do not have this capability, for certain.
> This is just the usual super method call in most object-oriented programming languages. If you have method and you want to call the method from the superclass, you have to explicitly call it.
Not a good comparison. While there is some similarity to super in languages like Java, the :around and call-next-method in CLOS is far more dangerous. In Java, super calls are inside the method in question, any stupid decisions around super are right in front of you. In CLOS, it can be in any file in your code base. This makes the source of the problem far less clear, and expands the possible scope of WTFs by a lot.
> The LOOP macro is not CL specific. CL has inherited it from Lisp Machine Lisp, which got it from Maclisp. At MIT it was even a single source file for several different Lisp dialects. The origin of this macro is actually Xerox' Interlisp - where it was called FOR.
An interesting history, for certain, but still jarring as hell, in my opinion. Macros like these are un-lispy, and really never should have been included. I always avoided them for the more primitive looping constructs, but which ones exactly now slip my mind.
> In Java, super calls are inside the method in question, any stupid decisions around super are right in front of you
The problem you mentioned was when the super call was missing. You have to find out which method gets called for the runtime object and then see that the super call is missing.
In CLOS I'd list the applicable methods and edit the first :around method.
True, CLOS is the much more complex system, with a meta-object protocol, generic functions, multi-methods, multiple-inheritance, meta-classes, method combinations, ...
Java was designed for the simple case and then promoted for the 'industrial programmer' - which explains part of its success - but it got complicated soon and people added for example Aspect Oriented Programming to it - which has similar features like CLOS :around methods.
> Macros like these are un-lispy
Not really. What you think as un-lispy is perfectly fine for many. There is a tendency to narrow down the language to simplify it, like Dylan or many others. Lisp is always more about freedom of design, experimentation, flexibility. Otherwise we wouldn't have widely different extension approaches in one language: format strings with loops, a whole complex language for printing code, macro like LOOP, ...
Sure you can just use macros to reorder arguments and make code more convenient, but at the same time you can use macros to add radical different sublanguages - that's one of the reasons I use Lisp - it allows me to do that and many have implemented very interesting language extensions on top of Lisp. Personally I think with some practice LOOP macros are relatively easy to write and I even prefer them over tail-recursive loops in Scheme - which are IMHO much harder to follow.
>That a footgun can be occasionally be handled carefully does not erase the fact that it's a footgun.
You know, there are languages that don't allow you to shoot yourself in the foot, because they don't allow any guns.
I prefer languages with power.
>The ability to modify the read table on the fly is for certain super interesting, but IMHO very dangerous.
> I am glad that the languages I use on a daily basis do not have this capability, for certain.
Reader macros are there to be used when they are necessary. And in those cases, they make ALL the difference in the world. In such cases, the "languages that do not have this capability" will require a lot of code, often hard to maintain, to achieve the same result.
So, i prefer languages with guns.
>Macros like these are un-lispy, and really never should have been included.
LOOP wasn't included on a whim. Read about the Common Lisp standard creation, it was based on at least a decade of experience writing Lisp for "serious" stuff.
>While it sure is pretty, it's really jarring when you're working in CL. Where are the parens? How the heck does this parse?
Note that the compiler will often tell you if the LOOP is wrongly written, at compile-time.
It is ugly to some. I love it. On imperative programming, a big part of programming is doing iteration. LOOP makes iterative code read easier, natural, as you sample code snippet proves. LOOP looks like that because it's a DSL; it is a DSL tailored for doing iteration.
>How do I figure out what magic keywords are available within this loop macro? It's maddening!
Yeah, but then it comes on second nature.
Again, you're not forced to use LOOP, there are alternatives like ITERATE.