Closures en C y Objective-C

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🙂

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: