========= Funciones ========= Esta el undécima parte del tour de Ceylon. En la parada anterior anterior hemos visto acerca de los paquetes y los módulos. Esta parada cubre los temas de clases de primero orden y de orden superior. ------------------------------------------ Clases de primer orden y de orden superior ------------------------------------------ Ceylon no es un lenguaje de programación funcional: debido a que tiene métodos que pueden tener efectos secundarios.Pero tiene algo en común con los lenguajes de programación funcional y es que permiten tratar a las funciones como valores, lo cual a los ojos de algunas personas hace del lenguaje algún tipo de híbrido. En verdad, esto de tratar funciones como valores en un lenguaje orientado a objetos no tiene nada nuevo, por ejemplo, Smalltalk es uno de los primeros y aun uno de los lenguajes de programación de objetos mas puro, fue construido en torno a esta idea. De cualquier manera, Ceylon, como smalltalk y un numero de otros lenguajes orientados a objetos, permite pasar a una función como un objeto y llevarlo a través del sistema. En esta parte, hablaremos acerca del soporte de Ceylon para funciones de primer orden y de orden superior. - El soporte a clases de primer orden significa la habilidad de tratar a funciones como valores, asignarla a variables y pasarlas como argumento. - Una función de orden superior es una función que acepta funciones como argumentos o devuelve una función. Esta claro que estas dos ideas van de la mano, así que usaremos el termino "funciones de orden superior" desde ahora en adelante. ----------------------------------- Representado el tipo de una función ----------------------------------- Ceylon es un lenguaje fuertemente tipado. Así que si vamos a tratar a las funciones como valores, la primera pregunta a contestar es: ¿Cuál es el tipo de una función? Necesitamos una manera de representar el tipo de retorno y los tipos de los parámetros de una función dentro del sistema de tipos. Recuerda que Ceylon, no tiene tipos de datos "primitivos". Un principio solido del diseño es que cada tipo deberá ser representado dentro del sistema de tipos como una declaración de una clase o una interfaz. En Ceylon, un simple tipo ``Callable`` abstrae todas las funciones. Su declaración es la siguiente: .. code-block:: ceylon shared interface Callable given Arguments satisfies Anything[] {} El parámetro de tipo ``Return`` representa el tipo de retorno de la función. El parámetro de tipo secuencial ``Arguments``, deberá ser una secuencia de tipo, representa los tipos de los parámetros de la función. Podemos representar una lista de parámetros como un tipo tupla. Por ejemplo, la lista de parámetros ``(String s, Float x)`` es representada como la tupla ``[String, Float]``. Así, que tomando la siguiente función: .. code-block:: ceylon function sum(Interger x, Integer y) => x+y; El tipo de la función ``sum()`` es: .. code-block:: ceylon Callabla ¿Qué hay acerca de las funciones ``void``? Bueno, El tipo de retorno de una función void es considerado a ser ``Anything``. Tal como el tipo de la función ``print()`` es: .. code-block:: ceylon Callable Habrá algunas personas que tienen un background en lenguajes como ML que tal ves estén esperando que ``void`` pueda ser identificado con algún tipo "unit", por ejemplos, ``Null`` o tal ves ``[]``. Pero este enfoque deberá significar que un método que no sea void deberá no estar disponible para refinar un método ``void`` y que una función que no sea de tipo void no estará disponible para ser asignado a un parámetro funcional ``void``. Sin embargo, código perfectamente razonable deberá ser rechazado por el compilador. Note que una función ``void`` con una implementación concreta devuelve implícitamente el valor ``null``. Esto es completamente diferente a una función declarada a devolver el tipo ``Anything``, que puede devolver cualquier calor, pero deberá hacerlo explícitamente, a través de la declaración ``return``. Las siguientes funciones tiene el mismo tipo, ``Anything``, pero no hacen exactamente la misma cosa: .. code-block:: ceylon Anything hello() { print("Hello") return "hello"; } void hello() { print("hello"); //retorno implicito de null } No deberás de confiar en una función que es declarada ``void``, debido a que puede ser un método que es refinado por una método no ``void`` o una referencia a una función no ``void``. Podemos abreviar tipos ``Callable`` con un poco de azúcar sintáctica: - ``Integer(Integer,Integer)`` significa ``Callable`` e igualmente, - ``Anything(String)`` significa ``Callable``. -------------------------------------- Definiendo funciones de orden superior -------------------------------------- Ahora tenemos suficiente conocimiento para poder escribir funciones de orden superior. Por ejemplo, podemos crear la función ``repeat()`` que ejecute repetidamente una función. .. code-block:: ceylon void repeat(Integer times, Anything(Integer) perform) { for (i in 1..times) { perform(i); } } Ahora, ejecutemos: .. code-block:: ceylon void printNum(Integer n) => print(n); repeat(10, printNum); Esto deberá de imprimir los números del 1 al 10 en consola. Existe un problema con esto. En Ceylon, como veremos después, frecuentemente llamamos funciones usando argumentos con nombre, pero el tipo ``Callable`` no necesita codificar el nombre de los parámetros de la función. Así Ceylon tiene una alternativa, mas elegante, sintaxis para declarar un parámetro de tipo ``Callable``: .. code-block:: ceylon void repeat(Integer timer, void perform(Integer n) ) { for (i in 1..times) { perform { n=i; }; } } Esta versión es un poco mas legible, así que el la sintaxis preferida. ----------------------- Referencias a funciones ----------------------- Cuando el nombre de una función aparece sin argumentos, como ``printNum`` en el anterior ejercicio, es llamada una referencia a una función. Una referencia a una función es el objeto que realmente es de tipo ``Callable``. En este caso, ``printNum`` tiene el tipo ``Callable``. Ahora, ¿recuerdas como dijimos que ``Anything`` es el tipo de retorno de una función ``void`` y también la raíz de la jerarquía de tipos? Bueno, esto es útil aquí, esto significa que podemos asignar una función de cualquier tipo a un parámetro que espera una función de tipo ``void``, siempre y cuando coincidan las lista de parámetros: .. code-block:: ceylon Boolean attemptPrint(Integer n) { try { print(n); return true; } catch (Exception n) { return false; } } Y podemos mandarla a llamar así: .. code-block:: ceylon repeat(10, attemptPrint); Otra forma en la que podemos producir una referencia a una función es por medio de aplicar parcialmente un método a una expresión (que recibe). .. code-block:: ceylon class Hello(String name) { shared void say(Integer n) { print("Hello, ``name``, for the ``n``th time!"); } } Y lo ejecutaremos de la siguiente manera: .. code-block:: ceylon repeat(10, Hello("Gavin").say); En la expresión anterior ``Hello("Gavin").say`` tiene el mismo tipo que ``print``, es decir es de tipo ``Anything(Integer)``. ------- Curring ------- Un método o función puede ser declarado en una forma llamada ``curried``, permitiendo al método o función ser parcialmente aplicado a sus argumentos. Una función ``curried`` tiene múltiples listas de parámetros: .. code-block:: ceylon Float adder(Float x)(Float y) => x+y; La función ``adder()`` tiene el tipo ``Float(Float)(Float)``. Podemos invocarla con un solo argumento para obtener la referencia a una función de tipo ``Float(Float)``, y mantener esta referencia como una función, como esta: .. code-block:: ceylon Float addOne(Float y); addOne = adder(1.0); O como un valor, como en el siguiente caso: .. code-block:: ceylon Float(Float) addOne = adder(1.0); (La única diferencia entre estos dos enfoques es que en el primer caso le asignamos un nombre a el parámetro de ``addOne()``.) Entonces subsecuente mente invocamos a ``addOne()``, el actual cuerpo de ``adder()`` es finalmente ejecutado, produciendo un ``Float``. ------------------ Funciones anonimas ------------------ Las funciones mas famosas del estilo de orden superior son un trio de funciones que transforman, filtran, coleccionan secuencias de valores. En Ceylon, estas tres funciones, ``map()``, ``filter()``, y ``fold()`` son métodos de la interfaz ``Iterable``, (Incluso hay una cuarta, una amiga un poco menos glamurosa llamada ``find()``, también un método de ``Iterable``.) Como probablemente habrás notado todas las definiciones de funciones han sido declaradas con un nombre, usando la sintaxis tradicional estilo C. No hay nada errado con pasar un nombre de una función a ``map()`` o ``filter()`` y de hecho es útil: .. code-block:: ceylon Float max = measurements.fold(0.0, largest); Sin embargo, común mente, es un inconveniente tener que declarar una función dándole nombre completa solo para pasárselo a ``map()``, ``filter()``, ``fold()`` o ``find()``. En vez de ello, podemos declarar una función anónima, como parte de la lista de argumentos. .. code-block:: ceylon Float max = measurements.fold(0.0, (Float max, Float num) => num > max then num else max); Una función anónima consta de: - opcionalmente, la palabra ``function`` o ``void`` - una lista de parámetros, encerrados entre paréntesis, seguidos por - una flecha gorda, =>, con una expresión o - un bloque. Así que podemos reescribir lo anterior usando un bloque. .. code-block:: ceylon Float max = measurements.fold(0.0, (Float max, Float num) { return num>max then num else max; }); Note que es un poco mas difícil dar una buena vista a las funciones anónimas con bloque, así que usualmente es mejor darle el nombre a una función y usarla como referencia. ----------------------------------------- Mas acerca de funciones de orden superior ----------------------------------------- Veamos un ejemplo practico, que mezcle ambas maneras de representar el tipo de una función. Supongamos que tenemos algún tipo de interfaz de usuario que puede ser observado por objetos en el sistema. Podemos usar algo como el patrón de Java ``Observer/Observable``: .. code-block:: ceylon interface Observer { shared formal void observe(Event event); } abstract class Component() { variable {Observer*} observers = {}; shared void addObserver(Observer observer) { observers = {observer, *observers}; } shared void fire(Event event) { for (o in observers) { o.observe(event); } } } Pero ahora todos los objetos tienen que implementar la interfaz ``Observer``, que solo tiene un método. ¿Porqué no simplemente dejamos fuera a la interfaz y permitimos a los observadores de eventos solo registrar un objeto función como su evento de escucha? En el siguiente código, definimos que el método ``addObserver`` acepte una función como parámetro. .. code-block:: ceylon abstract class Component() { variable {Anything((Event)*} observers = {}; shared void addObserver(void observe(Event event)) { observers = {observe, *observers}; } shared void fire(Event event) { for (observe in observers) { observe(event); } } } Aquí podemos ver la diferencia entre las dos maneras de especificar el el tipo de una función: - ``void observe(Event event)`` es mas legible en lista de parámetro, donde ``observe`` es el nombre del parámetro, pero - ``Anything(Event)`` es útil en el tipo del contenedor tal como un iterable. Ahora, cualquier observador de evento puede solo pasar una referencia a uno de sus métodos a ``addObserver()``: .. code-block:: ceylon class Listener(Component component) { void onEvent(Event e){ //responde al evento // ... } component.addObserver(onEvent); // ... } Cuando el nombre de el método aparece en una expresión sin una lista de argumentos después de el, es una referencia a un método, no una invocación al método. Aquí la expresión de tipo ``Anything(Event)`` que refiere al método ``onEvent()``. Si ``onEvent()`` fuese ``shared``, podemos incluso hilar ``Component`` y ``Listener`` desde algún otro código, para eliminar la dependencia de ``Listener`` sobre ``Component``. .. code-block:: ceylon class Listener() { shared void onEvent() { // respuesta a el evento // ... } } void listen(Component component, Listener listener) { component.addObserver(listener.onEvent); } Aquí la sintaxis de ``listener.onEvent()`` es un tipo de aplicación parcial del método ``onEvent()``. Esto no causa que el método sea ejecutado(debido a que no hemos provisto una lista de parámetros aún). En vez, resulta en una función que empaqueta juntos a el método referencia ``onEvent`` y el método recibidor ``listener``. Es también posible declarar un método que devuelva una función. Vamos a considerarla habilidad para remover observadores desde un ``Component``. Podemos usar una interfaz ``subscription``: .. code-block:: ceylon interface Subscription { shared formal void cancel(); } abstract class Component() { variable {Anything(Event)*} observers = {}; shared Subscription addObserver(void observe(Event event)) { observers = {observe,*observers}; object subscription satisfies Subscription { cancel() => observers = { for (o in observers) if (o!=observe) o }; } return subscription; } shared void fire(Event event) { for (observe in observers) { observe(event); } } } Pero una solución simple puede ser solo eliminar la interfaz y devolver el método ``cancel()`` directamente: .. code-block:: ceylon abstract class Component() { variable {Anything(Event)*} observers = {}; shared Anything() addObserver(void observe(Event event)) { observers = {observe,*observers}; return void () => observers = { for (o in observers) if (o!=observe) o }; } shared void fire(Event event) { for (observe in observers) { observe(event); } } } Aquí, hemos definido una función anónima dentro de el método ``addObserver()``, y retornar una referencia a esta función fuera del método. La referencia a la función anónima devuelta por ``addObserver()`` puede ser llamada por cualquier código que obtenga la referencia.. En caso que te estés preguntando el tipo de la función que se encuentra dentro del método ``addObserver()`` es ``Anything()(Anything(Event)``. Note que la función anónima esta habilitada para usar el parámetro ``observe`` de ``addObserver()``. Diremos que el método anidado recibe una ``closure`` de las no variable locales y parámetros desde afuera del método - Justo como un método de una clase recibe una ``closure`` de la clase inicializando parámetros y locales de la clase inicializador. En general, cualquier declaración de clase anidad, método o atributo siempre recibe la ``closure`` de la declaración de los miembros de la clase, método o atributo en que este es encerrado. Este es un ejemplo de que tan regular es el lenguaje. Podemos invocar nuestro método de la siguiente manera: .. code-block:: ceylon addObserver(onEvent)(); Pero si estamos planeando usar el método de esta manera, probablemente no se buena razón para darle dos listas de parámetros. Esto es mucho mas probable que cuando estábamos planeando mantener o pasar la referencia a el método anidado en algún lugar método antes de invocarlo. .. code-block:: ceylon Anthing() cancel = addObserver(onEvent); //... cancel(); La primera linea demuestra como la referencia de una función puede ser mantenida. La segunda linea de código simplemente invoca la referencia devuelta a ``cancel()``. ------------------- Composición y curry ------------------- La función ``compose()`` lleva acaba **composición de funciones**. Por ejemplo, dadas las funciones ``print()`` y ``plus()`` en ``ceylon.language``, con la siguiente forma: .. code-block:: ceylon shared void print(Anything line) { ... } shared Value plus(Value x, Value y) { ... } Podemos ver que el tipo de referencia de la función ``print()`` es ``Anything(Anything)``, y el tipo de la referencia a la función ``plus`` es ``Float(Float, Float)``. Entonces podemos escribir lo siguiente: .. code-block:: ceylon Anything(Float, Float) printSum = compose(print,plus); printSum(2.0,2.0); //imprime 4.0 La función ``curry()`` produce una función con múltiples listas de parámetros, dada una función con múltiples lista de parámetros: .. code-block:: ceylon Anything(Float)(Float) printSumCurried = curry(printSum); Anything(Float) printPlus2 = printSumCurried(2.0); printPlus(2.0); //imprime 4.0 La función ``uncurry()`` hace lo opuesto, dándonos la forma original. .. code-block:: ceylon Anything(Float,Float) printSumUncurried = uncurry(printSumCurried); Note que ``compose()``, ``curry`` y ``uncurry()`` son funciones ordinarias escritas en Ceylon. ------------------ El operador spread ------------------ Ya hemos visto unos pocos ejemplos de el operador ``spread``. Hemos visto como usarlo para instanciar un iterable: .. code-block:: ceylon { "hello", *names } O una tupla: .. code-block:: ceylon [x, y, *labels] También podemos usarlo cuando llamamos una función. Considere la siguiente función. .. code-block:: ceylon String formatDate(String format, Integer day, Integer|String month, Integer year) { ... } Y supóngase que tenemos una tupla representando una fecha: .. code-block:: ceylon value date = [15, "January", 2010]; Entonces podemos pasar la fecha a nuestra función como lo siguiente: .. code-block:: ceylon formatDate("dd MMMMMM yyyy", *date); Note que el tipo de la tupla ``["dd MMMMMM yyyy", *date]`` es: .. code-block:: ceylon [String,Integer,String,Integer] Ahora considerar el tipo de la función ``formatDate``: .. code-block:: ceylon String(String,Integer,Integer|String,Integer) O también: .. code-block:: ceylon Callable Desde que la tupla tipo ``[String,Integer,String,Integer]`` es un subtipo de ``[String,Integer,Integer|String,Integer]``, la invocación esta bien tipada. ¡Esto demuestra la relación entre tuplas y argumentos de función!. ----------- Aún hay más ----------- Podrás encontrar una discusión mas detallada de como Ceylon represente tipos de funciones usando tuplas `aqui `_ incluyendo una discusión a detalle de ``compose()`` y ``curry()``. Ahora es turno de la sintaxis para lista de argumentos con nombre y para definir interfaces de usuario e información estructurada.