Dos conceptos que son importantes en programación funcional, y que a primera vista no lo parecen, son currying y aplicación parcial de funciones. Ambos están ligados entre sí, pero no son lo mismo.
Por cierto, en Wikipedia usan currificación para referirse al currying. Aquí usaré el término original en inglés.
Currying
El término se refiere a tomar una función que toma n parámetros y transformarla en una serie de funciones que toman un número menor de parámetros. Aquí entran en juego los conceptos de funciones como ciudadanos de primera clase y funciones de orden superior:
Cuando definimos una función que toma argumentos, los declaramos casi siempre de la siguiente forma:
// Java private boolean areEqual(int x, int y) { return x == y; }
// Scala def areEqual(x: Int, y: Int) = x == y
Y los pasamos a la función así:
areEqual(2,3)
Podemos decir que la función areEqual tiene aridad 2; es decir: necesita 2 parámetros para poder ser calculada. Además, regresa un valor de tipo booleano.
Cuando aplicamos currying a areEqual, lo que obtenemos es una función que toma un argumento int y regresa otra función que toma un argumento int y que a su vez regresa un valor de tipo booleano.
Pongámoslo así . La => indica “función”. A la izquierda van los parámetros y lo que está a la derecha es el valor de retorno:
- areEqual = (Int, Int) => Boolean (Aridad 2)
- currying areEqual = Int => (Int => Boolean) (Aridad 1)
¿Cómo se hace el currying? Depende del lenguaje, pero básicamente es posible si éste permite expresiones lambda.
// Scala def areEqual(x: Int) = (y: Int) => x == y // También es posible así: def areEqual(x:Int)(y:Int) = x == y
Si revisamos el tipo de la función, encontramos que son lo mismo. Ignoren aquí el subguión que aparece. Lo explico más adelante:
scala> def areEqual(x:Int)(y:Int) = x == y areEqual: (x: Int)(y: Int)Boolean scala> :t areEqual _ Int => (Int => Boolean) scala> def ae2(x: Int) = (y:Int) => x == y ae2: (x: Int)Int => Boolean scala> :t ae2 _ Int => (Int => Boolean)
C# también permite currying:
public static Func<int,bool> areEqual(int x) { return y => x == y; } Console.Write(areEqual(2)(3)); // False
Hacer currying en Java es técnicamente posible, pero hay que hacerlo por medio de interfaces, y la cantidad de código que se tiene que escribir es exagerada. Alguien acá se tomó la molestia de poner un ejemplo:
http://stackoverflow.com/questions/6134278/does-java-support-currying
Aplicación parcial de funciones
Si una función tiene aridad n y la llamamos con m parámetros, donde m < n, normalmente el compilador nos dirá que nos faltan argumentos.
En el ejemplo anterior, si llamamos a areEqual con un parámetro nada más, el compilador nos dice que necesitamos 2 argumentos. Todo normal.
Aplicar parcialmente una función significa precisamente eso que el compilador nos diría que es un “error”: aplicamos una función a un número menor de parámetros que los que espera, y lo que obtenemos es una función que recibe los parámetros que hacen falta y regresa un valor del tipo que devuelve la función original. El “truco” consiste en asignar esa función que se regresa a un identificador (como una variable), o aplicarla directamente.
Reusemos areEqual. Esta vez, la llamaremos con un parámetro nada más y asignaremos el resultado a un identificador. Aquí viene la explicación sobre el subguión que prometí más arriba: en Scala, necesitamos poner un subguión en el lugar de los parámetros que faltan para indicar que estamos aplicando parcialmente la función:
// Scala /* Sin usar subguión, el compilador nos indica que faltan argumentos, y que debemos agregarlo si queremos tratar la acción como una función parcialmente aplicada */ scala> val f = areEqual(2) <console>:8: error: missing arguments for method areEqual; follow this method with `_' if you want to treat it as a partially applied function val f = areEqual(2) /* Ahora con subguión. Aplicamos parcialmente areEqual, y el resultado lo asignamos a f. f es una función que recibe un entero y regresa un boolean. */ scala> val f = areEqual(2) _ f: Int => Boolean = <function1> // Ahora podemos usar f scala> f(3) res0: Boolean = false scala> f(2) res1: Boolean = true
Cabe mencionar es más fácil aplicar parcialmente una función en Scala si la función tiene múltiples listas de parámetros. En nuestro ejemplo, areEqual está definida así:
def areEqual(x: Int)(y: Int) = x == y
x y y están en diferentes lugares de parámetros. Si por el contrario la función estuviera definida así:
def areEqual(x: Int, y: Int) = x == y
Tenemos que hacer lo siguiente para aplicarla parcialmente:
scala> val f = areEqual(2,_:Int) f: Int => Boolean = <function1> scala> f(3) res0: Boolean = false scala> f(2) res1: Boolean = true
Es decir: hay que especificar el tipo de datos del parámetro que no estamos incluyendo.
Muy bonito… ¿y?
Es fácil confundir currying con aplicación parcial de funciones. Estrictamente hablando, ambos toman una función de aridad n y regresan una función de aridad menor. La diferencia es que en aplicación parcial de funciones se fijan con valores específicos algunos parámetros.
Lo anterior nos permite abstraer conceptos de forma mucho más sencilla. Esto se hace importante cuando trabajamos con funciones de orden superior y comenzamos a pasar funciones por aquí y por allá.
Como nota, en Haskell todas las funciones reciben exactamente un parámetro. Lo que sucede cuando se crea una con varios parámetros es que Haskell hace currying y crea una serie de funciones, cada una recibiendo un parámetro:
-- Esto es Haskell -- Ejemplo tomado de http://aprendehaskell.es/content/OrdenSuperior.html -- Función que multiplica 3 números multThree :: (Num a) => a -> a -> a -> a multThree x y z = x * y * z
Para fines prácticos diríamos que multThree toma 3 parámetros y regresa un valor numérico, pero lo que realmente sucede es que el primer parámetro se aplica a multThree, y esto regresa una función que toma un parámetro (el segundo), y regresa una función que aplica el tercer parámetro y regresa el resultado.
En cuanto a aplicación parcial, como ya mencioné, el uso más lógico es fijar uno o varios de los parámetros de una función en vez de estarla llamando con el mismo parámetro repetido cada vez.
Por ejemplo, hace poco tuve que crear un parser para cierto formato de documentos. Al momento de extraer información, tenía que escribir en 3 diferentes logs dependiendo el tipo de información que extrajera o si encontraba algún error. Tengo una función simple (copiada de internet) que escribe lo que envíes en el archivo que indiques:
private def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) { val p = new java.io.PrintWriter(f) try {op(p)} finally {p.close()} }
En vez de estar alternando entre 3 archivos, lo que hice fue crear 3 funciones que aplicaban parcialmente printToFile con el primer parámetro (el archivo) previamente determinado:
val logError = printToFile(new File(errorfile)) _ val logInfo = printtoFile(new File(infofile)) _ val logDetails = printtoFile(new File(detailsfile)) _
Al momento de usarlas, lo único que tengo que pasar es el parámetro faltante: una función que toma un PrintWriter y regresa nada relevante (Unit):
/* Extrae la información y guarda cada parte en el archivo usando p.println */ logInfo(p => { extractInfo(something) map {p.println} })
Todo lo anterior se vuelve sumamente importante en programación funcional. Por mencionar un ejemplo, el State Monad (un Monad que permite guardar y modificar estados) se compone de un tipo de datos parcialmente aplicado. Si no se conoce el concepto, es muy fácil perderse y confundirse.
Recuerden que este paradigma no es necesariamente mejor que otros. Simplemente es una forma diferente de hacer las cosas, con sus ventajas y desventajas.
Java 8
Al parecer, Java 8 soportará muchos conceptos de programación funcional. Acá algunas de las mejoras a este lenguaje:
http://www.javaworld.com/javaworld/jw-07-2013/130725-love-and-hate-for-java-8.html
Por tanto, no está de más aprenderlos para poder usarlos, aún cuando no se usen lenguajes 100% funcionales.
RT @medinamanuel: Nuevo post en ¡Un mexicano en Japón! 「Currying y aplicación parcial de funciones」 http://t.co/xPFswyZbqK
escrive de Japon a nadie le importa la informatica.Que abandonado tienes el blog si no loquieres hacer mejor no hagas nada.
Mas respeto hombre, a mi me interesa mucho su blog de japon, pero cosas como estas tambien me interesan, aunque les entienda poco y sea un amateur xD
No comparto la opinión de mgt, por favor sigue escribiendo de lo que tu gustes.
Saludos.
En que tipo de situaciones usarias scala u otro lenguaje funcional? no veo muchos proyectos por ahi que hagan uso de scala, cual es su nicho de los lenguajes funcionales?.
Saludos.
En sí, no hay una situación específica para usar programación funcional. Algunos desarrolladores de aplicaciones reales que han usado lenguajes funcionales como F# comentan que la cantidad de bugs que salen de una aplicación creada con programación funcional son infinitamente menores que en lenguajes tradicionales. La razón principal, argumentan, es la minimización del uso de estados.
Aunque la programación funcional tiene muchos años, apenas ha tenido un repunte últimamente gracias al poder que tienen las computadoras ahora. Sin embargo, la transición desde programación estructurada o programación orientada a objetos no es tan sencilla, por lo que muchas empresas ni se molestan en ver qué se puede hacer.
En lo personal, Scala me parece una buena opción cuando hay que hacer refactoring de código de Java. El punto “malo” de Scala (o de Haskell, para tal efecto), además del cambio de paradigma, es que la sintaxis no es para nada amigable al principio. La curva de aprendizaje es muy pronunciada y no cualquiera se anima a dedicarle tiempo.
Saludos.
Manuel he llegado hace muy poco a tu blog, a través de los post de programación funcional.
Son realmente buenos, tremendamente didácticos.
Los he leído todos del tirón.
Ahora estoy con los de Japón y otros más recientes.
Enhorabuena gente como tú no abunda.
Y muchas gracias por compartir.
Muchísimas gracias por leerme, y por tu amable comentario.
Me da gusto que los posts de programación funcional le sean de utilidad a alguien. Luego me pasa que algo me apasiona, escribo al respecto, pero parece que no hay gente interesada en lo mismo.
Aquí sigo escribiendo en el blog, y seguiré hasta que el cuerpo y la mente aguanten.
Saludos.