Ceylon: Introducción rápida

Una forma de contribuir con Ceylon, o con cualquier otro proyecto de open source, es escribir en la lengua de Cervantes (modificada por Bárbaro), mucho de lo que ya está escrito en el sitio de Ceylon en la lengua de Shakespeare ( modificada por Gavin). Mi intención es traducirlo todo al español y confío en Manzanero con su semana de más de 7 días.
Por lo pronto lo que sigue es una traducción libre de Quick Introduction

Introducción rápida

Es imposible captar la esencia de un lenguaje de programación observando solamente la lista de características. Lo que realmente hace al lenguaje es la combinación de esa pequeñas cosas trabajando juntas. Y resulta que es imposible apreciar esa sinergia sin escribir código. En esta sección vamos a mostrar de manera rápida lo suficiente de Ceylon para lograr un interés suficiente que te conduzca a probar el lenguaje. Lo que sigue por supuesto que no es una lista completa de las características del lenguaje.

Una sintaxis legible y familiar

La sintaxis de Ceylon es a "diez de última" derivada del lenguaje C. Por lo que si eres un programador de C, Java o C# te sentirás como en casa. De hecho, uno de los objetivos del lenguaje es que gran parte del código sea legible por personas que no son programadores de Ceylon y que no han estudiado la sintaxis del lenguaje.
Sigue, por ejemplo, el código para escribir una función:

function distance(Point from, Point to) {
  return ( (from.x - to.x)**2 + (from.y .to.y)**2 ) **0.5
}

Y el código para una clase:

shared class Counter(Integer initialValue=0) {
 
    variable value count := initialValue;
 
    shared Integer currentValue {
        return count;
    }
 
    shared void increment() {
        count++;
    }
 
}

Y, si queremos crear e iterar la secuencia creada:

String[] names = { "Tom", "Dick", "Harry" };
for (name in names) {
    print("Hello, " name "!");
}

Si estos códigos te aburren, es porque precisamente esa es la idea - son aburridos porque los entiendes inmediatamente.

La sintaxis declarativa para estructuras arbóreas

Las estructuras jerárquicas son tan comunes en computación que para ellas tenemos lenguajes dedicados para lidiar con ellas, como es el caso del lenguaje XML. Pero cuando tenemos código procedural que interactúa con estructuras jerárquicas, el mismatching (lo dejo en inglés) de impedancia entre XML y nuestro lenguaje de programación es la causa de toda suerte de problemas. Ceylon entonces, incorpora una sintaxis "declarativa" especial para definir estructuras jerárquicas. Esto es especialmente útil para crear interfaces de usuario:

Table table {
    title="Squares";
    rows=5;
    Border border {
        padding=2;
        weight=1;
    }
    Column {
        heading="x";
        width=10;
        String content(Integer row) {
            return row.string;
        }
    },
    Column {
        heading="x**2";
        width=10;
        String content(Integer row) {
            return (row**2).string;
        }
    }
}

La utilidad de esa sintaxis declarativa va mucho más allá del ejemplo anterior y nos brinda un gran fundamento para expresar todo, desde guiones (scripts) para construir proyectos hasta suites de prueba:

Suite tests {
    Test {
        name = "sqrt() function";
        void run() {
            assert(sqrt(1)==1);
            assert(sqrt(4)==2);
            assert(sort(9)==3);
        }
    },
    Test {
        name = "sqr() function";
        void run() {
            assert(sqr(1)==1);
            assert(sqr(2)==4);
            assert(sqr(3)==9);
        }
    }
}

Cualquier marco de trabajo (framework) que combina Java y XML requiere de herramientas de propósito especial para lograr la comprobación de tipos y la asistencia en la autoría. Los marcos de trabajo de Ceylon, que hacen uso del soporte incorporado (built-in) del lenguaje para expresar estructuras arbóreas, logran esto y mucho más "de gratis".

Tipado (typing) principal, tipos unión y tipos intersección

