Lisp is Changing My C++
Posted on October 6, 2008
Filed Under Programming, Programming Languages |
I am a mild-mannered C#/C++ programmer by day, and I have been learning Lisp in my spare time. Comparatively speaking, I do not spend much time programming for personal projects (only an hour or two a day), but I noticed something awesome recently:
Lisp is influencing the way I write C++ in a few different ways.
To Generalize…
Imagine my horror: Lisp caused me to realize that I’ve been refactoring the wrong way! For a few years!
Well, that is not entirely accurate. My code did the same thing after I was done. I broke code down into small logical units. Unless I was careless, the code still worked. My functions were more readable, and one could usually see what they did at a glance.
I asked myself questions along the lines of, “What can I do to break this up logically?”, and therein lies the problem:
Weak questions have weak answers.
Powerful questions are much better suited to refactoring!
- “Can I use an existing solution to solve this?”
- “Could this solve a completely unrelated problem?”
- “Can I rearrange things elsewhere so that this code isn’t even needed?”
Sometimes, the answer is no. Some boilerplate code (like exception catching) is an overhead that can not be avoided, can not usually be generalized, and sticks out like a sore thumb in C++ style languages.
Why did Lisp fundamentally shape my refactoring practices? Paul Graham and Peter Norvig are both to blame for this, and for different reasons.
First up, Graham. I taught myself from “ANSI Common Lisp“. At first, I attacked the problems in the book from a C++ perspective, trying to wedge loops and functions into my solutions. Doing the Lisp examples forced me to think simpler. Within a month I started being able to think of functional solutions instead of C++ solutions (reaching for reduce and lambdas instead of loops and classes).
I worked problems out of this book every night for a month and a half, and it still sits next to me as a quick reference. After 8 years of C++ programming, it took a while for things like “reduce” to become the most obvious solution to a problem.
I haven’t reached that magical enlightening moment that Lisp causes. I don’t have the “ooh, shiny!” awe over macros that seasoned Lisp programmers seem to have, so I’m still hacking away.
Norvig has sent me towards enlightenment with Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp. In these pages, I found something that was missing from Graham: elegance
Some of the solutions that I see in this book give me a sense of awe and enlightenment that I haven’t had since I took Abstract Algebra. He presents them in a very straightforward and honest style that can only come with experience.
This book has given me a whole new level of quality to try to reach. I hope one day to be able to do this.
Tools
A year ago, I would have described my programming process like this:
- Design
- Code + document
- Neaten + prune
- Return to 1, as needed
Not bad. However, working with Lisp and seeing that there are different tools in the world has led me to fundamentally restructure how I program. I would now describe my process like this:
- Design
- Code + document
- “Would a tool/function/class have made this task easier?”
- Return to 1, as needed.
For instance, I’m writing some image processing libraries in CL right now, and I’ve written a few tools that I might never have thought of generalizing from a pure-C++ mindset! After all, iterating over an image is really easy in C++, so why bother making it simpler?
However, map-image is invaluable: apply a function at each pixel of an image, and return a new image. Combine this with the lambda construct, and you have a level of flexibility possible only with some of the goofier Boost libraries.
In Short…
While working through the Graham examples, I found that I was playing Code Golf against myself.
I’ve started to see this carry over to my coding at work as well. In the past, I might have done something like the following:
int num_times = SomeFunction(); return AnotherFunction(num_times);
This is a reasonable approach. The local variable name reflects the role of the function, and adds a little bit of clarity for the human reader. Using this style is fine if these are your priorities.
However, I’ve started giving functions better names (when I can), and cutting out the middleman:
return AnotherFunction(NumberOfTimes());
This is effectively the same code, and there’s a good chance that it would compile down to the same assembly language. It’s just as readable. The difference is the line count. It’s a difference small enough for a bikeshed issue holy war, but over the course of small projects (all I’ve done so far in Lisp), being able to fit more code on my screen is a big win for me (all else equal).
I am starting to prefer reading and writing compressed code as a result of trying to solve Graham’s examples in as few lines as possible.
Future Directions
I’m still looking for my “Lisp Enlightenment” with respect to macros, which leads me to believe that there’s something significant that I’m still not getting about Common Lisp. I’m not sure where all of these new techniques are going to lead me, but in the meantime, it is starting to fundamentally change how I approach programming problems.
Popularity: 31% [?]
Comments
13 Responses to “Lisp is Changing My C++”
Leave a Reply

