Atributos y variables, estrucutras de control

Esta es la terera parte de este tour por Ceylon. En la pasada parada hemos aprendido acerca de clases y los conceptos de un atributo. Una de las cosas que hace una clase especial es que puede estados-referencia a otros objetos. Asi que e tiempo de aprender mas acerca de atributos y variables.

Tambien veremos un poco de las estructuras de control (if, switch, for, while, y try).

Atributos y valores locales

En Java, un field de una clase es facilmente distinguir entre una variable local o parametro de un constructor. Esta distincion es menos significativa en Ceylon e frecuente irrelevante. Un atributo es realmente solo una valor declarado en la lista de parametros o en el cuerpo de la clase, puediendo ser capturado por alguna declaración shared.

Aqui, cuenta es una variable local-block del inicializador de Contador.

class Contador () {
    variable Integer cuenta=0;
}

Pero en los siguientes dos ejemplos, cuenta es un atributo.

class Contador() {
    shared varible Integer cuenta=0;
}

class Contador(){
    variable Integer cuenta=0;
    variable Integer inc() => ++cuenta++;
}

Esto puede ser un poco confuso la primera vez, pero es realmente como el principlo de trabajo de la closure. El mismo comportamiento aplica a valores local-block en el cuerpo de una funcion. Funciones no pueden ser declarados miembros shared, por su puesto, pero ellos pueden retornar un objeto que capture una varaible local:

interface Contador {
    shared formal Integer inc();
}

Contador crearContador () {
    variable Integer cuenta=0;
    object contador satisfies Contador {
        shared actual Integer inc() => ++cuenta;
    }
    return contador;
}

O, como veremos mas adelante, una función puede retornar una funcion anidada que capture la variable local:

Integer() contador {
    variable Integer cuenta=0;
    Integer inc() => ++cuenta;
    return inc;
}

(No te preocupes mucho acerca de la sintaxis aqui - por ahora todo lo que nos interesa es en que contador() devueve una referencía a una función inc() la cual captura la variable cuenta.)

Asi que aunque continuemos usando el termino “valor local” y “atributo” a lo largo de nuestro viaje, manten en mente que no hay una dinstinción significativa entre los terminos. Cualquier valor con nombre puede ser capturado por alguna otra declaración en el mismo contenido scope. Un valor local es solo un atributo que parece no ser capturado por nadie.

Variables

Ceylon alienta a usar atributos inmutables tanto como sea posible. Un atributo inmutable obtiene su valor cuando el objeto es inicializado, nunca mas puede ser reasignado.

class Referencia<Valor>(val){
    shared Valor val;
}

value ref = Referencia("foo");
print(ref.val);
ref.val = "bar"; // compile error: value is no variable

Si nostros queremos asignar un valor a la referencia necesitamos anotarla con variable:

class Referencia<Valor>(val){
    shared variable Valor val;
}

value ref = Referencia("foo");
print(ref.val);
ref.val = "bar"; // ok
print(ref.val);

Setters

Hemos conocido el concepto de un getter.

Si queremos crear un atributo con un getter mutable, necesitamos definir un matching setter. Usualmete esto unicamente usable si tu tienes algun otro atributo interno que quieras al que quieras establecer un valor indirectamente.

Supongamos nuestra clase tiene los siguientes atributos, diseñados para unicamente ser usados internamente (un-shared).

variable String? nombre=null;
variable String? apellido=null;

(Recuerda que ceylon nunca incializa los atributos automaticamente a null.)

Entonces nosotros podemos abstraer los atributos usando un tercer atributo definido como un par getter/setter:

shared String nombreCompleto =>
    " ".join(coalesce {nombre, apellido});

assign nombreCompleto {
    value tokens = nombreCompleto.split().iterator();
    if (is String primero = tokens.next()){
        nombre=primero;
    }
    if (is String ultimo=tokens.next()){
        apellido=ultimo;
    }
}

Un setter es identificado por la palabra reservada assign en lugar del tipo de la declaración. (EL tipo de el matching getter determina el tipo de el atributo.) Dentro de el cuerpo de el setter, el atributo nombre evalua al valor a ser establecido

Asi es, se parece mucho a un par de metodos get/set en Java, aunque la sintaxis es significativamente mas simplificada. Pero desde que los atributos en Ceylon son poliformicos, y desde que puedes redefinir una referencia como un getter o un par getter/setter sin afectar a los clientes que llamen al atributo, no necesitas definir getter y setters amenos que estes haciendo algo especial con el valor que estas obteniendo o estableciendo.

Nunca escribas codigo como este en Ceylon:

variable String _name = " ";
shared String name => _name; //pointless getter
assign name => _name=name;   //pointless setter

No es necesario, y no obtines ningun beneficio de el.

Estructuras de control

Ceylon tiene seis estructuras de control que vienen integradas. No hay muy nuevo para los desarrolladores de Java o de C#, asi que unos pequeños ejemplos sin muchos comentarios adicionales deberas de ser suficientes.

En primer lugar, un “gotcha” para los chicos que vienen de lenguajes similares a C: Ceylon no permite omitir las llaves en una estructura de control. El siguiente codigo niquiera sera parseado:

if (x>100) print("grande");  //error

Se debe se escribir:

if (x>100) { print("big"); }

La razón para que las llaves no sean opcionales en Ceylon es debido a que una expresion puede comenzar con una llave abierta, por ejemplo, {“hola”, “mundo” }, así que llaves opcionales en estructuras de control harán que la gramática sea ambigua a el parser.)

