style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">

Reseña del Taller de Ceylon: Conceptos Basicos (y experimentos no tan basicos). Primera Parte

El sábado pasado asisti al taller de Ceylon, en el taller vimos diferentes temas: Conceptos básicos, Clases, Manejo de Nulos, Uniones e Intersecciones, Funciones, Iterables

En la sección de Conceptos Básicos vimos al típico hola mundo:

void hello() {
    print("¡Holá, Mundo!");
}

En Ceylon un programa es simplemente una función de primer nivel sin parámetros.

Vimos que cuando una funcion sólo devuelve una expresión, se puede abreviar usando la "flecha gorda"

void helloName() => print(greeting("Ceylon"));

Y tambien nos introdujeron a los parametros variadicos (los que pueden aceptar multiples valores):

Integer sum(Integer* numeros) {
    variable value sum = 0; //Los valores asignables deben anotarse con "variable"
    for (x in numeros) {
        sum+=x;
    }
    return sum;
}

Una de las caracteristicas mas interesantes de Ceylon es que puede inferir el tipo de una
declaración local.

void inferredTypes() {
    Integer time = process.milliseconds;
    value nl = process.newline;
    function sqr(Float float) => float*float;
}

En ese ejemplo, el tipo de la variable "nl" es inferido sin necesidad de declararlo. Igualmente el tipo de retorno de la funcion "sqr" es inferido a partir de la expresion

En la primera parte del taller, al final del archivo que nos pasaron para enseñarnos, nos pedian que completaramosel siguiente ejercicio "Escribe un programa que imprima todos los números primos entre 1 y 100."

Aqui esta el mio:

class Primos(){

print({for (n in 2..100) if (n == 2 || { for (d in (n-1)..2) if (n % d == 0) d }.empty) n}.sequence);

}

Si le notan algun bug, sean misericordiosos, lo hice un poco a la carrera, esas expresiones "for" son "comprensiones". ¿Que son las compresnsiones? son una construccion sintactica para crear listas. Siguen la forma general de los "constructores de conjuntos" de matematicas

Antes de llegar a esa version tan depurada, escribi esta otra:

class Primos(){

Boolean esPrimo(Integer numero){
        {Integer*} divisores = { for (d in (numero-1)..2) if (numero % d == 0) d };    
        return divisores.empty;
}

print({for (n in 2..100) if (n == 2 || esPrimo(n)) n}.sequence);

}

Mas verbosa, pero en mi opinion, mucho mas facil de leer.

Cuando estaba armandola, me topé con un comportamiento curioso, el sistema de tipos de Ceylon te ayuda a "atrapar" muchos errores, pero este es un caso que me hubiera gustado que detectara y no lo hizo:

class Primos(){

Boolean esPrimo(Integer numero){
        {Integer*} divisores = { for (d in (numero-1)..2) if (numero % d == 0) d };    
        return divisores.count == 0;
}

print({for (n in 2..100) if (n == 2 || esPrimo(n)) n}.sequence);

}