La sintaxis con sabor convencional de Ceylon esconde un sistema poderoso de tipos capaz de expresar cosas que otros lenguajes de tipado estático simplemente no pueden. Todos los tipos de Ceylon pueden, al menos en principio, expresarse por si mismos dentro del sistema de tipos. No existen tipos primitivos, ni arreglos ni otras cosas similares.
El sistema de tipos está basado en el análisis del "mejor" o tipos principales. Para cualquier expresión, puede determinarse su tipo más específico, sin analizar el resto de la expresión en la cual ella aparece. Todos los tipos usados internamente por el compilador son denotables, i.e, ellos pueden expresarse dentro del propio lenguaje. En la práctica, esto significa que el compilador siempre produce errores que los humanos ( o sea nosotros) pueden entender, aun en los casos cuando se trabaja con tipos genéricos complejos. El compilador de Ceylon nunca produce mensajes de error con tipos no denotables mistificantes como   List<capture#3-of ?> en Java.
Una parte integrante de este sistema de tipos principales denotables es el soporte de primera clase para los tipos union e intersección. Un tipo unión es un tipo que acepta instancias de cualesquiera de los tipos de una lista:

Person|Organization personOrOrganization = ... ;

Un tipo intersección es un tipo que acepta instancias de todos los tipos de una lista:

Printable&Sized&Persistent printableSizedPersistent = ... ;

Los tipos unión e intersección son útiles y convenientes en el código ordinario. Aun más importante, ayudan a simplificar y a que las cosas sean más claras en situaciones donde otros lenguajes son complejos, especialmente en la inferencia de argumentos de tipos genéricos. Considere el siguiente código para secuencias:

value stuff = { "hello", "world", 1.0, -1 };
value joinedStuff = join({"hello", "world"}, {1.0, 2.0}, {});

El compilador en este caso infiere los tipos:

Sequence<String|Float|Integer> para el valor  stuff, y
Empty|Sequence<String|Float> para joinedStuff

Esos son los tipos principales de las expresiones. No tuvimos la necesidad de especificar explícitamente esos tipos en ninguno de los dos casos.

Herencia mixin

Ceylon tiene clases e interfaces como Java. Una clase puede heredar solamente de una clase base y de un número arbitrario de interfaces. Una interfaz puede heredar de un número arbitrario de interfaces pero no puede heredar de una clase que no sea Object. A diferencia de Java, las interfaces pueden definir miembros concretos. Por tanto, Ceylon soporta un tipo restringido de herencia múltiple conocido como herencia mixin (no traduzco el término):

interface Sized {
 
    shared formal Integer size;
 
    shared Boolean empty {
        return size==0;
    }
 
}
 
interface Printable {
 
    shared void printIt() {
        print(this);
    }
 
}
 
object empty satisfies Sized & Printable {
 
    shared actual Integer size {
        return 0;
    }
 
}

Lo que distingue a las interfaces de las clases en Ceylon es que las interfaces son stateless ( no traduzco el término). Esto quiere decir que una interfaz no puede contener directamente una referencia a otro objeto, no puede tener lógica de inicialización y no puede ser instanciada de manera directa. Ceylon, elegantemente evita la necesidad de realizar cualquier tipo de linearización de los super tipos.

Atributos polimórficos

Ceylon no tiene campos, al menos en el sentido tradicional. En su lugar, los atributos son polimórficos, y pueden ser refinados por una subclase, similar al refinamiento de los métodos en los lenguajes orientados a objetos.
Un atributo podría ser un valor simple:

String name = firstName + " " + lastName;

Podría ser un getter:

String name {
    return firstName + " " + lastName;
}

O podría ser un par getter/setter:

String name {
    return fullName;
}
 
assign name {
    fullName := name;
}

En Ceylon no necesitamos escribir getters o setters triviales, ya que el estado de una clase siempre se abstrae completamente de los clientes de la clase.

null seguro en tipo y estrechamiento más seguro de tipos

No hay en Ceylon NullPointerException ni cosa similar. Ceylon requiere de nosotros que declaremos de forma explícita que valores pueden ser nulos o que métodos pueden retornar nulos. Por ejemplo, si name pudiera ser null, tenemos entonces que declararlo como:

String? name = ...

que no es otra cosa que una abreviación de:

String|Nothing name = ...

