En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

lunes, 24 de diciembre de 2012

La maldición de la Callback de jade
cómo escribir una Callback en C

Hoy vamos a hablar de un tema un poco especial, las funciones callback. ¿Porqué especiales? Bueno, para empezar, en la biblia del C (el K&R) nunca se habla del argumento, así que un radical C-ista también podría afirmar que "¡las callback no existen!". En realidad, en el K&R se habla ampliamente de los tíos de las callback, es decir, los punteros a función (de los cuales las callback son un caso especial): entonces las callback existen.

No voy a escribir un tratado sobre el uso de las callback (ya puedo escuchar vuestros suspiros de alivio), ni voy a explicar cómo, cuándo, por qué, y (especialmente) si usarlas: hay muchas fuentes en la red interesantes y bien escritas. Eventualmente, sería bueno escribir un post sobre sus tíos, pero dado el tema muy complicado y el probable malestar estomacal que me vendría escribiéndolo, lo aplazo a una fecha futura (para ser claros: incluso Kernighan y Ritchie hablando de Pointers to Functions han titulado el capítulo 5.12 del K&R "Complicated Declarations", y si eran complicadas para ellos...).

¿De qué vamos a hablar entonces? Bueno, yo (como muchos otros, supongo) he usado muchas veces las callback, y he leído código que las usaba (código que era, en la mayoría de los casos, ilegible y difícil de interpretar en relación directamente proporcional a la frecuencia de utilización de las callback, sigh). Y bien, hasta el día en el que escribí una aplicación completa (o sea he escrito, además de la callback, la función a la que pasarla) no me he dado cuenta de algunos detalles ocultos. Por supuesto, si para vosotros las callback no tienen detalles ocultos, podéis dejar de leer lo siguiente y nos vemos en el próximo post.

¿Todavía estáis aquí? Vale, antes de empezar vamos a ver una definición de las callback tomada textualmente de Wikipedia: "una función que se usa como argumento de otra función", y yo agregaría: "y, con frecuencia y/o normalmente, la función llamante es una función de librería". El ejemplo más clásico y conocido citado en literatura es relativo al uso de qsort():
// funzione callback de comparación para la qsort()
static int cbCmpFunc(const void *elem_a, const void *elem_b)
{
    return *(int*)elem_a > *(int*)elem_b;
}

// main
int main(void)
{
    int array[] = {34,12,32,9,10,72,82,23,14,7,94};
    int nelems = sizeof(array) / sizeof(int);

    // ejecuto sort array con qsort()
    qsort(array, nelems, sizeof(int), cbCmpFunc);

    // enseño resultados
    int i;
    for (i = 0; i < nelems; i++)
        printf("%d - ", array[i]);
    printf("\n");

    // salgo
    return 0;
}
La qsort() es una función de libc que implementa el algoritmo de ordenación quicksort, y requiere una función de callback que especifique el tipo de ordenación deseado. Estamos, por tanto, en un caso clásico: qsort() es una función de libreria, y nosotros, a nivel local, la usamos pasándole una callback que, en nuestro ejemplo, se utiliza para ordenar en orden ascendente los números del array.

Y ahora vamos al grano: en otros tiempos, cuando no era fácil como ahora encontrar documentación y ejemplos, se me ocurría leer y escribir código como el que se muestra arriba y me preguntaba (quizás sólo inconscientemente): "pero de donde salen los parámetros elem_a y elem_b de cbCmpFunc()? " y otra vez: "Si llamo a la callback y no le paso explícitamente parámetros, cómo funciona todo?" Bueno, como descubrí más tarde, yo estaba pensando invirtiendo la relación causa-efecto: no era yo quien llamaba la callaback, ¡era la qsort() que la llamaba! Vale, me avergüenza un poco tener que contar una conclusión tan perogrullada, pero, efectivamente, lo comprendí a fondo solo el día en que necesité escribir una función de librería que utilizaba una callback. Claro, ahora con toda la información en la red es mucho más fácil ...

Así que para aclarar, vemos un ejemplo completo (NOTA: el ejemplo se podría escribir todo en un file, pero, como se muestra en los comentarios apropiados debería ser dividido en tres file en un proyecto real):
/* parte que tendría que estar en el file mysort.h
*/
// prototipos para mySort()
typedef int (*mycallback)(int, int);
void mySort(int *array, int nelems, mycallback cmpFunc);

/* parte que tendría que estar en el file mysort.c
 */
// mySort() - función de sort que usa el algoritmo bubblesort
void mySort(int *array, int nelems, mycallback cmpFunc)
{
    // loop sobre todos los elementos de array
    while (nelems > 0) {
        // loop interno con decremento longitud
        int i;
        for (i = 0; i < (nelems -1); i++) {
            // ejecuto callback de comparación
            if (cmpFunc(array[i], array[i + 1])) {
                // eseguo swap di array[i] e array[i+1]
                int temp = array[i];
                array[i] = array[i + 1];
                array[i + 1] = temp;
            }
        }

        // decremento nelems
        nelems--;
    }
}

/* parte que tendría que estar en el file mymain.c
*/
// cbCmpFunc() - función de comparación
static int cbCmpFunc(int elem_a, int elem_b)
{
    return elem_a > elem_b;
}

// main
int main(void)
{
    int array[] = {34,12,32,9,10,72,82,23,14,7,94};
    int nelems = sizeof(array) / sizeof(int);

    // ejecuto sort array
    mySort(array, nelems, cbCmpFunc);

    // enseño resultados
    int i;
    for (i = 0; i < nelems; i++)
        printf("%d - ", array[i]);
    printf("\n");

    // salgo
    return 0;
}
Como podéis ver es muy similar al ejemplo de la qsort(), pero, en lugar de utilizar una función de la libc, se utiliza una función de ordenación escrita ad-hoc, la mySort(). Para este ejemplo he utilizado, para no complicar demasiado el código, un algoritmo de tipo bubblesort, que es (lo sé) un asco, pero, para hacer una prueba sencilla es mas que suficiente. Como notaréis es la mySort(), la que se encarga de escribir los argumentos para la callback, procesando correctamente los otros parámetros que se pasan (array y nelems), y así, mágicamente, aparecen los valores en elem_a y elem_b.

¿Qué os parece? Sí, un poco ingenuo (tal vez), pero que levante la mano quien nunca tuvo dudas en su historia de programador (uhmm... veo pocas manos levantadas). Y si este post ha servido para ampliar el conocimiento sólo a uno de mis lectores estoy super-feliz. Y para los otros, aquellos que ya lo sabían todo de las callback: porque habéis llegado igualmente al final del post?

Hasta el próximo post.

4 comentarios:

  1. Si te sirve de algo te diré un par de cosas, la primera es que mientras leía tu primer ejemplo tuve la misma duda que tú. La segunda es que eres el único que he encontrado que explica esto con claridad.

    ResponderEliminar
  2. Gracia enhiro! Si te interesa he vuelto sobre el mismo argumento en otro post:
    http://artcprogramming-es.blogspot.com.es/2013/05/la-maldicion-de-la-callback-de-jade-ii.html

    ResponderEliminar
  3. Agradecerte, que yo tenia las mismas dudas y me ha servido para aclarar conceptos.

    ResponderEliminar
    Respuestas
    1. Gracias a ti! Si te interesa he vuelto sobre el mismo argumento en otro post:
      http://artcprogramming-es.blogspot.com.es/2013/05/la-maldicion-de-la-callback-de-jade-ii.html

      Eliminar