El lenguaje de programación ceylon

Un clasico hola mundo

Comenzemos escribiendo en nuestro editor preferido en un archivo llamado hola.ceylon el siguiente fragmento de codigo:

void hola(){
    print("hola mundo!");
}

La sintaxis es muy similar a la de java, esta es la manera de definir una función en este caso solo imprime un mensaje “Hola mundo!”, un clasico, pero en este momento no me enfocare en la sintaxis de la funcion si no en su rol dentro del programa.

Esta funcion es conocida toplevel function por que no es miembro del algun tipo de dato. Esto quiere decir que no necesitamos crear un objeto para poder utilizar la funcion hola, solo necesitas llamarla de la sig. forma:

hola()

Ceylon no tiene metodos estaticos como Java o C++ pero se pueden pensar que las funciones toplevel puden servir para el mismo proposito. La razon que las diferencia es que ceylon tiene una structura de bloques muy estricta - un bloque anidado siempre tiene acceso a las declaraciones en todos los bloques que la contienen. Esto no es el caso con los metodos estaticos de Java.

De acuerdo a la documentación, Ceylon por el momento no soporta scripting, y no podemos escribir codigo fuera de clases y funciones.

Compilacion del programa

Es importante tener un area de trabajo asi que por limpieza crearemos una carpta llamada ceylon donde mantendremos la estructura que necesita ceylon para trabajar.

Dentro de la carpeta ceylon crearemos una llamada source y dentro de ella colocaremos el archivo hola.ceylon. A continuacion mostraremos la manera de compilar el programa.

$ ceylon compile source/hola.ceylon
Note: Created module default

Durante la compilación se crearan las carpeta llamada modules/default, las cuales contendran los bytecodes de nuestro programa.

$ ceylon run --run hola default
hola mundo!

Con estos ejecutaremos el programa, la descripcion es simple:

  • run : Le indica a el comando ceylon que ejecutaremos un programa

  • –run : Le indicamos a ceylon que el punto de entrada el cual puede

    ser una funcion toplevel o una clase.

  • default : Es el nombre del modulo que ejecutaremos, por defecto es

    default.

Este modulo tienen por nombre defult.car y se encuentra en el directorio modules/default.

LA documentación oficial recomienda hecharle un ojo a la ayuda proporcionada por ceylon.

$ ceylon help compile
$ ceylon help run

Fundamentos del lenguaje

Sintaxis, como aperitivo

Sin duda los elementos basicos forman la base de estructuras complejas es por eso que antes de pasar a elementos propios de caylon debemos entender sus pilares.

Comenzemos hablando de los literales, los cuales son datos que podemos escribir en el programa y el compilador se encargara de transformalo para poder trabajar sobre el.

String literal

La forma de escribir una string literal dentro de nuestro programa, es colocando el texto deseado dentro de comillas dobles, como lo hemos hecho anteriormente en nuestro programa.

void hola() {
    print("Hola mundo!");
}

Las cadenas en ceylon tienen un comportamiento especial, por ejemplo:

