At the outset let me say that I know that Graham highly values Lisp's ability to transform itself into the language you need, especially by using macros. And that's nice, too, but it wasn't what excited me. For me, it is summed up in this quote:
> As you're writing a program you may think "I wish Lisp had such-and-such an operator." So you go and write it
I remember mentally screaming "Yes! Yes!" when I read this the first time, and wishing I could explain it to some of my coworkers. But who could possibly disagree with such a simple statement? When I read it, why did I think it would be hard to explain?
Thinking back, the conflict was between two perspectives on designing the modules or classes of a codebase: one says that you are implementing a Platonic ideal of the domain, another says you are implementing operations that support the functionality of the system.
In the eyes of many of my coworkers at the time, good domain code would have no traces of the specific needs of the program it was written for. It would have its own logical consistency, dictated by the reality of the domain. A Car class should behave like a car, and it should serve the needs of any future car-related programs our company wrote.
This was obviously an incomplete picture. For example, a Car class in a ride-share system probably doesn't have a coefficient of drag, even though a real car does. But some people felt that the naive, incomplete picture captured such a profound and helpful ideal that it was better than a complete picture that acknowledged the demands of the program (kind of like it might be better to tell a four year old "lying is bad" rather than tell them that it's super complicated and mommy and daddy lie all the time but only in the right ways for the right reasons.)
I hated the way this ideal glossed over the obvious truth that the supposedly neutral domain model was in fact very selective. Any Car class would specify a tiny and very specific subset of the behavior of a real car, and it would do so at a specific level of fidelity, which needed to support the functionality of the larger system. Because I hated pretending that we weren't writing the low-level code for specific high-level needs, I found it refreshing that Paul Graham would so clearly put the needs of the program first. He wrote domain functionality on demand! And if he left coherence and maintainability of the low-level code as an unspoken exercise for the reader, that was no worse (and better in my mind) than leaving unspoken the connection between high-level functionality and low-level design.
Anyway, I was always confused that Graham called this "bottom-up programming," because he explicitly goes top-down. He discovers a need at a higher level ("As you're writing a program you may think 'I wish Lisp had such-and-such an operator'") and then he drops down to the lower level to provide it ("So you go and write it"). To me this is opposite of the approach that says you start at the bottom and implement a Platonic ideal of the domain, without first explicitly considering the needs of the program.