cu/clos.h: Closures
[Basics]

Data Structures

struct  CLOS_t

Defines

#define cu_clos_def(PREFIX, PROTOTYPE, STRUCT_BODY)   ...
#define cu_clos_dec(PREFIX, PROTOTYPE, STRUCT_BODY)   ...
#define cu_clos_fun(PREFIX, PROTOTYPE)
#define cu_clos_edec(PREFIX, PROTOTYPE, STRUCT_BODY)   ...
#define cu_clos_efun(PREFIX, PROTOTYPE)
#define cu_clos_self(PREFIX)   ...
#define cu_call(clop, args...)   ...
#define cu_call0(clop)   ...
#define cu_prot(result_type, arg_decl_list...)   ...
#define cu_prot0(result_type)   ...
#define cu_clop(clop, result_type, arg_decl_list...)   ...
#define cu_clop0(clop, result_type)   ...
#define cu_clop_null   ...
#define cu_clop_is_null(clop)   ...
#define cu_clop_def(clop, result_type, arg_decl_list...)
#define cu_clop_def0(clop, result_type)
#define cu_clop_edef(clop, result_type, arg_decl_list...)
#define cu_clop_edef0(clop, result_type)

Typedefs

typedef IMPLEMENTATION_DEFINED CLOS_clop_t

Functions

CLOS_clop_t CLOS_prep (CLOS_t *clos)
CLOS_clop_t CLOS_ref (CLOS_t *clos)

Detailed Description

This submodule implements a efficient and partly type-safe encapsulation of closures. The capsule contains a function pointer and an optional context. This achieves two goals. It standardises the idiom of passing a function pointer and a void * of client data to functions, and it allows passing functions without the client data to the same function.

Note:
With a little knowledge of the architecture it is possible to encapsulate a closure as a normal function pointer. This is done in cu/i386/clos.h. However, that seems to be highly inefficient, maybe because the processor must jump outside the text section of the program. Therefore, this platform-independent implementation is used, even if it is less convenient.

The closures are implemented mostly with macros and some type hacks, so here is a usage-based documentation instead of the usual function-centered documentation.

Closure Pointers

A closure pointer (clop) is the modified function pointer type, where the closure data structure is abstracted out. A clop f is declared with

 cu_clop(f, result_t, arg1_t, arg2_t, ..., argN_t);
 cu_clop0(f, result_t);  // if no arguments

The special 0-argument form is needed due to limitations of the preprocessor. The closure is called with

 result = cu_call(f, x1, ..., xN);
 result = cu_call0(f);  // if no arguments

Examples

As a callback for buffered reading, the type reader_t defined as

 typedef cu_clop(reader_t, ssize_t, void *buf, ssize_t len);

may be handy. A function to iterate over the character sequence produced by such a function may look like this:

 void reader_iter(reader_t reader, cu_clop(cb, void, char ch))
 {
     char buf[512];
     ssize_t n_read;
     while ((n_read = cu_call(reader, buf, 512)) > 0) {
         size_t i;
         for (i = 0; i < n_read; ++i)
             cu_call(cb, buf[i]);
     }
 }

A global variable,

 extern cu_clop(global_error_handler, void, char const *msg);

To call this, use

 cu_call(global_error_handler, "Some message");

Creating and Using Closures

In my experience, the most useful way to create closure pointers is from a static definition with closure data. This is done with

 cu_clos_def(PREFIX,
             cu_prot(result_t, arg1_t arg1, ..., argn_t argn),
     ( local0_t local_var_0;      // This is the body of a struct
       local1_t local_var_1;      // definition which is typedef'ed as
       ... ))                     // PREFIX_t.
 {
     cu_clos_self(PREFIX);  // Declare and set the self variable.
     // Function definition.  The special variable self is a
     // pointer to the closure struct data, so here you can
     // use self->local_var_0, etc.
 }

