Argumentos con nombre

Esta es la duodécima parada del tour de Ceylon. En la parada anterior aprendimos acerca de funciones. Esta parte se enfoca en cubrir el soporte de Ceylon para usar argumentos con nombre.

Argumentos con nombre

Considere la siguiente función:

void printf(Writer to, String format, {Object*} values) {
    //...
}

Hemos visto cientos de ejemplos de invocar una función o instanciar una clase usando una sintaxis similar a C, donde los argumentos son delimitados por paréntesis y separados por coma. Argumentos son asignados a parámetros por su posición en la lista. Veamos un ejemplo más:

printf(writer,
       "Thanks, %s. You have been charged %.2f.
        Your confirmation numbers is %d.",
       { user.name, order.total, order.confirmationNumber });

Esto trabaja bien. Sin embargo, Ceylon ofrece un alternativo protocolo de invocación de una función que es usual mente fácil de leer cuando hay más de uno o dos argumentos:

printf {
    to = writer;
    format = "Thanks, %s. You have been charged %.2f.
              Your confirmation number is %d.",
    { user.name, order.total, order.confirmationNumber }
};

Esta invocación es llamada lista de argumentos con nombre. Podemos reconocer una lista de argumentos con nombre por que utiliza llaves en ves de paréntesis. Note que los argumentos son separados por punto y coma. Hemos especificado el nombre de cada parámetro.

Usual mente le damos un formato en múltiples lineas a la invocación de argumentos con nombre.

Argumentos iterables

Desde que el parámetro values es de tipo Iterable, se nos esta permitido abreviarlo, dejando fuera el nombre del parámetro y las llaves que encierran a la expresión iterable.

printf {
    to = writer;
    format = "Thanks, %s. You have been charged %.2f.
              Your confirmation number is %d.";
    user.name, order.total, order.confirmationNumber
}

De hecho, podemos dejar fuera los nombres de los parámetros completamente.

Dejando fuera los nombres de los parámetros

Contraria a la descripción de la característica “lista de argumentos con nombre”, podemos actualmente dejar fuera los nombres de el parámetro si escribimos los argumentos descendente mente en el orden correcto:

printf {
    writer;
    "Thanks, %s. You have been charged %.2f.
     Your confirmation number is %d.";
    user.name, order.total, order.confirmationNumber
}

Si, existe una muy buena razón para esto, ¡estamos a punto de verla!

Sintaxis declarativa para instanciar objetos

Las siguientes clases definen una estructura de datos para definir tablas:

class Table(String title, Integer rows, Border border,
                {Column*} columns) {}