La solucion es simple, no se debe usar "count" se debe usar "size" (o como en mi ejemplo final, invocar a "empty"

class Primos(){

Boolean esPrimo(Integer numero){
        {Integer*} divisores = { for (d in (numero-1)..2) if (numero % d == 0) d };    
        return divisores.size== 0;
}

print({for (n in 2..100) if (n == 2 || esPrimo(n)) n}.sequence);

}

¿Por que no funciona con count?

Por que count esta definido asi:

shared default Integer count(
            "The predicate satisfied by the elements to
             be counted."

            Boolean selecting(Element element)) {
        variable value count=0;
        for (elem in this) {
            if (exists elem) {
                if (selecting(elem)) {
                    count++;
                }
            }
        }
        return count;
    }

count es un método que recibe a otro metodo y solo "cuenta" lo elementos dentro si satisfacen una condición, el modo correcto de usar count hubiera sido pasarle un funcion. Pero yo, por mi costumbre javera/dotnetera, simplemente invoque a count, esperando que si no le paso ningun parametro, me devolviera el "conteo" de elementos. Considero que el que no lo haya hecho violo el principio de mínima sorpresa

count no es como tal erroneo, de hecho size esta definido en ceylon en terminos de count

shared default Integer size => count((Element e) => true);

Lo que básicamente aquí se esta diciendo es que la implementacion de size es una invocación a count para la que se cuentan todos los elementos.

Lo que no me queda del todo claro, es por que cuando llamo a divisores.count == 0 no me manda un error de sintaxis diciendo me que no puedo invocar a count sin pasarle una funcion filtradora de parámetro...

Se que cometí este error por que soy novato en el lenguaje pero pienso que este es un caso que con el que un buen numero de personas llegara a encontrarse... (Ceylon no esta liberado todavía... igual y pudiera quizá ser buen momento para alterar algo y poder cachar este tipo de situación... o quizá presentar algún "warning")

En general, la introducción fue muy divertida, y armar este pequeño programa "busca primos" fue muy entretenido (no me divertía así desde que empece a aprender Haskell)

En mi siguiente post les hablaré sobre lo que vimos de las Clases de Ceylon en el Taller.

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

count

La evaluación divisores.count == 0 simplemente te da false porque divisores.count te da una referencia al método count del objeto divisores; no lo estás invocando. En el taller más adelante vimos lo de referencias a funciones, pero aquí todavía no lo veíamos.

El método count no es igual a un cero, por eso te da false. Tal vez la solución sería que las referencias a métodos arrojaran una excepción al invocar equals, pero entonces 0 == divisores.count seguiría devolviendo cero. Tal vez entonces... que el typechecker no permita hacer comparaciones entre referencias a funciones? Pero entonces cuando quieras hacer una comparación entre dos referencias a funciones no vas a poder...

Finalmente cuando trabajas en el IDE, al hacer un hover sobre count te muestra la definición y ahí te puedes dar cuenta de por qué no funciona. La bronca es si no tienes IDE.

Imagen de ezamudio

por cierto

Varios de las métodos de Iterable como count, every, any, etc tienen una contraparte como función de nivel tope. Por ejemplo tu función esPrimo puede quedar de una sola línea:

Boolean esPrimo(Integer numero) =>
  count { for (d in (numero-1)..2) numero % d == 0 } == 0;

O si lo estás pensando como que un primo es un número que no tiene ningún divisor más que 1 y él mismo, entonces:

Boolean esPrimo(Integer numero) =>
  !any { for (d in (numero-1)..2) numero % d == 0 };

any es una función que recibe una lista de booleans y devuelve true si cualquiera de ellos es true. Entonces le pasas la comprensión para que evalúe cada elemento de la misma y si se encuentra un true, devuelve true (y al negarlo, esPrimo devuelve false). Si le pasas un número primo, any devuelve false, y al negarlo, esPrimo devuelve true.

POR CIERTO si quieren jugar más con Ceylon y no quieren o no pueden instalar el IDE, pueden probarlo en línea, se compila en el server a javascript que se ejecuta en su navegador: http://try.ceylon-lang.org/

Imagen de luxspes

Si todo es diferente a una referencia a funcion...

La evaluación divisores.count == 0 simplemente te da false porque divisores.count te da una referencia al método count del objeto divisores; no lo estás invocando. En el taller más adelante vimos lo de referencias a funciones, pero aquí todavía no lo veíamos.

El método count no es igual a un cero, por eso te da false.

Ok. Gracias por la aclaración. No estaba seguro de la causa raíz.

Tal vez la solución sería que las referencias a métodos arrojaran una excepción al invocar equals, pero entonces 0 == divisores.count seguiría devolviendo cero. Tal vez entonces... que el typechecker no permita hacer comparaciones entre referencias a funciones? Pero entonces cuando quieras hacer una comparación entre dos referencias a funciones no vas a poder...

Mi sugerencia ahora que me has explicado la causa seria hacer que el typechecker verifique que las referencias a funciones solo puedan ser comparadas con otras referencias a funciones.

En este caso, sugeriría que se produjera un error de tipo "Can not compare function reference "count" with constant value "0", function references can only be compared to other function references"

La justificación seria que si al comparar una referencia a función con cualquier otra cosa, el resultado siempre es "no es igual", permitir una comparación así es absurdo, seria igual que escribir directamente  if (false) ¿no?

Imagen de luxspes

Comparaciones siempre falsas

Igual y mi sugerencia tendría la limitación conceptual de que haría a las referencias a función un tipo especial.

En Ceylon (al igual que en Ruby y en Scala) es valido escribir:

1 == "hola"

Y evalúa a false. Siempre.

Mientras que en Haskell o en C# o en Java ocurre un error en tiempo de compilacion...

Uno pensaría que si se sabe desde la compilación (como en el caso de Haskell) que ningún String será jamas igual a un Integer... pues debería ser posible cacharlo... ¿no? (En caso de Scala, se le puede pedir al compilador que mande un warning en estos casos)

En el entendido de que no hay forma por código de heredar y alterar el método "equals" de una referencia a una función (y por lo tanto es un tipo "especial") entonces creo que la mejor solución seguiría seria que fuera un error de compilación... ¿no?

Imagen de ezamudio

Ambos lados

Pero recuerda que a == b se traduce como a.equals(b). Si pones la referencia al método del lado derecho, entonces qué? el compilador no sabe si lo que está del lado izquierdo puede dar true al compararse con un Callable.

Entonces terminas tratando las referencias a funciones como tipos especiales para que al final solamente resuelvas la mitad del problema, porque si escribes 0 == divs.count sigue dando false. Y no puedes restringir que NADIE pueda compararse con una ref a función.

Imagen de luxspes

No me queda claro... ¿como es que Java y C# si pueden?

Pero recuerda que a == b se traduce como a.equals(b). Si pones la referencia al método del lado derecho, entonces qué? el compilador no sabe si lo que está del lado izquierdo puede dar true al compararse con un Callable.

No me queda claro como es que entonces C# o Java no tienen problema para deducir que "hola" == 1 o 1 == "hola" son siempre falsos.

Entonces terminas tratando las referencias a funciones como tipos especiales para que al final solamente resuelvas la mitad del problema, porque si escribes 0 == divs.count sigue dando false. Y no puedes restringir que NADIE pueda compararse con una ref a función.

No veo por que no... ¿Cómo lo logran C# y Java con los strings y los ints?

Imagen de luxspes

¿Solucion? ¿Compilar a == en vez de a equals?

En Java (y en C#) el siguiente código no compila:

"Hello World"==1 //Incomparable types: String and int

Pero obviamente esto si compila:

"Hello World".equals(1)

Estoy entendiendo bien y Ceylon siempre traduce a == en "equals" ?

Y no puedes restringir que NADIE pueda compararse con una ref a función.

Si puedo, el typechecker debe poder contestarme cual es el tipo de cada lado de la comparación, por lo tanto La solución podría ser que cuando compiles en Ceylon comparaciones de referencias a funciones con otros tipos uses == en vez de equals? (Y cuando ambos lados sean referencias a funciones uses equals)

¿No?

Imagen de ezamudio

a ver

Sí, nosotros siempre traducimos == en una llamada a equals. Si quieres comparación de identidad tenemos el operador ===. Y si intentas comparar un entero y un string usando ese operador, te da el mismo error, prueba print(1==="hey"); en try.ceylon-lang.org y verás.

En cuanto a lo segundo: el typechecker sabe el tipo de cada lado de la comparación, pero equals recibe un Object, entonces no sabe si el objeto de la izquierda implementa equals de tal modo que pueda dar un true cuando lo de la derecha es una ref a función. A eso me refiero con que no puedes restringir eso. Tal vez lo dije mal. La frase correcta es "no podemos restringir al compilador para que impida hacer comparaciones entre un objeto y una ref a función porque no sabemos si la implementación del objeto del lado izquierdo pueda dar true con ciertas refs a funciones".

Pero bueno, suponte ahora que usáramos == en vez de equals cuando el lado izquierdo es una ref a función. Tu código seguiría dando false; cuál sería entonces tu queja? La misma, no? Entonces no soluciona nada el tratar de manera especial las refs a funciones...

Imagen de luxspes

Ya

Pero bueno, suponte ahora que usáramos == en vez de equals cuando el lado izquierdo es una ref a función. Tu código seguiría dando false; cuál sería entonces tu queja? La misma, no? Entonces no soluciona nada el tratar de manera especial las refs a funciones...

Ya te entendí... Mmmm

Habrá que pensar en otra cosa...

Imagen de luxspes

Colores

Lo mas sencillo que se me ocurre de entrada es que al poner divisores.count == 0 la palabra count debería aparecer de otro color en la expresión que la palabra size en la expresion divisores.size == 0 para advertir visualmente al usuario de que la semántica es distinta...

Imagen de abrahamstalin

Aclaracion == y equals

A ver quiero dejar claro dos puntos importantisimos que no debemos de perder de vista:

== compara Objetos
y equals compara contenido.

es decir si comparamos "caso" == "caso", nos va a dar falso ya que son objetos diferentes
pero si ponemos "caso".equals("caso") nos dara true ya que aunque son distintos objetos el contenido es el mismo.

existen variantes de los metodos como equalsIgnoredCase, que compara el contenido no importando mayusculas o minusculas.

espero que esta información les ayude.

Saluds.

Imagen de Sr. Negativo

Esperando la segunda parte ...

void run(){
  print("Buen post");
}
Imagen de ezamudio

aclaración sobre ==

abraham lo que mencionas es cierto en Java pero en C# el operador == invoca el método equals. Igual pasa en Ceylon. Por lo tanto "x"=="x" da true en esos lenguajes.

Y la definición correcta para Java es que == compara referencias. Y la realidad es que equals compara lo que el implementador de la clase quiera, pero pues el contrato establece que se debe definir el criterio de igualdad entre dos objetos del mismo tipo, que generalmente consiste en comparar ciertos atributos.

style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">