which creates the type PREFIX_t which is a typedef for a structure with the given local variables. An instance of the closure can now be declared as PREFIX_t clos, local variables assigned with clos.local_var_n = ..., and the closure referred to by PREFIX_prep(&clos) which returns a cu_clop(, result_t, arg1_t, ..., argn_t) pointer. PREFIX_prep will also initialise the function pointer which is hidden at the start of PREFIX_t.

For closures taking no arguments, use cu_prot0 instead of cu_prot above.

Examples

The following function sums up the value slots of a cucon_pmap_t, assuming they contain inlined double values:

 cu_clos_def(sum_callback,
             cu_prot(void, void const *key, void *value),
     ( double sum; ))
 {
     cu_clos_self(sum_callback);
     self->sum += *(double *)value;
 }

 double
 sum_double_valued_pmap(cucon_pmap_t pmap)
 {
     sum_callback_t cb;
     cb.sum = 0.0;
     cucon_pmap_iter_mem(pmap, sum_callback_prep(&cb));
     return cb.sum;
 }

If you want to return the closure from the function it is created you better allocate it dynamically:

 typedef int cu_clop0(counter_t);

 cu_clos_def(counter_callback,
     ( int value; ))
 {
     cu_clos_self(counter_callback);
     return self->value++;
 }

 counter_t
 make_counter(int start)
 {
     counter_callback_t *cb = cu_gnew(counter_callback_t);
     cb->value = start;
     return counter_callback_prep(cb);
 }

The counter can now be used as

 counter_t cnt = make_counter(10);
 int i = cu_call0(cnt);   // i = 10
 int j = cu_call0(cnt);   // j = 11

Wrapping Normal Functions

Todo:
Write this.

Summary

Closure pointers:

 cu_clop(CLOSURE_POINTER, RESULT_TYPE, ARG1_TYPE, ..., ARGn_TYPE);
 cu_clop(CLOSURE_POINTER, RESULT_TYPE);
 RESULT_TYPE result = cu_call(CLOSURE_POINTER, ARG1, ..., ARGn);
 RESULT_TYPE result = cu_call0(CLOSURE_POINTER);

Real closures: Define in static file scope with

 cu_clos_def(CLOS_NAME,
             cu_prot(RESULT_TYPE, ARG1_TYPE a1, ..., ARGn_TYPE am),
     ( LOCAL_VAR1_TYPE V1;
       ...;
       LOCAL_VARm_TYPE Vm; ))
 {
     cu_clos_self(CLOS_NAME);
     FUNCTION_BODY
 }

Use cu_prot0(RESULT_TYPE) for empty argument list. This can be converted to a closure pointer using the pattern

 CLOS_NAME_t CLOS;                  // type defined by cu_clos_def
 CLOS.V1 = ...;   // initialise variables
 ...;
 CLOS.Vm = ...;
 CLOSURE_POINTER = CLOS_NAME_prep(&CLOS);

Function adapters: TODO

Debugging

The extensive use of macros can make errors uninformative. Things to check:


Define Documentation

#define cu_call ( clop,
args...   )     ...

Calls clop with arguments args. If there are no argumets, use cu_call0 instead.

#define cu_call0 ( clop   )     ...

Calls clop with no arguments.

#define cu_clop ( clop,
result_type,
arg_decl_list...   )     ...

Expands to a declaration of clop as a closure pointer with result type result_type and argument declarations arg_decl_list which may optionally contain argument names. Use cu_clop0 if N is 0. You can add linkage or typedef in front of this declaration. When used in a cast, you can drop the clop, but leaving the comma, eg (cu_clop(, void *, void *))equal.

#define cu_clop0 ( clop,
result_type   )     ...

A 0-argument variant of cu_clop.

#define cu_clop_def ( clop,
result_type,
arg_decl_list...   ) 

Declare a static clop pointer and initialise it with a function definition whose body follow this macro invocation. Example:

 cu_clop_def(dump_int, void, int x)
 {
     printf("%d\n", x);
 }

 void
 dump_umap_keys(cucon_umap_t map)
 {
     cucon_umap_iter_keys(map, dump_int);
 }
See also:
cu_clos_def if you need closure context data
cu_clop_def0 for a 0-argument variant
#define cu_clop_def0 ( clop,
result_type   ) 