Un atributo de tipo String? podría referirse a una instancia real de String o podría referirse al valor null (la única instancia de la clase Nothing. De suerte que, Ceylon no nos permite hacer nada útil con un valor de tipo String? sin antes comprobar que no es null usando el constructo especial  if exists(...).

void hello(String? name) {
    if (exists name) {
        print("Hello, " name "!");
    }
    else {
        print("Hello, world!");
    }
}

De forma similar no existe en Ceylon ClassCastException. En su lugar, hacemos uso de los constructos (is . . .)y  case (is . . .) para probar y estrechar el tipo de un valor en un solo paso. De hecho, el código anterior es realmente un atajo para escribir lo siguiente:

void hello(String? name) {
    if (is String name) {
        print("Hello, " name "!");
    }
    else {
        print("Hello, world!");
    }
}

Subtipos enumerados

Es una mala práctica en la programación orientada a objetos escribir sentencias switch largas que manejen todos los subtipos de un tipo. Resulta tedioso extender un código escrito de esa manera.Al añadir un nuevo subtipo al sistema provocamos la ruptura de las sentencias switch. Por eso, en el código orientado a objetos, usualmente refactorizamos el código escrito de esa manera, usando un método abstracto del super tipo que se refina apropiadamente por los subtipos.
Sin embargo,hay una clase de problemas donde este tipo de refactorización no es apropiado. En la mayoría de los lenguajes orientados a objetos, estos problemas se resuelven usando el patrón "visitador". Desafortunadamente, una clase visitadora puede resultar más verbosa que una sentencia switch y no es más extensible. Hay, por otra parte, una ventaja significativa del patrón visitador: el compilador produce un error si añadimos un nuevo subtipo y olvidamos manipularlo en uno de nuestros visitadores.
Ceylon nos da lo mejor de ambos mundos. Podemos especificar una lista enumerada de subtipos cuando definimos un super tipo:

abstract class Node() of Leaf | Branch {}

y podemos escribir una sentencia switch que manipule todos los subtipos enumerados:

Node node = ... ;
switch (node)
  case (is Leaf) { ... }
  case (is Branch) { .... }

Alias de tipos e inferencia de tipos

Las declaraciones de tipos totalmente explícitas pueden contribuir a que un código complejo pueda ser más fácil de entender. Pero hay otras ocasiones donde la repetición de un tipo genérico verboso atenta contra la legibilidad del código. Hemos observado que:

  1. las anotaciones explícitas de los tipos son de mucho menos valor para las declaraciones locales, y
  2. la repetición de un tipo parametrizado con los mismos argumentos tipo es común y extremadamente ruidoso en Java.

Ceylon ataca el primer problema permitiendo la inferencia de tipos para declaraciones locales. Por ejemplo:

value names = LinkedList { "Tom", "Dick", "Harry" };
 
function sqrt(Float x) { return x**0.5; }
 
for (item in order.items) { ... }

Por otra parte, para las declaraciones que están accesibles fuera de la unidad de compilación en las que son definidas, Ceylon requiere una anotación explícita del tipo. Pensamos que eso hace al código más legible, nunca menos, y logra que el compilador sea más eficiente y menos vulnerable a desbordamientos de la pila.

Ceylon enfrenta el segundo problema usando alias para los tipos, que son muy similares a un typedef en el lenguaje C. Aun alias de un tipo puede fungir como una abreviación para un tipo genérico junto con sus argumentos tipo:

interface Strings = List<String>;

Fomentamos el uso de estas dos características del lenguaje donde y sólo donde ellas logren hacer que el código sea más legible.

Funciones de orden superior

Como la mayoría de los lenguajes de programación , Ceylon permite pasar funciones como argumentos a otras funciones.
Una función que opera sobre otras funciones es llamada una función de orden superior.Por ejemplo:

void repeat(Integer times, void do()) {
    for (i in 1..times) {
        do();
    }
}

Cuando se invoca una función de orden superior, podemos pasar, una referencia a una función nombrada:

void hello() {
    print("Hello!");
}
 
repeat(5, hello);

O podemos especificar la función argumento en línea, bien así:

repeat(5, void print("Hello!"));

O usando una invocación con argumento nombrado, como esto:

repeat {
    times = 5;
    void do() {
        print("Hello!");
    }
};

Es posible también pasar una referencia a un atributo o método miembro a una función de orden superior:

String[] names = ... ;
String[] uppercaseNames = map(names, String.uppercase);

Genéricos simplificados con tipos totalmente reificados

Ceylon no soporta los parámetros tipo de wildcard al estilo de Java, no soporta los tipos raw(no traduzco el término) ni ninguna otra clase de tipo existencial. El compilador de Ceylon nunca usa tipo alguno "no-denotable" para razonar acerca del sistema de tipos. No hay en Ceylon restricciones implícitas sobre los argumentos tipo. De esta forma, los mensajes de errores relacionados con los genéricos siempre son entendidos por los humanos.
En lugar de tipos wildcard, Ceylon incluye información de la varianza en el sitio de la declaración. Un parámetro tipo puede marcarse como covariante (out) o contravariante (in) por la clase o la interfaz que declara el parámetro.

interface Correspondence<in Key, out Item> { ... }

Ceylon tiene un sistema más expresivo e restricciones de tipos genéricos con una sintaxis más regular y más amplia. La sintaxis para declarar restricciones sobre los tipos en el parámetro tipo es muy similar a una declaración de clase o interfaz.

interface Producer<in Input, out Value>
        given Value(Input input) satisfies Equality { ... }

El sistema de tipos de Ceylon es totamente reificable. En particular, los argumentos de tipos genéricos son reificados, eliminando muchos problemas que resultan del borrado de los argumentos de tipos genéricos en Java.

Polimorfismo de operadores

Ceylon incluye un rico conjunto de operadores, incluyendo la mayoría de los operadores que soportan C y Java. Ceylon no soporta la sobrecarga real de operadores. Lo sentimos, no puedes definir el operador papa <+|:-) en Ceylon. Ni tampoco puedes redefinir el operador * con algo que nada tiene que ver con la multiplicación numérica. Sin embargo, cada operador predefinido por el lenguajes esta definido bajo cierta clase o interfaz, permitiendo la aplicación del operador a cualquier clase que extienda o satisfaga el tipo en cuestión. Llamamos a este enfoque polimorfismo de operadores.
Por ejemplo, el módulo del lenguaje Ceylon define la interfaz Equality como:

