Thursday, April 17, 2008

Lisp'isms in C++

In the previous post, someone commented about the lack of exposition on how my re-learning Lisp has directly affected the C++ I write. It is true that functional programming per se is not a Lisp only domain, and it would be unfair to attribute domain decomposition and bottom up design to learning Lisp. So in this post I then expound on the "Lisp'isms" in the C++ code I write on a daily basis now.

One of the things that Lisp has are macros that not every other language out there has. Admittedly, in C++ there's nothing close to that -- until the advent of template metaprogramming. So let me try and do justice (with the very limited Lisp knowledge I still have about macro's) and give an example.

Generating Function Objects

In Lisp (Common Lisp, for those language lawyers out there) you have the capability to define macros that take an argument (or set of arguments in the form of a list or lists) and return generated code. This is built into the language and is nothing really new in the Lisp world.

There's nothing close to this in C++, but you can emulate this with template metaprogramming. The trick is to use the available libraries to make generating code at compile time simpler. There's the Boost.MPL and Boost.Fusion libraries that allow you to exploit C++'s template system to generate types and eventually function objects you can use throughout your code.

An example of these applications can be seen in Boost.Proto where you're able to implement your domain specific embedded language using, well, a domain specific embedded language. You can call it a meta domain specific embedded language hosted in C++. Essentially, like in Lisp where you use Lisp to extend Lisp, Boost.Proto allows you to extend C++ using -- well, C++. This post is too short and limited to discuss the details of Boost.Proto in depth (which is something that might be worth doing some other time). [0]

That being said though, you'll still need to create the new DSEL within the rules of C++ -- unlike in Lisp where your macro's can take in even illegal Lisp forms.

Lambda

In another previous post I wrote a little bit about Lambda, and how it will change C++. Well actually, we don't need to wait for C++0x to be able to approximate Lambda support in C++. All we'd need to do is right now use existing libraries like Boost.Lambda, Boost.Bind, and Boost.Phoenix (in Boost.Spirit) to create *almost* nameless function objects.

The idea behind all the above libraries is to leverage C++'s template system to compose functions out of already defined primitive function object types. Using expression templates, these function objects are essentially created at compile time and converted to machine code by the compiler. These function objects are usually -- but not exclusively -- used in conjunction with the STL's algorithms that take function objects applied to a range of iterators.

In Lisp, lambda is the way of creating functions -- nameless functions that is -- and can be bound to symbols which can then in turn be referred to to refer to functions. In C++, we (almost) have this capability by using function object wrappers -- we have the TR1 function object wrapper, or Boost.Function which is an implementation of the same TR1 function object wrapper only in Boost.

CLOS and Boost.Fusion's Struct Adapter

In Lisp, you have the Common Lisp Object System, where you were able to essentially bring in Object Oriented design to influence the implementation of certain concepts in the solution you're writing. Until recently, there was no easy way in C++ to be able to create structs or types that you can inspect even at compile time. I say until recently, but because now you can do this with Boost.Fusion's Struct Adapter.

The CLOS is a collection of macros (in the Lisp sense) and functions that allows you to represent classes in Lisp. Boost.Fusion's Struct Adapter is a set of preprocessor macro's (in the C++ sense) that allows you to generate a type that looks and feels like a normal C++ struct when being used, but in reality is a Boost.Fusion sequence. This is a very important development because this means we can now start writing generic functions that deals with Fusion sequences at compile time *and* runtime.

Not only that, but now that means we can leverage other CLOS goodness available in Lisp in C++ as well. This I'll have to write about in a later post.

Multimethods

In Lisp, since functions are generic and are external to objects and dispatched through pattern/type matching, you get for free the efficient implementation of the multimethod solution. This post is too limited to discuss the multimethod problem in object orientation, so I won't go into it in detail. [1]

Essentially, with Boost.Fusion's Struct Adapter, we can now perform some magic by following "the Lisp way" of making free functions that dispatch on matching two or more types of the parameters. Here's an example implementation using first, pure C++ templates:

template <class T1, class T2>
static void draw(T1 const &, T2 const &);

template <>
static void draw<rectangle,absolute_canvas>(rectangle const & object, absolute_canvas const & canvas) { ... };

template <>
static void draw<circle, absolute_canvas>(circle const & object, absolute_canvas const & canvas) { ... };

There are a number of problems with the above approach, which involves ambiguities and the combinatorial explosion of the number of implementations. There is a cleaner way of doing this with template metaprogramming -- but I will have to write another post about that some day soon.

Summary

So how does knowing Lisp change how you think and program? At least programming in C++, you get introduced to concepts like functional programming and template metaprogramming that are very powerful enablers for making bottom-up design and top-down decomposition a reality. It doesn't stop with the concepts though, you get to reflect the concepts in code that directly translates to solutions whatever the particular domain you're working in.

Yes, C++ will never be a functional programming language. But just like Lisp, it supports multiple programming paradigms -- object oriented, imperative, functional, generic, and sometimes declarative. The parallels exist and there is a case to be made to put C++ and Lisp in the same category as definitely extensible and very powerful programming language.

I agree with the great Lisp writers and developers out there -- if only to change the way you think about programming, you should learn Lisp. But as with any performance critical project, nothing beats machine code -- and the best solution I personally will still choose C++ when developing high performance and highly scalable systems overy any other language out there.

But pound for pound, I'll take Lisp for anything remotely complex for quick prototyping and deployment. For performance criticial stuff (like the projects I'm working with), I will choose C++ in a heartbeat.

References

[0] - Boost.Proto documentation
http://boost-sandbox.sourceforge.net/libs/proto/doc/html/index.html

[1] - Multiple Dispatch or Multimethods
http://en.wikipedia.org/wiki/Multiple_dispatch

4 comments:

Alex Ott said...

You can also look to the http://intelib.org -- it allow use lisp-like syntax in C++

ok said...

Very interesting.

In my job, I'm combining Common Lisp with a (really well-written) C++ library. The interesting part here is that this Lisp implementation compiles to plain C, allowing for inline C code wherever I need it.

Now, I've seen some (quite desperate) attempt to add scripting facilities to the C++ library.

My (quite simple) Lisp solution (which compiles to plain C, as mentioned above) not only is much smaller and simpler, it even runs much faster! And the main advantage here is that you can run it either interpreted or compiled (so the built-in 'scripting' facility of Lisp doesn't have any drawbacks at all!).

Alex Ott said...

I'm myself prefer to use Common Lisp where possible ;-)

Conny said...

((I was a programmer long before I was exposed to lisp (or so I understood it) my first experience with LISP was, when my teacher said do a program that derivate math expressions , and it was doable , the next was to change that program so that it made integral calculus. In just some weeks I made my first compiler.)
(LISP is definitely the language that changed my way of thinking )