“While working through the Graham examples, I found that I was playing Code Golf against myself. Flipping through the
I’ve started to see this carry over to my coding at work as well.”
Needs editing.
Whoops! Fixed. Thanks for the pointer.
I’m on a very similar path.
The elegance of Norvig over Graham comment prompts me to suggest you take a look at the SICP text and video lecture series. Especially the lecture series. It is amazing what they can do with a chalkboard’s worth of code.
http://groups.csail.mit.edu/mac/classes/6.001/abelson-sussman-lectures/
If you haven’t already, read Practical Common Lisp, available online at http://gigamonkeys.com/book/ . It was the book that made me “get” the power of macros, and then come to resent C++ templates for being utterly anemic by comparison.
What I’ve been doing for awhile now is I ask myself if I’ve ever written or needed whatever logic I’m about to write before in some other part of the program. If the answer is yes, I pull it out into a general tool/function/class or whatever instead of writing it (or something very similar) again. While sometimes that makes the projects feel like they are getting bigger (especially if the re-used pattern is pretty small and I’m making whole new .cpp/.h files for the new class or whatever - depending on language), it helps with mental organization rather dramatically.
As an added bonus, often when I realize I’ve used some pattern at least twice, I find there are several other places I could use the newly created pattern to condense and simplify code in ways I didn’t originally consider. It’s a wonderful feeling.
You needed lisp to teach you to write functions with clear names, not to keep temporaries in locals and to make deterministic mappings out of algorithms instead of loops?
Methinks you might want to take a tour through the algorithm header. Seems like learning C++ would teach you the same things learning Lisp would.
You mention not grokking macros. Have you checked out On Lisp? It principally focuses on macros and how they can make your code smaller.
don’t worry about the feeling of enlightenment. if you aren’t the type who’s impressed by metacircularity and other tomfoolery then it probably won’t come
There is an obvious improvement in readability in your example, but there’s another improvement to.
The second example has stronger encapsulation, as it doesn’t expose a needless variable into the scope of the enclosing function/method.
I always try and minimize state in my objects, which is especially important if you need to write concurrent code. Any state left, should if possible be made immutable.
Reducing the scope of variables, and removing them altogether (where possible) is often a great way to improve code.
Learning functional languages can definitely make the nature of these improvements clearer.
read streams chapter in sicp book for some enlightenment
Hello Jake - Would like to learn more from you (and others) about speed issues, if any, you’ve had (or are having) in doing things such as imaging(!!) with CL?
Now I know very little CL as I write this, but I somehow somewhat unscientifically suspect that even compiled CL code may not be able to beat its imperative, side-effect ridden C/C++ counterpart. Even with my little exposure to CL, I will give CL very high marks for its elegance, completeness, and the code writing techniques it makes possible (via macros, lambdas, and any and all FP-style enabling facilities) but I’m not so convinced if it can generate the same type of speed-efficient object code as C/C++. Would like to hear more on this from you / others.
Hey Harry,
Compiled CL code can definitely keep up with compiled C/C++ code IF YOU OPTIMIZE CORRECTLY. The biggest reason that CL keeps this stigma for being slow is because the language makes it easy for you to write relatively inefficient (albeit very elegant) solutions quickly. The problem is that many Lisp programmers then stop with this version of their program.
The flexibility and robustness gained by manifest typing, implicitly polymorphic functions, and automatic memory management have to be traded for some overhead in terms of CPU cycles and memory usage.
HOWEVER - and this is the big however -, where you are pretty much stuck with this situation in languages such as Perl/Python/Ruby/etc, in CL you can add type declarations, use type-specific and destructive functions, and even manage your memory explicitly to gain back all the speed and space benefits that C/C++ gets.
The really key piece to remember though is code profiling. I cannot stress this enough. All CL implementations come with some suite of profiling tools, and by running these on your initial elegant/functional solution, you can find exactly which functions are the runtime bottlenecks in your code. You then step into those functions and make the changes I mentioned above. Remember that “Premature optimization is the root of all evil” and 90% of a program’s runtime is typically localized in 10% of the code.
So good luck with the imaging library, Jake (I’m doing some similar stuff myself), and Harry, check out the chapters on optimization in both Graham’s ANSI Common Lisp and Norvig’s Paradigms of Artificial Intelligence Programming.
> check out the chapters on optimization in both
> Graham’s ANSI Common Lisp and Norvig’s Paradigms
> of Artificial Intelligence Programming.
I recall skimming thru ACL a while back and not finding anything unusual in the ‘optimization’ chapter… unusual in terms of advice or things I did not already know or appreciate. But PAIP… I’ve never checked out. I hopefully will be able to soon. Thanks much, Gary!