Tipos en Ceylon: Un sistema de tipos con sentido

Para mi primer post acerca de Ceylon, quiero hablar un poco acerca del sistema de tipos. Una de las metas de Ceylon es la legibilidad del código; otra es tener un sistema de tipos que sea sencillo pero a la vez poderoso (Ceylon es un lenguaje de tipado estático). A continuación pretendo demostrar cómo estos dos objetivos se unen, dando como resultado código más legible, con un ejemplo sencillo: una lista heterogénea (es decir, que contiene elementos de distintos tipos). Veamos el ejemplo en un par de lenguajes de tipado estático, incluso en algunos de tipado dinámico. Primero obviamente en Java:

 

Las colecciones en Java con generics nos permiten definir colecciones de un solo tipo; si queremos almacenar tipos distintos, como en este caso enteros y cadenas, hay que definir algún supertipo en común, y pues en este caso únicamente puede ser  .

Por lo tanto, cuando recorremos la lista, el elemento sobre el cual iteramos debe ser un  . Si queremos hacer algo con él, primero hay que revisar de qué tipo es. Y aquí es donde ya se empieza a sentir incómodo el asunto: cuando utilizo  , pues ya estoy seguro de que el elemento es de ese tipo, no? Entonces, ¿por qué debo hacer un cast en un bloque de código que todos sabemos que únicamente se ejecutará cuando el elemento es un String? Como que eso sobra, ¿no? Si el compilador fuera tantito inteligente, podría hacer ese cast por nosotros.

Scala también es un lenguaje de tipado estático, y tiene una manera distinta de tratar con objetos de distintos tipos. Veamos:

 

Entonces, en Scala podemos ahorrarnos el cast; mucho mejor que en Java. La única bronca es que si esa lista viene de otro lado (nos la entrega un componente de una biblioteca externa por ejemplo), no hay manera de saber si sólo trae enteros y cadenas; podría traer cualquier otra cosa. Sólo leyendo la documentación podríamos saber. Lo mismo va para el código en Java.

Si en Java ejecutamos esa iteración con una lista que tuviera un URL, por ejemplo, simplemente se salta ese elemento porque no cae en ninguno de los dos casos. En Scala, al llegar al URL se arroja una excepción tipo   porque no está contemplado ese caso. No sé qué es peor. Obviamente la solución en Java es poner un   para manejar todos los demás casos y en Scala hay que poner un   para manejar el caso de cualquier otro tipo.

En un lenguaje dinámico como Groovy, podemos determinar el tipo y no necesitamos hacer un cast, porque la invocación al método se resolverá de manera dinámica, en tiempo de ejecución:

 

Incluso, podemos hacer uso del   de Groovy, que puede incluir el tipo de objeto:

 

Es muy importante no olvidarse del   en cada caso. Si no hubiera   entre el caso de String y el de Number, por ejemplo, entonces cada cadena se imprimiría en mayúsculas y después dos veces, porque en Groovy el operador de multiplicación aplicado a una cadena la repite tantas veces como se indique (es decir,   devuelve  ). Para manejar el caso de elementos que no sean números ni cadenas, hay que agregar al final del switch el caso  .

Bueno, pero ¿y en Ceylon?

Antes de las explicaciones, veamos el código en Ceylon:

 

Primero que nada, tenemos la declaración de una secuencia, asignada a un valor inmutable. Solamente requerimos   sin indicar el tipo porque tenemos inferencia de tipos en declaraciones locales. Para declarar secuencias podemos usar llaves; las secuencias son inmutables por default. Pero lo interesante es que en Ceylon tenemos unión e intersección de tipos;   es de tipo  . Esto es muy conveniente porque ahora ya sabemos que la secuencia puede contener solamente cadenas y enteros, aunque sean tipos dispares que no tengan nada en común más allá de la clase raíz. Sabiendo esto, no es necesario manejar el caso de que la secuencia traiga elementos de algún otro tipo, porque la definición indica que sólo son cadenas y enteros.

La unión de tipos se hace con el pipe:  , y significa que el valor puede tener cualquiera de los dos tipos; la intersección se hace con el ampersand:  , y significa que el valor debe extender ambos tipos.

La iteración sobre la secuencia es con el  , similar al de Java, pero aquí también tenemos inferencia de tipos porque es una declaración local nuevamente. Si tuviéramos que declarar el tipo de elem, es simplemente  .

Dentro del ciclo, hacemos uso del operador  , que devuelve   cuando el valor indicado es del tipo solicitado (o algún subtipo del mismo). Y si la condición se cumple, el compilador ya trata a   como un String dentro de ese bloque de código (y como Integer en el otro bloque alterno). No necesitamos un   porque ya quedó establecido que la secuencia sobre la que estamos iterando solamente puede contener enteros y cadenas.

