Created
June 24, 2021 17:41
-
-
Save informatimago/07594e34f917897dcab2cbea61644d82 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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