4 ms·
I remember nodded excitedly along with this when I first read it maybe twenty years ago, and less so now, so I decided to go into my mental time machine and see
by dkarl 3y ago
I remember nodded excitedly along with this when I first read it maybe twenty years ago, and less so now, so I decided to go into my mental time machine and see if I could remember what was so exciting about it.
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.
- sillysaurusx 3y agoThere’s an interesting thread of truth in this, and you’ve touched on it. Paul always goes top down fist, and bottom up second. I don’t think he explained that very well. Doing bottom-up first is fraught with danger, since you don’t know what you’ll need until you start writing an app. That’s why it’s so important to let the bottom-up approach be guided by the needs of an actual application. It rarely goes well any other way. The problem isn’t starting top-down, but that people never do the bottom-up work that you uncover. They get around it in other ways, mostly by suffering. A lot of devs don’t even realize they’re suffering; they think it’s part of the work. Whereas if you pull apart the elements of your top-down program into their component parts, you’ll almost always end up with shorter programs that are more powerful. Sometimes exponentially so.
- zelphirkalt 3y agoGetting around the problem in other ways often means they reach for third party libraries, adding dependencies, just glueing half fitting parts together, solving the problem now, but suffering in the long term. (Or whoever comes afterwards to maintain the project, if the original devs left after 2 years for the next gig.)
- 0xpgm 3y ago> 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. This sums up why I've never really understood this essay. I agreed with the technique but was mystified by why it was called bottom up programming when you first had to approach the problem top down to discover what you needed to implement.