How to Write a Lambda Expression and Callback Function in C

Lambda expression has been supported by many programming languages, but not standard C. By using lambda expression, one can easily do complex job with only the main part of algorithm. For example, one can write a parallel mapping function by lambda without declaring a multi-threading function. Therefore one writes only the essential part of code. The result is a better and simpler way of coding.

A classic example in C, you can use the qsort from standard library. But it requires you to write a compare function and pass it to qsort. Lambda expression is gonna make your life way easier on this. The comparison would be something like this:

Original:

int compare (const void * a, const void * b)
{
    return ( *(int*)a - *(int*)b );
}

qsort(values, 6, sizeof(int), compare);

Lambda:

my_qsort(values, 6, a, b, return (*(int*)a - *(int*)b););

It is very clear that lambda expression makes the programmer focus on only main part of algorithm. There is no need to worry about a good and appropriate function name for this comparison.

Before jump into the solution, there is a very important thing you must know. If you want to have the same functionality of lambda as shown above, your compiler needs to support *nested function for making an fake anonymous function in C.

Here is another example of using lambda. This is a demo of lambda callback function. If you only needs a callback function, there’s no extra requirements. it can be compiled by almost every C compiler. The result is like this:

pop_job(job_semaphore,
    //This is for double check, preventing unexpeted bug
    while(isBufferEmpty(buffer_t));
    bufferRead(buffer_t, p);
    printf("\tpop %d, %d\n", p.x, p.y); //Print message
);

It looks amazing!!!

The secret to achieve this coding style is MACRO. Macro is a rule or pattern that specifies how a certain input sequence (often a sequence of characters) should be mapped to a replacement output sequence (also often a sequence of characters) according to a defined procedure. Normally, we only map a variable, string, or a statement, but actually macro can map multiple statements onto out sequence. Here is the macro definition of the second example:

#define pop_job(sem_mutex_t, call_back_function) do {\
    sem_wait(sem_mutex_t); /* down semaphore */\
    call_back_function;\
}while(0)

The semaphore is for thread synchronization. Here, I define a macro( or you could call it an inline function), and wait for semaphore signal. After receiving the signal, it will execute a call back function. But here, this call back function will contain lines of code. In other word, the string call_back_function would be mapped to four statements in the last argument of macro, not just one. The result after expanding this macro would be:


sem_wait(sem_mutex_t); while(isBufferEmpty(buffer_t)); bufferRead(buffer_t, p); printf("\tpop %d, %d\n", p.x, p.y);

This magic is not a magic anymore once you understand. But this is not a real lambda expression. Now, let’s extend the usage of macro to achieve a true lambda expression. You might know there is something called nested function in C. Thus, one can define a local function in a function. There are three different types of lambda we can do in C.

Explicit type and argument:

The first type is the general form which can provide flexibility of code but long.

my_qsort(values, 6, (const void * a, const void * b) {return (*(int*)a - *(int*)b);});
#define my_qsort(value, size, lambda_function) do {\
    int compare_ lambda_function;\
    qsort(value, 6, sizeof(int), compare_);\
}while(0)

Implicit type with explicit argument:

And the second expression is that only pass void pointer argument(it can be any type and structure) without declaration of argument.

my_qsort(values, 6, a, b, return (*(int*)a - *(int*)b););
#define my_qsort(value, size, arg1, arg2, lambda_function) do {\
    int compare_ (const void * arg1, const void * arg2) {lambda_function;};\
    qsort(value, 6, sizeof(int), compare_);\
}while(0)

Implicit type and argument:

And the third expression is hiding the arguments’ name in the macro and let the code focus only on the main part of the code. This is my favorite one since the code is clean and neat. There is no need to mess up main code with unnecessary explicit arguments naming.

my_qsort(values, 6, return (*(int*)a - *(int*)b););
#define my_qsort(value, size, lambda_function) do {\
    int compare_ (const void * a, const void * b) {lambda_function;};\
    qsort(value, 6, sizeof(int), compare_);\
}while(0)

Now, here is a question. Even we use nested function, we define the same name compare_ in the macro, and we all know that we can not define the same function name twice. Is this gonna be a problem? The answer is absolutely no. Because here we have a do-while to escape the scope problem. The scope inside do-while is different from outside. If you use ccr to expand the source file you will see the result as show in follow. There is a do-while to embrace the inner code. Even the function name compare_ are the same, since they are in different scope, it is fine here. And thus, we success to have a lambda expression in C.

ccr-macro-lambda

At the end, I know some of you may say “hey! this is not lambda, it’s just macro+nested function”. What I proposed here is just a solution for standard C to have a lambda-like expression. By adopting this writing style, library can provide a easier interface for user to use. I do believe this would be popular in the future and widely use in every C code, especially those who care about performance such as embedded system. The sample code is on github. If you are interested in it and want to use it, feel free to use it and make your code pretty. 😀

*nested function: This is supported by GNU gcc, but not llvm-gcc on MAC.

Sample Code: https://github.com/MedicineYeh/C-Lambda-and-Callback

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s