sábado, 14 de junio de 2014

Manejando Funciones como Objetos en Scala



En Scala las funciones también son objetos por tanto es posible pasar funciones como argumentos, guardarlas en variables y devolverlas como respuesta de otras funciones. Esta habilidad para manipular funciones como valores es una de las piedras angulares de la programación funcional.

Como ejemplo muy simple de porqué puede resultar útil el uso de funciones como valores, consideremos una función temporizador cuyo propósito sea realizar alguna acción una vez por segundo. ¿Cómo le pasamos la acción a realizar? lógicamente, como una función.

En el siguiente programa, la función temporizador se llama unaVezPorSegundo, y toma una función como argumento. El tipo de esta función se escribe () => unit y es el tipo de todas las funciones que no tienen parámetros de entrada y que no devuelven nada (el tipo unit es similar a void en C/C++). El método main de este programa simplemente llama a la función temporizador para que repita cada segundo la impresión por pantalla de la frase “el tiempo pasa volando”.

object Temporizador {
      def unaVezPorSegundo(repite : () => unit) {
            while (true) { repite(); Thread sleep 1000 }
      }

      def elTiempoVuela() {
            println("el tiempo pasa volando...")
      }

      def main(args: Array[String]) {
            unaVezPorSegundo(elTiempoVuela)
      }
}

Aunque este programa es fácil de entender, se puede refinar un poco. Antes de nada, se ve que la función elTiempoVuela solo se ha creado para ser pasada posteriormente como argumento al método unaVezPorSegundo . Tener que dar nombre a esa función, que sólo se usa una vez, parece innecesario, y de hecho estaría bien poder construirla justo cuando se le pasa a unaVezPorSegundo. En Scala esto es posible usando funciones anónimas, que son exactamente eso: funciones sin nombre. La versión revisada de nuestro programa temporizador utilizando una función anónima en vez de elTiempoVuela queda así:

object Temporizador {

      def unaVezPorSegundo(repite: () => unit) {
            while (true) { repite(); Thread sleep 1000 }
      }

      def main(args: Array[String]) {
            unaVezPorSegundo(() =>
                  println("el tiempo pasa volando..."))
      }
}

La presencia de una función anónima en este ejemplo, se revela por la flecha ‘ => ’ que separa a la lista de argumentos de la función, del cuerpo de la misma. En este ejemplo la lista de argumentos es vacía, como indican los paréntesis vacíos a la izquierda de la flecha. El cuerpo de la función es el mismo que el de elTiempoVuela antes.

Otra característica es que una función puede ser devuelta como resultado de otra función. Esto es sumamente útil y nos permite currificar funciones. Pero que es currificar? En resumen "Si ajustas algunos argumentos, tendrás una función de los argumentos restantes".

Veamos un ejemplo:

object CurryingTest {
 
      def multiplicar (nro1: Int) : (Int)=> Int = (nro2: Int) => nro1 * nro2
                                                 
      def doble(nro: Int) = multiplicar(2)(nro)    
 
      doble(3)         //> res0: Int = 6
}

Pero podemos utilizar el azúcar sintáctico:

object CurryingTest {
      def multiplicar (nro1: Int)(nro2: Int) = nro1 * nro2
     
      def doble(nro: Int) = multiplicar(2)(nro)    
 
      doble(3)       //> res0: Int = 6
}

Para los curiosos el nombre "currificar", acuñado por Christopher Strachey en 1967, es una referencia al lógico Haskell Curry.

Las funciones son muy importantes y modelarlas como objetos nos hace la vida más fácil.