class Column(String heading, Integer width,
                String content(Integer row) {}

class Border(Integer padding, Integer weight) {}

Por supuesto, podemos construir una Table usando una lista de argumentos posicionales y funciones anónimas:

Table table = Table("Squares", 5, Border(2,1),
                   { Column("x",10, (Integer row) => row.string),
                     Column("x^2",12,(Integer row) => (row^2).string) });

Sin embargo, es mucho mas común usar una lista de parámetros con nombre para construir un complejo gráfico de objetos. En esta sección vamos a conocer algunas nuevas características de la lista de argumentos con nombre, que hacen especialmente conveniente construir gráficos de objetos.

Primero, note que la sintaxis que ya hemos usado para especificar el valor de un argumento con nombre se ve exactamente como la sintaxis para refinar un atributo formal. Si tu lo piensas así, teniendo en cuenta que un parámetro de una función puede aceptar referencia a otras funciones, todo el problema de especificar valores para argumentos con nombre comienza a parecerse mucho como el problema de refinar miembros abstractos. Entonces, Ceylon deberá permitirnos reusar mucha sintaxis de la declaración de los miembros dentro de una lista de argumentos con nombre.

Es completamente legal incluir las siguientes construcciones en una lista de argumentos con nombre:

  • Declaración de funciones - especifica el argumentos de un parámetro que acepte una función.
  • declaración de object(clase anónima) - son útiles para especificar el valor de un argumento con nombre donde el tipo es una interfaz o una clase abstracta, y
  • Declaración de getter - nos permite computar el valor de un argumento.

Esto ayuda a explicar por que listas de argumentos con nombre son delimitadas por llaves: La sintaxis general para una lista de argumentos es muy muy cercana a la sintaxis para el cuerpo de una clase, función o atributo. Note, otra vez, cuanta flexibilidad deriva de un lenguaje regular.

Así podemos escribir un código que construye una Table como sigue:

Table table = Table {
    title="Squares";
    rows=5;
    border = Border {
        padding=2;
        weight=1;
    };
    Column{
        heading="x";
        width=10;
        function content(Integer row)
            => row.string;
    }
    Column {
        heading="x^2";
        width=12;
        function content(Integer row)
            => (row^2).string;
    }
}

Note que especificamos el valor de el parámetro con nombre content, usando la sintaxis usual para declarar una función.

Aun mejor, usando el atajo que hemos visto antes, nuestro ejemplo puede ser un poco mas abreviado como este:

Table table = Table {
    title="Squares";
    rows=5;
    Border {
        padding=2;
        weight=1;
    };
    Column{
        heading="x";
        width=10;
        content(Integer row)
            => row.string;
    }
    Column {
        heading="x^2";
        width=12;
        content(Integer row)
            => (row^2).string;
    }
}

Note como hemos transformado el código desde una forma que enfatiza invocación a una forma que enfatiza declaración de una estructura jerárquica. Semántica mente, las dos formas son equivalentes. Pero en términos de legibilidad, son un poco diferentes.

Podemos poner el código anterior totalmente declarativo en su propio archivo y deberá verse como un tipo de “mini-lenguaje” para definir tablas. De hecho, su óodigo ejecutable puede ser corregido sintáctica mente por el compilador de Ceylon y después compilado a bytecode de Java o de Javascript. Aun mejor, el IDE de Ceylon deberá proveer soporte para nuestro mini-lenguaje. En un completo contraste a el soporte DSL en algunos lenguajes dinámicos, ¡cualquier DSL de Ceylon es completamente seguro en cuanto a tipos! Puedes pensar en la definición de las clases Table, Column y Border como definiendo el “esquema” o la “gramática” de un mini-lenguaje. (De hecho, ellos realmente definen una sintaxis de árbol para el mini-lenguaje.)

Ahora veamos un ejemplo de una lista de argumentos con nombre con una declaración getter:

shared class Payment(PaymentMethod method,
                     Currency currency,
                     Float amount) {}

Payment payment {
    method = user.paymentMethod;
    currency = order.currency;
    value amount {
        variable Float total = 0.0;
        for (item in order.items) {
            total += item.quantity * item.product.unitPrice;
        }
        return total;
    }
}

Finalmente, aquí hay un ejemplo de una lista de argumentos con nombre con una declaración de object:

shared interface Observable {
    shared void addObserver(Observer<Nothing> observer) {
        //...
    }
}

shared interface Observer<in Event> {
    shared formal void on(Event event);
}

observable.addObserver {
    object observer
            satisfies  Observer<UpdateEvent> {
        on(UpdateEvent e) =>
            print("Update: " + e.string);
    }
};

(Note que Observer<T> es asignable a Observer<Nothing> para cualquier tipo T, desde que Observer<T> es contra variante en su parámetro de tipo T. Si esto no te es entendible, por favor lee la sección de generics otra vez.)

Por supuesto, como hemos visto en la parada anterior de funciones, una mejor manera de resolver este problema puede ser eliminar la interfaz Observer y pasarle la función directamente:

shared interface Observable {
    shared void addObserver<Event>(void on(Event event)) { ... }
}

observable.addObserver {
    void on(UpdateEvent e) =>
            print("Update: " + e.string);
};

Definiendo intefaces de usuario

Uno de los primeros módulos que vamos a crear para Ceylon deberá ser una librería para templates HTML en Ceylon. Un fragmento de HTML estático deberá lucir como esto:

Html {
    Head {
        title = "Hello world";
        cssStyleSheet = "hello.css";
    };
    Body {
        Div {
            cssClass = "greeting";
            "hello World"
        },
        Div {
            cssClass = "footer";
            "Powered by Ceylon";
        }
    };
}

Incluso aunque parezca como un tipo de lenguaje de template, es solo una expresión ordinaria.

Importante

Nota de implementación - Milestone 5

Esta libreria aun no existe. Por que no te unes al desarrollo de la plataforma de Ceylon.

Aún hay más

Hay demasiadas aplicaciones de esta sintaxis del lado de la definición de interfaces de usuario. Por ejemplo, Ceylon nos permite usar una lista de argumentos con nombre para especificar los argumentos del elemento anotación de un programa. Pero tendremos que volver a este temas de las anotaciones en una entrega futura.

La siguiente sección aun introduce otra manera de especificar un argumento a una función: comprensión.