C++ Lambdas Top Gear. Part 3

This time we go to the Lambda Top Gear. Forget the templates. Use lambdas. Part 3, can actually hurt the brain. If not feeling safe or no chaperone present, please leave now.

Or just jump back to Part 2, simple but useful text.

First and foremost I do know, I have not invented the core ideas in the code presented here.  But, I do not know the original author. If you are the one please let me know and I will state it clearly here.

LISP

Is a “LISt Processing language”. Importance (and beauty) of lists as a key concept in computing is proven way back in 1959. Here we will develop something very close to LISP Lists but using standard C++ lambdas and auto facility.

Thus, not LISP Lists but close approximation of them in modern C++.

Perhaps not an exact copy  (for that please use LISP) but still very functional and eminently usable C++ constructs.

For this journey you need one key ingredient: Open (and clear) mind.

Mind open to functional concepts, functional programing and “all that functional jazz” you have been listening about. And yes, if you are coming from JavaScript by any chance that will help a lot. RUST or HASKELL too. And now …

The Deep Dive

Fig 0

First the usage

After this call my_list is an lambda as made and returned from inside  thelist() closure. Fig 0 tells you, resulting closure “sees” the closure around it, from which it was made and returned. And that means  the arguments pack, (the list() has been called with) is available to the closure returned, as long as my_list variable exists.

This is the key:  list( 1, '2', "3", false, 13.0f) is a closure (aka lambda) inside which the result  my_list() was born and returned. The rules of language dictate the param pack, is available inside my_list. Ditto; the parameters pack  1, '2', "3", false, 13.0f, is available inside the closure my_list .

So presented in some kind of “lambda implementation pseudo code” the first call to the list would be leaving this situation on the stack after it has happened.

Fig 1

And the variable my_list is the lambda returned.

Fig 2

To use it, you can call  my_list with exactly one argument that has to be a lambda .

my_list( some_other_lambda ) ;

This some_other_lambda will get called with list() parameters pack.

Remember I am using here a conceptual view on the C++ lambda. C++ Lambdas are implemented as functors but we will keep it short and sweet and still true, without mentioning this too much.

Thus we have our list (as params pack ) preserved for us in the closure, on the calling stack of the list() lambda.  Opposite to LISP, this is lambda with list atoms (aka elements) preserved and present as parameter pack arguments of the lambda call.

Note: depending on the compiler the lambda arguments are not unpacked as above but might be unpacked from the original list(1, '2', "3", false, 13.0f) call above when and if needed. That is not C++. We just show it that way to make it simpler for you.

Into the known

John McCarthy has proven, in order to make them useful, only three commands are required to operate on lists. The three LISP lists core commands are: List, CAR and CDR.  List we have done. CAR returns the “head of the list” and CDR returns the  “tail of the list”.

The requirement for our standard C++ implementation, is this kind of future usage:

Fig 3.

First the head(). Using my_list from above as explained, actually happens inside the head() implementation. The lambda that is argument to my_list is anonymous and inside the head() :

Fig 4.

Ok. I know. This is a real head hurting code. Let us do it slowly step by step. Remember what was returned as a result of  the list() call above. Yes, just a lambda. Fig 2.

head() call uses that as its single argument. This is the list_lambda in the head() code. And that is given another anonymous lambda as its only argument. Huh.

So in a pseudo code head() returns the following:

Fig 5.

But. my_list is coming from inside the call list(1, '2', "3", false, 13.0f); We say: list is the original closure where my_list was created.  Call stack of the list() call is still here.So,the lambda

Fig 6.

is the lambda given to the result of  its closure: list(1, '2', "3", false, 13.0f); Again in pseudo code situation is this:

Fig 7.

Please understand that the Fig 7 is executed back inside the closure of Fig 1.

I am sure not everyone is happy with this explanations. Again please comment and we will sort it out for you. Now please follow the rest, and then revisit.

The tail() implementation

Fig 8.

Again hopefully the comments should help.  We have list, head and tail; let’s make two steps more to increase the usability of Lambda Lists. Let’s make the length() :

Fig 9.

By now, I think you are maybe beginning to like it?  Yes, the list elements  are the parameters pack of the list call. The rest is just lambdas reaching to it and using it.

Next step is quite useful and conceptually interesting. It opens a door to the rest of the “normal” standard C++ code to use Lambda Lists.

Fig 10

We simply collect all the argument of the list call and return them as tuple set.  And there is a lot of in the std library that uses tuples and nicely operates on them. For example my dbj::print() knows how to print the tuples but not how to print the list.

Fig 11

Here is the result:

Dbj Lambda Lists
Dbj Lambda Lists in C++ notation

Would it be too cruel if we leave touple_to_list() implementation as an exercise to the astute reader?

Lambda Top Gear
Lambda Top Gear. Forget the templates. Sort of.