void hola() {
    print("Hola
           mundo!");
}

Se mostrara como:

Hola,
Mundo!

Cabe destacar que apesar de que en la segunda linea contenia espacios hasta el primer carater de esa linea, ceylon se encarga de remover estos espacios, Esto ayuda a darle un formato al codigo mas agradable a la vista.

Es frecuente querer quitar(collapse) todos los espacios en blanco de una string literal, para ello Ceylon nos provee de una clase String, la cual tiene un atributo llamado <normalized “”>:

void hola() {
    value mensaje = "Hola,
                     mundo";
    print(mensaje.normalized);
}

El cual da una salida como la siguiente:

Hola mundo!

Documentación, como una prioridad

Siempre es bueno agregar algo de documentacón, sobre todo si son funciones imporantes como hola().

Una forma de hacerlo is usando un comentario estilo C, como el siguiente:

/* El clasico programa hola mundo*/
void hola() {
    print("Hola mundo!\n");
}

o como esta:

// El clasico programa hola mundo
void hola() {
    print("Hola mundo!\n");
}

Pero es mucho mejor usar la anotacion doc para comentarios que describen declaraciones.

doc ("El clasico hola mundo")
by ("Zoek")
see (adios)
throws (IOException)
void hola() {
    print("Hola mundo!\n");
}

Las anotaciones doc, by, see, y tagged contienen documentación que es incluida en la salida de la herramienta de documentación de Ceylon, ceylon doc.

Anotaciones como doc, by, see, throws son son palabras reservadas, ellos son solo identificadores ordinarios. Lo mismo aplica para identificadores que son parte de la definicion del lenguaje, por ejemplo: abstract, variable, shared, formal, default, actual, etc.

La anotacion doc es ubicuo(omnipresente) los parentesis y la anotacion pueden ser obviadas cuando es la primera anotación en la lista de anotaciones:

"El clasico hola mundo"
by ("Zoek")
see (adios)
throws (IOException)
void hola() {
    print("Hola mundo!\n");
}

La anotación doc pueden ser escritas on formato Markdown.

"El Clasico [Programa Hola mundo][holamundo]
 que imprime un mesaje a la consola, esta vez
 escrito en [Ceylon][].

 Este simple programa demuestra:

 1. Como definir una funcion toplevel, y
 2. como imprimir con `print()` una literal `String`.

 Tu puedes compilar y ejecutar `hello()` desde la
 linea de comandos:

    ceylon compile source/hola.ceylon
    ceylon run -run hola default

 [holamundo]: http://en.wikipedia.org/wiki/Hello_world_program
 [Ceylon]: http://ceylon-lang.com"

void hola () {
    print("Hola mundo!\n");
}

Debes tener cuidado al indentar las multistring literal pues Markdown es sensible a la primera columna en que el texto aparece.

Secuencias de escape

Dentro de las string literals, puedes usar secuencias de escape como n, t, \, “

print("\"Hola!\", dijo el programa");

Tambien puedes usar secuencias de escape hecadecimales de 2-bytes y 4-bytes.

"La constante matematica \{#03C0}, el
 el radio de la circunferencia de un circulo
 a su diametro."
Float pi=calculatePi();

"La constante matematica \{#0001D452},
 la base del logaritmo natural."
Float e=calculateE();

Las Ceylon strings son compuestas de UTF-32 caracteres, pero esto lo retomaremos mas adelante.

Strings Verbatim

Algunas veces, las interpolación de secuencias de escape puede ser molesto, por ejemplo en aquellas veces que embebemos codigo dentro de las string literals. Si utilizamos tres comillas dobles, “”“, para delimitar nuestra cadena, obtendremos una string verbatim, que puede contener backlash no escapados y comillas dobles:

print(""""Hola!", dijo el programa.""");

String Interpolacion y concatenacion

Supongamos que queremos conocer mejor a nuestro programa, que nos habla de el:

"The Hello World program ... version 1.1!"
void hello() {
    print("Hola, Este es Ceylon ``language.version``
           corriendo sobre Java ``process.vmVersion``!\n
           Me corriste en ``process.milliseconds`` ms,
           con ``process.arguments.size`` argumentos.");
}

Notese como nuestro mensaje contiene expresiones interpoladas, delemitadas por doble acentos graves, ``, estas son conocidas como string templates.

En mi maquina, este programa da la siguiente salida:

Hola, Este es Ceylon 0.5
corriendo sobre Java 1.7!

Me corriste en 1374960853214 ms,
con 0 argumentos.

Otro elemento importante hablando de cadenas es la concatenación, esta atravez del operador +:

print("Hola, Este es Ceylon " + language.version +
       "corriendo sobre Java " + process.vmVersion + "!\n" +
       "Me corriste en " + process.milliseconds.string
       + " ms, con " + process.arguments.size.string +
       " argumentos.");

Notese que hemos tenido que invocar explicitamente al atributo string para convertir expresiones numericas a cadenas. EL operador + no convierte automaticamente sus operandos a cadenas.

Lidiando con objetos que no existen

Vamos a tomar un nombre como entrada desde la linea de comandos y necesitamos cubrir el caso donde nada ha sido especidicado, esto nos da la oportunidad de explorar como los valores null son tratados en ceylon.Consideremos un overlay-verbose ejemplo para comenzar:

"Print a personalized greeting"
void hello() {
    String? name = process.arguments.first;
    String greeting;
    if (exists name) {
        greeting = "Hello, ``name``!";
    }
    else {
        greeting = "Hello, World!";
    }
    print(greeting);
}

El tipo String? indica que nombre puede contener un valor null. Entonces usaremos bloque if para manejar el caso de un valor null separandolo del de un valor no null.

Pero tambien es posible abreviar el codigo, delarando localmente al nombre dentro de la codicion del if:

String greeting;
if (exists name =
        process.arguments.first) {
    greeting = "Hello, ``name``!";
}
else {
    greeting = "Hello, World!";
}
print(greeting);

Esta es estilo preferido la mayor parte de la veces, puesto que no usaremos nombre para alguna otra cosa fuera de la condicional (pero esta no es la manera mas compacta de escribir el codigo).

Tipo opcionales

A diferencia de java, locales, parametros y atributos que pueden contener el valor null deberan de ser declaradas explicitamente como de tipo opcional (la sintaxis T?). A su vez no existe una manera de asignar un valor null a una variable local que no sea de tipo opcional, el compilador no debera permitirlo. Este es un error.

String name = null; // compile error: null is not an instance of String

El compilador de ceylon no te permitira hacer algo peligroso con un valor de yipo T? - esto es nada que puede causar un NullPointerException en Java - Sin primero revisar que el valor no es null usando un if (exists ...). El siguiente codigo tambien es un error.

String? name = process.arguments.first;
print("Hello " + name + "!");  // compile error: name is not Summable

De hecho, no es posible usar el operador == con una expresion de tipo opcional asi es que no podemos escribir lo siguiente sin causar un error.

String? name = process.arguments.first;
if ( name == null) { ... } // compile error: name is not Object

En un leguaje con tipado estatico, siempre quieres conocer que tipo tienen los objetos. Entonces, ¿Cual es el tipo de null?

La respuesta simple es: null es de tipo Null

Asi como se lee, el valor null no es valor primitivo en Ceylon, es una instacia ordinaria de una clase ordinaria Nul, al menos desde el punto de vista del sistemas de tipos de Ceylon.

Y la sintaxis String? es solo una abreviacion del tipo union: Null|String.

Dado esto, podemos entender claramente por que no podemos hacer operaciones de String en String?. Simplemente son tipos distintos. La construcción if (exists ..., reduce el tipo de nombre dentro del bloque if ` permitiendo tratarlo como de tipo `String.

(si eres de los que se preocupan por el performace, esta de mas mencionar que el compilador de Ceylon hace algun tipo de magia especial para transformar este valor a un null a nivel de la maquina virtual.

Operadores para el manejo de los null

Existen in conjunto de operadores que pueden ser de ayuda cuando se tratan con valores null. El primero es else:

String greeting = "Hello," +  (name else "World");

El operador else da como resultado:

  • El primero operador si no es null, or
  • El segundo operado si sí lo es.

Es mas conveniente manejar valores null, en casos simples. Tambien puedes crear una cadena de else:

String name = firstName else userId else "Guest";

Tambien existe un operador para producir un valor null:

String? name = !arg.trimmed.empty then arg;

El operador then produce:

  • El segundo operador si el primer operando evalua a verdadero, o
  • null si no es asi.

Tambien puedes ligar un else despues de un then para reproducir el comprotamiento del operador ternario en C:

String name = !arg.trimmed.empty then arg else "World!";

Usando el operador else, podemos simplificar nuestro ejemplo a algo mas razonable:

"Imprime un mensaje personalizado"
void hola() {
    print("Hola, ``process.arguments.first else "World"``!");
}

Despues de todo solo es una linea.

Funciones y valores

Los dos elementos mas basicos encontrados en todos los lenguages de programacion son las funciones y las variables. En ceylon, las “variables” por defecto solo se pueden asignar una vez. Esto es, que las variables no pueden ser asignadas a otro valor despues de que han sido asignadas a un valor por primera vez.

Sin embargo, usaremos la palabra value para referirnos a “variables” en general y reservar la palabra variable para referirnos a un value que explicitamente definido para ser reasignable.

String adios = "adios"; // un value
variable Integer contador = 0; // una variable

adios = "Adeu"; // compile error
count = 1; // permitido

Notese que un value que no es una variable en este sentido, puede aun ser una “variable” en el sentido que su valor varia entre dinstintas ejecuciones del programa o entres contextos dentro de la ejecuion de un programa.

Un value puede ser recalculado cada vex que es evaluado.

String nombre = { return primerNombre + " " + segundoNombre; }

Si el valor de primerNombre y segundoNombre varia entonces el valor de nombre tambien varia entre evaluaciones.

Una funcion toma un paso adeante en esta idea,El valor de una funcion depende no unicamente del contexto en que es evaluado pero tambien el argumento a los parametros.

Float sqr (Float x) { return x * x; }

En ceylon la declaración de un valor o de una funcion puede ocurrir en casi cualquier lugar : Como un toplevel, pertene a un paquete directamente, como un atributo o un metodo de una clase o como una declaracion local de bloque dentro un different value o el cuerpo de una función, De hecho, como veremos mas adelante, la declaracion de un value o funcion pueden ocurrir dentro de una expresion en algunos casos.

La declaracion de funciones es muy similar a la que probablemente hallas usado en otros lenguajes parecidos a C, pero con dos excepciones. Ceylon has:

  • Parametros por defecto, y
  • parametros variables(variadic)

Parametros por defecto

Un parametros de una funcion puede especificar un valor por defecto.

void hola (String nombre="Mundo") {
    print("Hola  ``nombre``!");
}

Entonces no necesitamos forsosamente pasarle argumentos cuando se llama a la función.

hola(); // Hola mundo!
hola("JBoss); // Hola JBoss!

Parametros por defecto deberan de ser declarados despues de los parametros requeridos en la lista de parametros.

Parametros variables

Un parametro variadic de una funcion o clase es declarado usando un asterisco posfijo, por ejemplo String*. Una funcion o clase solo puede tener un parametro variadic y debera ser el ultimo parametro.

void HolaTodos (String* nombres) {
    //...
}

Dentro del cuerpo de la funcion, el parametro nombres tiene el tipo [String*], un tipo sequence, del cual hablaremos mas tarde. Para acceder a cada uno de sus elementos podemos iterar sobre el utilizando un ciclo for.

void holaTodos (String* nombres) {
    for (nombre in nombres) {
        hola(nombre);
    }
}

Para pasar un argumento a un parametro secuenciado tenemos tres alternativas. Podemos:

  • Proveer una lista explicita de argumentos,
  • pasar un objeto iterable produciendo el argumentos,o
  • especificando un comprehencion

El primer caso es facil:

holaTodos("world", "marte", "saturno");

Para el segundo caso, Ceylon requiere utilizar el operador spread:

String[] todos = ["world", "marte", "saturno"];
holaTodos(\*todos);

Volveremos a el tercer caso, comprenhencion, mas adelante.

Fat arrows y declaracion adelante

Las expresiones en Ceylon son mas poderosas que las de Java, y dado esto es posible expresar mas en una expresion mas compacta y es por lo tanto extremadamente comun encontrar funciones y valores que simplemente evaluan y devuelven una expresion. Asi que Ceylon nos permite abreviar tales definiones de funciones y valores usando una fat arrow, =>. Por ejemplo:

String nombre => PrimerNombre + " " + ultimoNombre;
Float sqr (Float) => x*x;

Ahora es el tiempo para que te empiezes a sentir comodo con esta sintaxis, debido a que estar viendo comunmente muchos ejemplo de esto. Toma especial atencion a la diferencia entre un fat arrow:

String nombre => nombre + " " + apellido;

Y una asignación:

String nombre = nombre + " " + apellido;

En el primer ejemplo, la exprecion es recomputada cada vez que nombre es evaluado. En el segundo ejemplo la expresion es computada una sola vez y el resultado es asignado a nombre.

Tambien podemos definir uan funcion de tipo void usando una fat arrow. En nuestro primer ejemplo pudimos haber escrito hola() como lo siguiente:

voide hola() => print("Hola mundo!);

En java y C#, nos permite separar la declaracion de una variable y la iniciación de su valor. Hemos visto que esto es permitido en ceylon. Asi que podemos escribir:

String nombre;
nombre = nombrePila + " " + apellido;
print(nombre);

Pero en Ceylon tambien nos permite hacerlo con fat arrow:

Debido a un bug en M5, el siguiente ejemplo actualmente no es complatiable para la JVM.

String nombre;
nombre => nombrePila + " " + apellido;
print(nombre);

e incluso con funciones:

Float sqr(Float x);
sqr(Float x) => x*x;
print(sqr(0.01));

void hola();
hola() => print("hola mundo");
hola();

El compilador se asegura de no evaluar un valor o invocar a una funcion antes de asignarle un valor o especificar su implentación, como veremos mas adelante. (Por que si lo hacemos, deberia de resultar en un NullPointerException, el cual !Ceylon no tiene!)

Numeros

Desafotunadamente, no todos los programas son simples y elegantes com “hola mundo!”. En los negocios o computación cientifica, podemos encontrar comunmente programas que resuelven complicadas cosas con numeros. Ceylon no tiene ninguna tipo primitivo, asi que los numero son usualmente representados por las clases Integer y Float, volveremos mas tarde sobre este concepto.

las literales Float, son escritas con punto decimaly los enteros sin el.

Integer uno = 1;
Float zero = 0.0;

Incluso aunque son clases, puedes usarlos como tipicos literales numericos y operadores con ellos.

Por ejemplo, la siguiente funcion eficientemente determina si un Integer representa un numero primo:

"Determine if `n` is a prime number."
 throws (Exception, "if `n<2`")
 Boolean prime(Integer n) {
     if (n<2) {
         throw Exception("illegal argument \``n``<2");
     }
     else if (n<=3) {
         return true;
     }
     else if (n%2==0 || n%3==0) {
         return false;
     }
     else if (n<25) {
         return true;
     }
     else {
         for (b in 1..((n.float^0.5+1)/6).integer) {
             if (n%(6*b-1)==0 || n%(6*b+1)==0) {
                 return false;
             }
         }
         else {
             return true;
         }
     }
 }

Intentalo corriendo la siguiente funcion:

"Muestra una lista de todos los numeros de dos digitos primos"
void EncuentraPrimos() {
    print([ for (i in 2.99) if (prime(i)) i ]);
}

Este fue solo un pequeño rompecabezas para mantener tu interes, Explicaremos la sintaxis que usamos aqui mas adelante.

Aun hay mas

Ceylon es un lenguage orientado a objetos, asi que una gran cantindad de codigo que escribamos en Ceylon es contenido en clases. Aprendamos acerca de las clases justo ahora, antes de volver a mas conceptos basicos.