A friend once asked me a question like this:
I’m a little confused about the purpose of
fis a handler function that gets called when the event is triggered, and
f_ctxis − according to the documentation – some pointer argument that gets passed to
fwhenever it gets called. Why do we need
This is a trick for low-level languages like C where functions are represented using a raw function pointer, which does not store an enclosing environment (sometimes called a context). It is not needed in higher-level languages with support for first-class functions, such as Python, as these languages allow functions to be nested inside other functions and will automatically store the enclosing environment within the function objects in a combination called a closure.
The need for an environment pointer
f_ctx arises when you want to write a function that depends on external parameters not known at compile time. The
f_ctx parameter allows you to smuggle these external parameters into
f however you like.
It might be best to illustrate this with an example. Consider a 1-dimensional numerical integrator like this:
This works fine if you know the complete form of the function
f ahead of time. But what if this is not the case – what if the function requires parameters? Say we want to calculate the gamma function using an integral:
integrand_1 there are only three ways to do this:
tinto a global variable, sacrificing thread safety. It would be bad to simultaneously call
gamma_functionfrom different threads as they will both attempt to use the same global variable.
Write raw machine code to create an integrand on the fly. This can be implemented in a thread-safe and reentrant manner, but it is both inefficient, unportable, and inhibits compiler optimizations.
However, if the numerical integrator were to be re-designed like this:
Then there is a much simpler solution that avoids all of these problems:
This is thread-safe, reentrant, efficient, and portable.
As mentioned earlier, this problem does not exist in languages like Python where functions can be nested inside other functions (or rather, it is automatically taken care of by the language itself):