En el release inicial M1 de Ceylon, aún no hay soporte para la sentencia  , pero la manera en que va a funcionar es una especie de mezcla entre el   de Scala y el   de Groovy:

 

El switch en Ceylon tiene otras peculiaridades, pero en este caso, lo importante es que se pueden tener casos con el operador  , para cada caso debe haber un bloque, no se necesita  , y dentro del bloque de una condición  , el valor evaluado se trata como del tipo verificado, lo cual hace innecesario un cast.

Por último, aunque no tiene mucho que ver con el sistema de tipos, aprovecho para mencionar una característica adicional del   en Ceylon: tiene  . Si el ciclo termina de manera normal (se llega al último elemento), se ejecuta el  , si está definido. Esto es muy útil para iteraciones sobre secuencias cuando se está buscando algún elemento o alguna condición específica; si se encuentra, se termina el ciclo con un simple  , pero si se recorre toda la secuencia y no se cumplió la condición, se ejecuta el  . Supongamos que queremos terminar el ciclo al encontrar un número impar en la secuencia:

 

Dado que los números de la lista son todos pares, se ejecutará el  .

Pues bien, espero que en un futuro no muy lejano esto sirva como referencia para los que quieran adentrarse a la programación con Ceylon, y para picar la curiosidad a partir de hoy mismo de quienes tengan la inquietud de conocer nuevos lenguajes de programación.

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.

Supongo que vale la pena

Supongo que vale la pena mencionar que el uso de los tipos union para la inferencia de argumentos genericos funciona bien porque Ceylon tiene covariancia para los tipos genericos. Cuando vemos una sequencia como:

 

O instanciacion de una clase generica, como:

 

El tipo princpal invuelcra un tipo union.:

  •   tiene el tipo principal  
  •   tiene el tipo principal  

Luego, observamos que   y   son tipos covariantes, entonces esto tambien es bien tipado:

 
 

Y esto de hecho es lo que queremos decir cuando utilizamos la frase "tipo principal" que es tan importante en el diseño de Ceylon: estamos diciendo que el tipo principal como   es un subtipo de cualquier otro tipo que podamos querer. Y el chiste es que el compilador puede determinar el tipo principal de   o   sin que escribamos los argumentos   o  . Compara:

 

 

Esto si es bien chingonsisimo.

Imagen de Abaddon

Interesante

Esta interesante el esquema de definición de valores usando Unión e Intersección.

Muy buena explicación Enrique.

Aguas con el Switch

Leyendo la sintaxs del switch puedo entender que siempre vas a evaluar booleanso. Si bien es cierto que el operador   siempre retorna booleano. Me parece que una manera mas clara es:

 

En este caso, si entenderia que se esta evaluando que sea String o Integer. Actualmente en java (y no es que quiero que se parezca a Java o a X lenguaje) se hace

 
y no
 

finalmente seria estetica pensarian ustedes pero no. Mi punto de vista   es uno tal cual mientras que   es   y lo mismo pasa con el operador este de ceylon que regresa booleanos

En cuanto a lo del for esta genial, no se que tan acertado sea mi opinion pero yo lo veo que es el catch del for aunque seria de la roptura del ciclo controlada porque si entras en un ArrayIndexOutOfBounsException es claro que no entraria al  . Recuerdo que Gaving comentaba algo que decia mas o menos asi: "En ceylon no hay Excepciones al menos que vengan de un codigo Java" bueno en ese caso si vendria como contradiccion a lo que digo de llamarle "catch del for" a ese  

Imagen de ezamudio

no es catch

for/else no es un catch, es un else. No veo cómo pueda arrojarse IndexOutOfBoundsException en un foreach; no tienes acceso al iterador. En Ceylon no hay NullPointerExceptions a menos que vengan de código Java.

El switch tendrá muchas más cosas; si ves toda la spec del switch ya queda claro por qué se pone   y no solamente una clase. De hecho si haces switch sobre un tipo, puedes poner casos con  ; puedes poner más condiciones aparte del puro is, yo puse un ejemplo muy simple pero créeme que va a ser algo bastante poderoso. Según entiendo, podrás hacer algo así:

 

Variantes más sofisticadas, haciendo el switch sobre un tipo y no sobre un valor:

 

Como nota al margen: El switch de Java está mucho más limitado. Apenas en la versión 7 ya se puede hacer switch sobre cadenas...

En Ceylon, String es una

En Ceylon,   es una expresion que tiene el tipo  . Puedo escribir cosas como:

 

Esto es el metamodelo "tipado estatico" de Ceylon. Bueno, aun nos falta implementarlo pero ya existe el la spec.

Entonces tiene ambigüedad si el   tambien tendrá otra forma para comparar valores en lugar de tipos. Considera:

 

Se refiere al tipo de x o a su valor?

