Skip to content

Instantly share code, notes, and snippets.

@informatimago
Created June 24, 2021 17:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save informatimago/07594e34f917897dcab2cbea61644d82 to your computer and use it in GitHub Desktop.
Save informatimago/07594e34f917897dcab2cbea61644d82 to your computer and use it in GitHub Desktop.
The most complex C macros are those that are used to implement
alternative flow-control. For example, to iterate some data structure,
where you want to process an element with a block, or worse, when you
need multiple blocks, such as for an unwind-protect macro. The
current "state of the art" is to have the macro expand to a for() so
that it can be followed by a block that is executed according to your
expressions in the for(). But this is very limited, because of
restrictions on what can be done in the for expressions. Notably, you
cannot define loop variables of different types (and you cannot define
a type there either, such as struct to wrap multiple variables of
different types). And of course, this doesn't help when you need
several blocks.
If you want to replace a macro with a function, then you need to have
a way to inhibit the evaluation of a block, to leave it to the
function to decide when and how to evalute the block (and with what
parameters). This is why you need anonymous functions.
So instead of writing:
{DOLIST(element,list){
process(element);}}
you'd write:
dolist(list,^(Ref element){process(element);});
with:
#define DOLIST(elementvar,listexpr) \
Ref CS(dolist_current,__LINE__); Ref elementvar; \
for((CS(dolist_current,__LINE__) = (listexpr), \
elementvar = ((consp(CS(dolist_current,__LINE__))) \
?cons_car(CS(dolist_current,__LINE__)) \
:NULL)) ; \
consp(CS(dolist_current,__LINE__)) ; \
(CS(dolist_current,__LINE__) = cons_cdr(CS(dolist_current,__LINE__)), \
elementvar = ((consp(CS(dolist_current,__LINE__))) \
?cons_car(CS(dolist_current,__LINE__)) \
:NULL)))
inline dolist(Ref list,void (*thunk)(Ref)){
Ref current;
for(current=list;consp(current);current=cdr(current)){
Ref element=car(current);
thunk(element);}}
We can all agree that the solution with an inline function is clearer.
But as you can see, for that, you need the anonymous function.
While a macro such as DOLIST is feasible because it uses only one
block, it becomes even more difficult when you want something like:
UNWIND_PROTECT
{ do_something(); }
{ do_some_cleanup(); }
There you can try to play tricks, but you need multiple macros, and
the user must ensure they're called in the right order with the right
context. For example:
UNWIND(result){
UWBODY{
evale(subform,environment,results);}
UWCLEANUP{
dolist(subform,cleanup){
evale(subform,environment,results);}}}
I'll spare you the implementation. With anonymous functions, it would
be a trivial matter:
unwind(^(){ evale(subform,environment,results);},
^(){ dolist(subform,cleanup){
evale(subform,environment,results);} });
https://en.wikipedia.org/wiki/Blocks_(C_language_extension)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment