Patterns in static

Apophenia

Implementation of optional arguments

Optional and named arguments are among the most commonly commented-on features of Apophenia, so this page goes into full detail about the implementation.

To use these features, see the all-you-really-need summary at the Designated initializers page. For a background and rationale, see the blog entry at http://modelingwithdata.org/arch/00000022.htm .

I'll assume you've read both links before continuing.

OK, now that you've read the how-to-use and the discussion of how optional and named arguments can be constructed in C, this page will show how they are done in Apophenia. The level of details should be sufficient to implement them in your own code if you so desire.

There are three components to the process of generating optional arguments as implemented here:

None of these steps are really rocket science, but there is a huge amount of redundancy. Apophenia includes some macros that reduce the boilerplate redundancy significantly. There are two layers: the C-standard code, and the script that produces the C-standard code.

We'll begin with the C-standard header file:

#ifdef APOP_NO_VARIADIC
void apop_vector_increment(gsl_vector * v, int i, double amt);
#else
void apop_vector_increment_base(gsl_vector * v, int i, double amt);
apop_varad_declare(void, apop_vector_increment, gsl_vector * v; int i; double amt);
#define apop_vector_increment(...) apop_varad_link(apop_vector_increment, __VA_ARGS__)
#endif

First, there is an if/else that allows the system to degrade gracefully if you are sending C code to a parser like swig, whose goals differ too much from straight C compilation for this to work. Just set APOP_NO_VARIADIC to produce a plain function with no variadic support.

Else, we begin the above steps. The apop_varad_declare line expands to the following:

typedef struct {
gsl_vector * v; int i; double amt ;
} variadic_type_apop_vector_increment;
void variadic_apop_vector_increment(variadic_type_apop_vector_increment varad_in);

So there's the ad-hoc struct and the declaration for the wrapper function. Notice how the arguments to the macro had semicolons, like a struct declaration, rather than commas, because the macro does indeed wrap the arguments into a struct.

Here is what the apop_varad_link would expand to:

#define apop_vector_increment(...) variadic_apop_increment_base((variadic_type_apop_vector_increment) {__VA_ARGS__})

That gives us part three: a macro that lets the user think that they are making a typical function call with a set of arguments, but wraps what they type into a struct.

Now for the code file where the function is declared. Again, there is is an APOP_NO_VARIADIC wrapper. Inside the interesting part, we find the wrapper function to unpack the struct that comes in.

\#ifdef APOP_NO_VARIADIC
void apop_vector_increment(gsl_vector * v, int i, double amt){
\#else
apop_varad_head( void , apop_vector_increment){
gsl_vector * apop_varad_var(v, NULL);
Apop_assert(v, "You sent me a NULL vector.");
int apop_varad_var(i, 0);
double apop_varad_var(amt, 1);
apop_vector_increment_base(v, i, amt);
}
void apop_vector_increment_base(gsl_vector * v, int i, double amt){
#endif
v->data[i * v->stride] += amt;
}

The apop_varad_head macro just reduces redundancy, and will expand to

void variadic_apop_vector_increment (variadic_type_variadic_apop_vector_increment varad_in)

The function with this header thus takes in a single struct, and for every variable, there is a line like

double apop_varad_var(amt, 1);

which simply expands to:

double amt = varad_in.amt ? varad_in.amt : 1;

Thus, the macro declares each not-in-struct variable, and so there will need to be one such declaration line for each argument. Apart from requiring declarations, you can be creative: include sanity checks, post-vary the variables of the inputs, unpack without the macro, and so on. That is, this parent function does all of the bookkeeping, checking, and introductory shunting, so the base function can just do the math. Finally, the introductory section will call the base function.

The setup goes out of its way to leave the _base function in the public namespace, so that those who would prefer speed to bounds-checking can simply call that function directly, using standard notation. You could eliminate this feature by just merging the two functions.

The m4 script

The above is all you need to make this work: the varad.h file, and the above structures. But there is still a lot of redundancy, which can't be eliminated by the plain C preprocessor.

Thus, in Apophenia's code base (the one you'll get from checking out the git repository, not the gzipped distribution that has already been post-processed) you will find a pre-preprocessing script that converts a few markers to the above form. Here is the code that will expand to the above C-standard code:

//header file
APOP_VAR_DECLARE void apop_vector_increment(gsl_vector * v, int i, double amt);
//code file
APOP_VAR_HEAD void apop_vector_increment(gsl_vector * v, int i, double amt){
gsl_vector * apop_varad_var(v, NULL);
Apop_assert(v, "You sent me a NULL vector.");
int apop_varad_var(i, 0);
double apop_varad_var(amt, 1);
APOP_VAR_END_HEAD
v->data[i * v->stride] += amt;
}

It is obviously much shorter. The declaration line is actually a C-standard declaration with the APOP_VAR_DECLARE preface, so you don't have to remember when to use semicolons. The function itself looks like a single function, but there is again a marker before the declaration line, and the introductory material is separated from the main matter by the APOP_VAR_END_HEAD line. Done right, drawing a line between the introductory checks or initializations and the main function can really improve readability.

The m4 script inserts a return function_base(...) at the end of the header function, so you don't have to. If you want to call the funtion before the last line, you can do so explicitly, as in the expansion above, and add a bare return; to guarantee that the call to the base function that the m4 script will insert won't ever be reached.

One final detail: it is valid to have types with commas in them—function arguments. Because commas get turned to semicolons, and m4 isn't a real parser, there is an exception built in: you will have to replace commas with exclamation marks in the header file (only). E.g.,

APOP_VAR_DECLARE apop_data * f_of_f(apop_data *in, void *param, int n, double (*fn_d)(double ! void * !int));

m4 is POSIX standard, so even if you can't read the script, you have the program needed to run it. For example, if you name it prep_variadics.m4, then run

m4 prep_variadics.m4 myfile.m4.c > myfile.c

Autogenerated by doxygen on Sun Oct 26 2014 (Debian 0.999b+ds3-2).