Closures en C y Objective-C

junio 30, 2010

Si, aunque intenteis leer otra vez el t铆tulo del post, vamos a hablar de, las tan de moda, closures para C. 馃檪

Sin duda, dada la naturaleza de C, se hace extra帽o que se a帽adan funcionalidades que normalmente se asocian a lenguajes mas modernos (o a煤n en evoluci贸n) como C#, Java o otros lenguajes de script como Python o Ruby

El nombre elegido para referirnos es “blocks”, y b谩sicamente ha sido un a帽adido de Apple al compilador GCC que usamos en Mac y al futuro CLang que corre sobre el tambi茅n futuro y prometedor LLVM.

Por el momento esta extensi贸n de C no es estandar, y dada la cerrada naturaleza de los est谩ndares de C es complicado que llegue a serlo. Pero, por otro lado, al estar implementado en compiladores como GCC o en el frontend de LLVM Clang, y adem谩s, al ser estos compiladores opensource y portables entre diferentes plataformas, presumiblemente esta extensi贸n se pueda utilizar sin problemas en el futuro, al igual que usamos ciertas extensiones propias de GCC que tampoco forman parte de los estandares de C.

Apple desarroll贸 los blocks como parte de la tecnologia “Grand Central Dispatch”. Esta tecnologia optimiza ciertos bloques de ejecuci贸n, y como podeis esperar, el “empaquetar” los bloques de ejecuci贸n usando esta extensi贸n del lenguaje facilita mucho la gesti贸n por parte del sistema operativo y la programaci贸n por parte de los desarrolladores.

Hasta ahora hemos estado hablando alegremente de closures, b谩sicamente los closures es una forma de encapsular un comportamiento bajo una variable. Usando esta variable podemos manejar el comportamiento (o funci贸n) de la misma manera que manejamos el resto de variables, esto es, por ejemplo, pasarlo como par谩metros a una funci贸n.

Como siempre, lo mejor es verlo con un ejemplo, antes de adentrarnos en C,vamos a ver un ejemplo en Javascript. Lo bueno de este lenguaje es que la sintaxis es muy sencilla y nos va a permitir ver el concepto mas f谩cilmente.

funcion_suma = function(a,b) { return a+b; }
funcion_resta = function(a,b) { return a-b; }

function opera(op1, op2, operacion) {
	return operacion(op1, op2);
}

opera(3, 2, funcion_suma);
  // Imprime 5
opera(3, 2, funcion_resta);
  // Imprime 1
opera(3, 2, function(a,b) {
	return a*b;
});
  // Imprime 6

En el ejemplo creamos dos variables asignadas a funciones, que realizan las diferentes operaciones, y estas variables se las pasamos a la funcion “opera”. Cuando desarrollamos la funcion “opera” no sabemos que operaci贸n vamos a realizar, dejamos esa puerta abierta para que cada cliente de la funci贸n aporte su propia funci贸n de operaci贸n. Como vemos en el 煤ltimo caso, podemos definir la propia funci贸n cuando construimos los par谩metros de la llamada.

La gran ventaja de esta t茅cnica es la extensibilidad, ya que dejamos total libertad para que el desarrollador aporte la funci贸n a la que se va a llamar. Este ejemplo es muy sencillo, pero al final del post veremos puntos de Cocoa donde se estan usando los blocks y veremos de forma mas clara en que son una ventaja.

Centrandonos ya en C, Apple ha escogido el simbolo ^ para marcar un bloque. Es curioso ya que este simbolo se utilizaba en otra extension de C++ por parte de Microsoft para marcar cierto tipo de variables cuando se desarrolla con Managed C++.

Por lo tanto, para definir un bloque en C, usamos el simbolo ^, seguido de la lista de par谩metros entre parentesis y despu茅s el bloque de codigo que conforma el bloque. Para definir el tipo del bloque usamos el tipo de retorno, el nombre del bloque entre parentesis y precedido de ^ y finalmente la lista de par谩metros entre parentesis. Aunque pensandolo bien todo este parrafo me lo podr铆a haber ahorrado, simplemente poniendo un ejemplo 馃槈

int (^funcion_suma)(int, int) = ^(int a, int b) { return a + b; };

Siguiendo la linea anterior, esta ser铆a la forma de declarar una variable de tipo “funcion_suma” al igual que haciamos en javascript. Como vemos, la sintaxis es realmente mas compleja que la de javascript.

int opera(int op1, int op2, int (^funcion_opera)(int, int)) { return funcion_opera(op1, op2); }