shared interface Equality {
    shared formal Boolean equals(Equality that);
    shared formal Integer hash;
}

Y la operación == está definida para los valores que son asignables a Equality. La expresión siguiente:

x==y

es meramente una abreviación de:

x.equals(y)

De forma similar, < se define en términos de la interfaz Comparable, * en términos de la interfaz Numeric y así con otros operadores.

Metaprogramación segura en tipos y anotaciones

Ceylon ofrece un soporte sofisticado para la meta programación, incluyendo un meta modelo seguro en tipos y eventos. El código genérico puede invocar miembros de manera reflexiva e interceptar invocaciones a miembros. Esta característica es más poderosa y mucho más segura en tipos, que la reflexión en Java.

Class<Person,Name> personClass = Person;
Person gavin = personClass(Name("Gavin", "King"));

Ceylon soporta las anotaciones a los elementos de un programa con una sintaxis racional. De hecho, las anotaciones son usadas para los modificadores del lenguaje como abstract y shared (no son palabras reservadas de Ceylon) y también para incrustar la documentación de un API para el compilador de documentación:

doc "The user login action"
by "Gavin King"
throws (DatabaseException
        -> "if database access fails")
see (LogoutAction.logout)
scope (session)
action { description="Log In"; url="/login"; }
shared deprecated
void login(Request request, Response response) {
    ...
}

Modularidad

Ceylon ofrece constructos para módulos y paquetes de nivel del lenguaje, de conjunto con control de acceso de nivel de lenguaje mediante la anotación compartida la cual puede usarse para expresar visibilidad pública, privada de módulo, privada de paquete y local de bloque para los elementos de un programa. No existe en Ceylon el equivalente de protected de Java. Las dependencias entre módulos se especifican en el descriptor del módulo, que por supuesto se escribe en Ceylon:

Module module {
    name='org.jboss.example';
    version='1.0.0';
 
    doc="This module is just a silly example.
         You'll be able to find some proper
         modules in the community repository:
 
         <modules.ceylon-lang.org>
 
         Happy modularizing!"
;
 
    Import {
        name='ceylon.io';
        version='1.1.0';
    },
    Import {
        name='ceylon.dbc';
        version='1.0.2';
    }
}

El compilador de Ceylon produce directamente archivos de módulos .car en repositorios de módulos. Nunca te enfrentas con archivos .class
En ejecución, los módulos se cargan siguiendo una arquitectura de cargadores de clases de tipo peer to peer, basada en el mismo runtime de módulos que es usado por el núcleo de JBoss AS 7.

Toma el tour

Hasta aquí la introducción. Toma el tour de Ceylon para un tutorial a profundidad.

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de ezamudio

sitio oficial

De hecho se quiere tener el sitio disponible en varios idiomas; si pones este texto en el formato adecuado y mandas el pull request en github, lo podemos integrar al sitio oficial. esto es algo que ayuda mucho para la adopción del lenguaje.