Anything you can do with closures you can also do with objects, and vice versa. At least if no static type system gets in the way. They two concepts are fundamentally equivalent.
Before Java got closures, it was normal to implement callbacks as anonymous classes with a single method, which looked like:
interface Callback {
void call(int data);
}
int captured = 42;
functionWithCallback(
// anonymous class captures referenced arguments by value
new Callback() {
void call(int argument) {
System.out.println("got argument=" + argument
+ " and captured=" + captured);
}
}
)
A couple of years ago I wrote a blog post on implementing an object system via closures in JavaScript, based on the idea that you can express object property access object.method_or_field
as a function call object_like_closure("method_or_field")
.
Representing closures in C
Under the hood, objects and closures are generally implemented similarly. Both need to reference some runnable code and need a structure for the owned data. In C, a data structure for a closure/object might look like:
struct CodeAndData {
void (*code)(void* data, int argument);
void *data;
}
To call this closure, it is necessary to give it access to the data:
struct CodeAndData closure = ...;
closure.code(closure.data, 123);
To create a closure, we must provide an appropriate function. The equivalent to the above Java code would be:
void functionWithCallback(CodeAndData callback);
static void callback_code(void* data, int argument) {
int captured = *(int*) data; // cast the data pointer
printf("got argument=%d and captured=%d\n", argument, captured);
}
int captured = 42;
CodeAndData callback = {
.code = callback_code,
.data = &captured, // manually capture relevant data
};
functionWithCallback(callback);
So it is possible to implement closures even in languages that do not support them natively, as long as we have function pointers and a flexible type system.
As a practical example of this pattern, consider the qsort() function in the C standard library. It implements a quicksort algorithm over arbitrary data, and uses a function pointer to compare elements.
The basic qsort() function has signature
void qsort(void *ptr, size_t count, size_t size,
int (*comp)(const void *, const void *) );
Here, the function pointer only receives pointers to the elements it is currently comparing. If it needs access to external data, it would have to use global variables.
The qsort_s()
(C11) or qsort_r()
(POSIX) function adds a data pointer, which will be passed to the callback as a third argument:
errno_t qsort_s(void *ptr, rsize_t count, rsize_t size,
int (*comp)(const void *, const void *, void *),
void *context );
This is exactly the solution you proposed with setTimeout()
. This is also equivalent to my CodeAndData
struct, except that this function uses separate variables for the function pointer and the data instead of a single struct.
Representing objects in C
On this level, it's worth noting that functions and objects are often implemented slightly differently: a core feature of objects is that they are recursive, so that a method can call other methods on the same objects. If our CodeAndData
were more object like, then its definition and the calling convention would change to:
struct CodeAndData; // forward declaration
struct CodeAndData {
void (*code)(CodeAndData* self, int argument);
// ^^^^^^^^^^^^^^^^^
void *data;
}
And instead of a single function-pointer and a data-pointer, it's common to pass a vtable pointer that can contain multiple methods with different signatures, and to embed the captured data directly into the structure. For example, the C++ class
class Foo {
int some_field;
int another_field;
virtual int methodA() const;
virtual void methodB(int);
}
...
Foo* f = ...;
f->methodB(123);
Would probably be layouted like
struct Foo;
struct VtableFoo {
int (*methodA)(Foo const* self);
void (*methodB)(Foo* self, int);
}
struct Foo {
VtableFoo const* vtable;
int some_field;
int another_field;
}
...
Foo* f = ...;
f->vtable->methodB(f, 123);
A possible methodB implementation that makes use of this ability to invoke methods might look like:
void Foo_methodB(Foo* self, int argument) {
int result = self->vtable->methodA(self);
self->some_field = result + argument;
}
As a practical example, this pattern is used throughout the Linux kernel, in particular for the file system (see fs.h
). An open file might be a network socket, or a pipe, or a file on an Ext4 filesystem, or many other things. To manage this flexibly, there is a file_operations
struct with tons of function pointers, and the file
struct points to matching file_operations:
struct file_operations {
ssize_t (*read)(struct file*, char*, size_t, loff_t*);
ssize_t (*write)(struct file*, const char*, size_t, loff_t*);
...
};
struct file {
const struct file_operations* f_op;
...
};
i
moves around. Anonymous methods are a true boon. But implicit variable capture is an abomination against all things - ...in my view!i
is not in scope of the inner method, as the callback fromsetTimeout
is asynchronous and occurs after the scope of 'i' has already closed.for
loop feature by using only "while" loops, or "goto" loops. The point of such a feature is that it allows to introduce more structure, or more explicit and useful structure into programs.