opera(3, 2, funcion_suma);
  // Imprime 5
opera(3, 2, ^(int a, int b) {
	return a * b;
})
  // Imprime 6

Y aqu铆 tenemos la anterior versi贸n de la funci贸n “opera” pero en C, como vemos el concepto es similar, pero con una sintaxis mas enrevesada.

Si habeis programado en otros lenguajes como Java, la ultima invocaci贸n os sonara a clases anonimas, y ciertamente se les parece bastante. Al igual que con estas clases los blocks tienen suelen ser un buen sustituto a la hora de crear un callback. Una ventaja con respecto a la forma tradicional de implementar dichos mecanismos, es que pueden acceder a las variables locales del entorno donde se estan ejecutando. Esto es bastante util en numerosas ocasiones, usando blocks nos ahorraremos tener que pasar dichos datos a la funcion u objeto que implemente el callback.

Para marcar que una variable va a ser accedida dentro de un bloque, usamos el modificador __block antes de la variable que va a ser accedida dentro del bloque. Como por ejemplo:


__block int multiplicador = 6;

opera(3, 2, ^(int a, int b) {
	return a * b * multiplicador;
})
  // Imprime 36;

A partir de iOS 4 en el iPhone y OSX 10.6 los bloques son una realidad en Cocoa y Cocoa Touch. Poco a poco van apareciendo m茅todos que soportan un bloque para indicar el comportamiento de una acci贸n.

Un ejemplo, sacado de la documentaci贸n de apple es m茅todo sortedArrayUsingComparator: del NSArray. El uso tradicional de este funcionalidad seria pasandole un puntero a funci贸n que resuelva la comparaci贸n, como podemos ver en sortedArrayUsingFunction:context:. Como hemos visto anteriormente, dada a la limitaci贸n de este m茅todo, Cocoa nos proporciona un puntero a nuestros datos, reflejado en el par谩metro context.

Si usamos bloques en este caso, nos saltamos esta limitaci贸n, y adem谩s, facilita la lectura del c贸digo ya que podemos poner la l贸gica del comparador en la misma llamada de la funci贸n.

Para finalizar el post, vamos a ver un ejemplo de una ordenaci贸n de un Array usando bloques y sin usarlos

Usando el m茅todo tradicional, la ordenaci贸n del array quedar铆a algo como:

NSInteger alphaComparator(id arg1, id arg2, void* arg3) {
  NSString *a = (NSString*)arg1;
  NSString *b = (NSString*)arg2;
  return strcmp([a cStringUsingEncoding:[NSString defaultCStringEncoding]],
				[b cStringUsingEncoding:[NSString defaultCStringEncoding]]);
}

int main (int argc, const char * argv[]) {
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  NSArray *stringsArray = [NSArray arrayWithObjects:
    @"Roberto",
	@"Alfonso",
	@"Ruben",
	@"Mariano",
    @"Juan",
	@"Antonio",nil];

  NSArray* sortedArray = [stringsArray
			sortedArrayUsingFunction:alphaComparator context:nil];
  for ( NSString* cad in sortedArray ) {
    NSLog(@"%@\n", cad);
  }

  return 0;
}

Por otro lado, usando bloques quedar铆a algo como:

iint main (int argc, const char * argv[]) {

  NSArray *stringsArray = [NSArray arrayWithObjects:
   @"Roberto",
   @"Alfonso",
   @"Ruben",
   @"Mariano",
   @"Juan",
   @"Antonio",nil];

  NSArray* sortedArray = [stringsArray sortedArrayUsingComparator:^(id str1, id str2) {
    NSString *a = (NSString*)str1;
	NSString *b = (NSString*)str2;
	return (NSComparisonResult)
			strcmp([a cStringUsingEncoding:[NSString defaultCStringEncoding]],
				  [b cStringUsingEncoding:[NSString defaultCStringEncoding]]);
  }];

  for ( NSString* cad in sortedArray ) {
	NSLog(@"%@\n", cad);
  }

  return 0;
}

Sin duda, bajo mi punto de vista la soluci贸n usando bloques es mucho mas elegante, pese a que el enfrentamiento inicial con los blocks pueda ser algo “dura” 馃榾

Como conclusi贸n, simplemente decir que es muy grato ver como se van a帽adiendo funcionalidades a nuestro lenguaje favorito y, de esta manera, adaptandolo un poco mas a los tiempos en los que vivimos 馃檪