(Aun no hemos decidido si realmente necesitamos esta otra forma. Yo personalmente casi no uso el   de Java excepto con los tipos enumerados.)

P.S. Se necesitan las cucarachitas por que   es unu expresion legal en Ceylon. No se puede eliminar las parentesis de `if`, `for`, etc.

De hecho el ultimo ejemplo

De hecho el ultimo ejemplo aqui no captura completamente lo que quiero demostrar. Mejor:

 

 

En este caso quizás podría

En este caso quizás podría ser igualmente inteligente y evaluar tipo o valor. Por ejemplo

 

¿Que quiero decir? A pues en la primera linea evaluamos x, en la segunda comparamos con  . ah pero que cosa es String, ¿un valor?, que valor tiene... ah no, es un tipo entonces podría hacer la comparación por tipo. Después, en la linea 3 comparamos con  . Pero, que cosa es 123 ¿un valor? Si, ¿tiene tipo? CLARO! 123 es de tipo Integer. Entonces digamos que se está evaluando el valor de tipo Integer no?. Es así como a mi punto de vista podría omitirse el  

Se me ocurre que intente evaluar un numero ya sea de tipo   o  . Tomando las bondades de Ceylon quizás se pueda utilizar:

 

Quizás lo mas seguro es que pongamos:   pero quizás para los decimales necesitemos hacer otra cosa.


Editado:
P.S. Lo de las cucarachitas, eso si es estetico y no creo que cause ruido, solo lo comentaba porque como dices en if, for se usan, pero me brincó que en el case si. Nada especial
Imagen de ezamudio

Builder pattern

Cierto, en el switch no se puede poner nada más   porque   es la sintaxis para construir un objeto de MiClase...

java.daba.doo, en todo caso como dices "para los decimales", podrías hacer algo así:

 

Como nota al margen, Integer es un número entero de 64 bits en Ceylon (el equivalente de Long en Java). Así que pues no hay Long (o más bien, no hay int32).

Esto es el punto: si es un

Esto es el punto: si es un valor  . Es un valor de tipo  . Tambien es un tipo. Entonces la sintaxis tiene que distinguir. No queremos in Ceylon que el compilador intente adivinar a menos que sea algo que pueda adivinar sin corner cases. (En Java tenemos que escribir   para referirse a la clase, pero en Ceylon no.)

Si, podemos escribir en Ceylon:

 
 
 

Tambien podemos escribir:

 

etc.

Imagen de Sr. Negativo

Ceylon en "español"

@Gavin King "hablando en español" ... me parece muy bien (+1)

Que bueno que se involucre en este sitio y que aporte su conocimiento. No he usado Ceylon, se ve muy parecido a Groovy y Python... por lo pronto ya lo descarge

0_o

@Mr(-) Excepto que Ceylon es

@Mr(-) Excepto que Ceylon es de un "extremo tipado seguro" cosa que esta muy lejos de Groovy y Python. Si te refieres a la fluidez del lenguaje, entonces creo que es de los mejores cumplidos que puede haber.

:)

Imagen de ezamudio

Tipado

Así es, una diferencia crucial con Groovy/Python es el sistema de tipos. Ceylon es un lenguaje de tipado estático, con mucho énfasis en la seguridad de tipos.

Tipos

Gavin King ha hecho una excelente convención de código al usar nombre completos en vez de letras sueltas para los tipos,   es muchísimo mejor que   y cuando la cosa se complica porque hay muchos tipos genericos mezclados se vuelve todavia mejor. Yo estoy haciendo eso mismo en Java y ese sencillo cambio vuelve legibles cosas ilegibles

Además, el sistema de tipos de Ceylon es una maravilla. No se si es menos poderoso que el de scala, pero es mucho más cómodo para usar y para leer. Ha demostrado que se puede ser mejor que Java sin ser mas oscuro que Java.

Aparte de eso, voya defender un poco a Java...Si yo necesitara una lista que puede tener enteros o strings, no usaria   sinó  , creando una clase Either que finja ser un union type....es mas, no se si Ceylon no hace algo asi cuando una escribe  . ¡Se puede ser mas expresivo en Java, pero es mucho mas molesto y hay que escribir un montón!

El sistema de tipos de Ceylon me encanta....si logran arreglar (reificar) los generics, es perfecto. Y si no, igual ya es excelente.

Imagen de ezamudio

reificar

Reificar generics es un objetivo a corto plazo, y según cuentan las malas lenguas, ya mero está.

En Ceylon la unión de tipos no se simula con un Either. Recuerda que los generics se van a la goma en el bytecode, sólo sirven para que el compilador pueda hacer validaciones adicionales. El compilador de Ceylon sabe de la unión e intersección de tipos y simplemente si defines una lista que recibe uno o dos o tres tipos, los considera en las validaciones (porque no nada más hay de dos, puedes hacer   si quieres).