A 0-argument variant of cu_clop_def.

#define cu_clop_edef ( clop,
result_type,
arg_decl_list...   ) 

A variant of cu_clop_def which exports clop. Most useful if you also put a corresponding forward declaration in your header file:

 extern cu_clop(clop, result_type, arg_decl_list...)
#define cu_clop_edef0 ( clop,
result_type   ) 

A 0-argument version of cu_clop_edef.

#define cu_clop_is_null ( clop   )     ...

Check if clop is the dedicated invalid closure pointer.

#define cu_clop_null   ...

A dedicated invalid closure pointer. Can not be called. Preferably use cu_clop_is_null rather than == or != to check if a closure pointer holds this value.

#define cu_clos_dec ( PREFIX,
PROTOTYPE,
STRUCT_BODY   )     ...

Forward declaration of a static closure struct. The arguments are the same as cu_clos_def, but no function body shall follow. The function itself is defined with cu_clos_fun.

#define cu_clos_def ( PREFIX,
PROTOTYPE,
STRUCT_BODY   )     ...

Starts the definition of a static closure. It is followed by the actual function body in the usual form of a brace-enclosed block. The PROTOTYPE must be specified with cu_prot or cu_prot0, and must contain parameter names, which are local to the function body. STRUCT_BODY is the body of the closure struct, enclosed in parentheses. The function body should start with cu_clos_self(PREFIX) declaration, which makes sure a local variable self is declared and initialised to point to the closure struct.

The following example defines a closure for printing comma-separated integers.

 cu_clos_def(print_int, cu_prot(void, int x), (FILE *out; int done_cnt;))
 {
     cu_clos_self(print_int);
     fprintf(self->out, "%s%d", self->done_cnt++ == 0? "" : ", ", x);
 }

 int main()
 {
     print_int_t f;
     f.out = stdout;
     f.done_cnt = 0;
     cu_call(print_int_prep(&f), 4);
     cu_call(print_int_ref(&f), 5);
     cu_call(print_int_ref(&f), 6);
     fputc('\n', stdout);
     return 0;
 }
#define cu_clos_edec ( PREFIX,
PROTOTYPE,
STRUCT_BODY   )     ...

Forward declaration of an external closure struct. The arguments are the same as cu_clos_def, but no function body shall follow. The function itself is defined with cu_clos_efun.

#define cu_clos_efun ( PREFIX,
PROTOTYPE   ) 

Provide the function body of a closure which was forward declared with cu_clos_edec. See cu_clos_def for details on the function body.

#define cu_clos_fun ( PREFIX,
PROTOTYPE   ) 

Define the function body of a closure which was forward declared with cu_clos_dec. See cu_clos_def for details on the function body.

#define cu_clos_self ( PREFIX   )     ...

Use this in the start of a closure body to declare and assign a variable self as a pointer to a struct containing the local variables of the current closure.

#define cu_prot ( result_type,
arg_decl_list...   )     ...

This is used in some contexts to group macro arguments which specifies a prototype. It is needed when a variable-sized argument sequence is not at the end of a macro call, such as with cu_clos_def. Use cu_prot0 if there are no arguments.

#define cu_prot0 ( result_type   )     ...

A 0-argument variant of cu_prot.


Typedef Documentation

typedef IMPLEMENTATION_DEFINED CLOS_clop_t

A typedef emitted by the invocation of cu_clos_def with prefix CLOS. This is the closure pointer type.


Function Documentation

CLOS_clop_t CLOS_prep ( CLOS_t clos  ) 

Creates a closure pointer from a closure struct pointer. In the process, the private field of clos is initialised. Here CLOS is the prefix given as the first argument of the cu_clos_def definition.

CLOS_clop_t CLOS_ref ( CLOS_t clos  ) 

Returns the closure pointer of clos assuming it has been prepared in advance with CLOS_prep. Here CLOS is the prefix given as the first argument of a cu_clos_def definition.

Generated 2009-11-23 for culibs-0.25 using Doxygen. Maintained by Petter Urkedal.