Ok, ahora es momento de ir a los ejemplos.

if

La declaración del if/else es completamente tradicional:

if (x > 1000) {
    print("Realmente grande");
}
else if (x > 100) {
    print("Grande");
}
else {
    print("pequeño");
}

Después aprenderemos como el if puede estrechar el tipo de referencia en su bloque. Ya hemos visto un ejemplo de esto, pero volveremos a hablar de esto con cuando lleguemos a tipos opcionales.

Nosotros usamos frecuentemente el operador then e else en vez de if.

switch

La declaración switch/case elimina el muy criticado comportamiento “fall through” y la sintaxis irregular:

switch (x<=>100)
case (pequenio) { print("pequeño"); }
case (igual) { print("Cien"); }
case (grande) { print("Grande"); }

EL tipo de la expresión que evalua switch deberá de ser un tipo enumerated. Tu no puedes usar switch con un String o un Integer. (Utiliza if en vez de ello.)

Aun tenemos mucho mas que decir acerca de switch cuando discutamos tipos enumerated.

assert

Ceylon también tiene una declaración assert:

assert (longitud < 10);

Los assert son buenos para crear declaraciones donde tu conoces que tiene que ser verdadero, pero no son aparentes a otros lectores del código(incluyendo el identificador de tipos!). Los usos comunes de un assert incluyen cosas como precondiciones, postcondiciones y clases invarientes.

Si la condición es false es lanzada una excepciones en tiempo de ejecución. El mensaje de la excepción ayudara incluira detalles de la condición que fue violada, esto es muy importante cuando un assert tiene mas de una condicional.

assert (exists arg, !arg.empty);

Para personalizar el mensaje de assert, agrega una anotación doc:

"La longitud debera ser de almenos 10"
assert (longitud < 10)

En su caso, el analizador de tipos usara assert información para los tipos cuando checa declaraciones que siguen al assert, por ejemplo:

Integer? x = parseInteger("1");
assert (exists x);
// Despues de `assert`, x tiene el tipo Integer enve de Integer?
value y = x+10;

Esto es realmente el mismo comportamiento que hemos visto anteriormente, unicamente esta vez enmedio de un bloque en vez de al inicio de un bloque if. (No te preocupes, habrá más de estos ejemplos después.)

Nótese que, a diferencia del assert de Java, que puede ser deshabilitado en tiempo de ejecución, los assert en Ceylon siempre estarán habilitados.

for

EL ciclo for tiene un bloque opcional else, que puede ser ejecutado cuando el ciclo termina completamente sus iteraciones si pasar por una declaración return o un break.

variable Boolean menores;

for (p in gente) {
    if (p.age<18) {
        menores=true;
        break;
    }
}
else {
    menores=false;
}

Este no es un for estilo C. En vez de ello, puedes usar el operador de rango longitudinal :, para producir una secuencia de Integer, dando su inicio y su longitud:

for (i in min:lon) { ... }

Alternativamente, puede usar el operador de rango común .. para producir una secuencia de Integer entre dos puntos:

for (i in min..max) { ... }

Existen algunos otros trucos con un for que veremos mas adelante.

Nosotros usamos comprehensions o incluso funciones higher order en vez de for.

while

EL while puede se usado de la forma tradicional.

value it = nombres.iterator();
while (is String siguiente = it.next()){
    print(siguiente);
}

No hay una declaración do/while.

try

La declaración try/catch/finally trabajan como en Java:

try {
    message.send();
}
catch (ConnectionException|MessageException e) {
    tx.setRollbackOnly();
}

Para manejar todas la excepciones de Ceylon, junto con todas las excepciones de JavaScript, o todas las excepciones que son subclases de java.lang.Exception, , podemos catch el tipo Exception definido en ceylon.language. Si no especificamos explicitamente un tipo, Exception es inferido:

try {
    message.send();
}
catch (e) {// Equivalente a "catch (Exception e)"
    tx.setRollbackOnly();
}

No hay una manera de manejar excepciones de tipo java.lang.Error.

Eventualmente try soportara expresiones resource similares a las Java 7.

try (Transaction()) {
    try(s = Session()){
        s.persist(person);
    }
}

Notas de la implementación Milestone 5

Expresiones Resource aun no son implementados.

Condition list

Construcciones como if,while y assert aceptan Contion list. Una condition list es una lista no ordenada de múltiples booleanos, exists, nonempty e is. La `condition list es satisfecha si (y solo si) cada una de las condicionales es satisfecha.

Con condicionales booleanas puedes lograr el mismo comportamiento con el operador &&. Pero con condition list te permite usar el “structured typecasting” de exists, is y amigos en condiciones que aparezcan después en la misma lista.

Veamos un ejemplo usando assert:

value url = parseUri("http://ceylon-lang.org/download");
assert(exists authority=url.authority,
       exists host=authority.hostname);
// Hacer algo con host

Aqui puedes ver dos condiciones exists en la declaración assert, separadas con coma. La primera declara authority`(que es inferida a ser una `String en vez de una String? debido al exists). La segunda condición entonces usa su propia exists condición.

Lo importante a notar es que el compilador nos permite usar authority en la segunda condición y conocer que es una String y no una String?. No puedes hacer esto con múltiples “&&” condiciones. Puedes lograrlo anidando varios if, pero hará menos legible el código y no trabajara bien en un declaración while o somprehension.

Aun hay mas...

Ahora que conocemos acerca de las clases y sus miembros, estamos listos para explorar herencia y refinamiento(overriding).