Material‎ > ‎Software‎ > ‎

XTend (intro al lenguaje)

(intro a xtend para programadores java + smalltalk)

Para complementar este apunte pueden leer este otro 

Introducción

XTend es un lenguaje de programación orientado a objetos, parte de la comunidad de proyectos de Eclipse.
Por esto, por ahora, resulta dificil trabajar en xtend con otro IDE que no sea el eclipse (en realidad es posible, pero dificil)

Ante todo, el link al sitio oficial


Ahí van a encontrar la documentación más actualizada y completa.
Acá simplemente pretendemos hacer una introducción y relacionarlo con lenguajes que ustedes ya conocen de cursadas anteriores.

Características Generales

Xtend es un lenguaje:
  • Con tipado estático: es decir que las variables tienen un tipo, y que el compilador, en "tiempo de compilación" checkea la validez de los tipos del programa. Por ejemplo que si un método espera un Integer como parámetro, no pueda yo estar pasándole un String. El término correcto para esta característica es checkeos en tiempo de compilación. Y debería resultarles conocido de lenguajes como Java.
  • Inferencia de tipo: a diferencia de Java, no hace falta declarar los tipos "siempre". El lenguaje tiene un mecanismo "inteligente" que los infiere en ciertas condiciones. Esto va a hacer que nuestro código sea bastante más conciso que en java. Más tirando a parecerse a código "smalltalk", si se quiere.
  • Con sintaxis concisa: a diferencia de java también, varios elementos de la sintaxis son opcionales, como los puntos y coma, y a veces paréntesis. Con lo cual el código se reduce bastante.
  • Con elementos de lenguajes modernos: como bloques de código, multiple dispatch, etc. De nuevo, esto reduce la cantidad de código que escribimos.
En general, para el uso que le vamos a dar en la materia, van a ver que Xtend es muy parecido a Java (en cuanto a que sigue las reglas más importantes/generales de java), pero con una sintaxis mucho más amigable.

Compatibilidad con Java

Xtend es un lenguaje 100% compatible con java:
  • código xtend puede usar código java
  • código java puede usar código xtend
Esto es porque, en realidad el "compilador" de xtend, lo que hace es generar código Java a partir de nuestro código xtend.
Una vez que se generó el código Java, se sigue la ejecución de cualquier programa Java.


               .xtend      ->    .java     ->  .class


Características Específicas

Veamos algunas características en detalle

Variables y Valores

Existen dos tipos de "variables", o mejor dicho "referencias":
  • Variables: son referencias que pueden inicializarse apuntando a un objeto, y luego reasignarse a otro. Justamente "variables"
  • Valores: referencias que nacen apuntando a un valor y no pueden ser modificadas para apuntar a otro objeto. Serían como "constantes"
La sintaxis es:

var String unString = "Pepito"
unString = "Otro String"

val String constante = "Constante"
constante = "Otro"   <----- NO COMPILA !

Ojo ! no confundir el hecho de que no se pueda modificar la "referencia" de la mutabilidad/inmutabilidad del objeto al que apunta.

Puedo tener un "val" apuntando a una colección, que es mutable.

val List miLista = unaLista

miLista = otraLista   <----- NO COMPILA: no puedo modificar la referencia

miLista.add(23)    <---- SI COMPILA: sí puedo mandarle mensajes al objeto lista y agregarle elementos

La referencia es inmutable (no se puede modificar), pero el objeto es mutable (se puede modificar), en este caso.

Inferencia del tipo de las referencias

La declaración de una variable puede omitir especificar el tipo de la misma, ya que xtend analiza el código, por ejemplo el valor al que estamos asignándola, e infiere el tipo.

Ejemplo de equivalencias

 val n = 23 val Integer n = 23
 val s = "hola" val String s = "hola"
 var Collection<String> strings = new ArrayList<String>()

 var strings = new ArrayList<String>()

Literales de Colecciones

Existe una sintaxis especifica para crear colecciones con sus elementos, sin tener que escribir el código que instancia un objeto y luego envía el mensaje "add(..)", etc.

Lista inmutable:

  1. val myList = #['Hello','World']

Set inmutable

  1. val mySet = #{'Hello','World'}

Mapa/Diccionario inmutable

  1. val myMap = #{'a' -> 1 ,'b' ->2}

Clases

La mecánica de las clases en xtend es igual que en Java. La diferencia es que la sintaxis es mucho más concisa.
Veamos un ejemplo en java

public class Golondrina {
     private int energia;

     public int getEnergia() {
          return this.energia;
     }

     public void setEnergia(int e) {
          this.energia = e;
     }

     public void volar(int kms) {
          this.energia -= kms;
     }
}

En xtend esto se puede escribir así

class Golondrina {
     int energia

     def getEnergia() {
         energia
     }

     def void setEnergia(int e) {
         energia = e
     }

     def volar(int kms) {
          energia -= kms
     }
}

Acá vemos:
  • En general no hace falta especificar la visibilidad de las cosas (public, private, etc), porque xtend ya tiene buenos "defaults". Entonces si uno no especifica nada:
    • clases: son public
    • variables de instance: private
    • métodos: public
  • Métodos:
    • se declaran con def
    • No hace falta declarar el tipo de retorno (ej: "getEnergia") ya que lo infiere mirando el código dentro, el tipo del valor que retornamos
    • No hace falta escribir return, el valor de retorno de un método será la última linea (en realidad se banca expresiones como if's que retornen cosas en ambas ramas)

@Property

El ejemplo anterior se puede simplificar aún más.
En java es bastante común que una clase tenga una "propiedad", es decir un par de métodos "getter" y "setter" que retornan una variable de instancia y la alteran respectivamente.
Esto es engorroso porque tenemos que escribir la variable de instancia y ambos métodos. 
Para evitar esta tarea tediosa, xtend tiene una annotation llamada @Property, y nos permite sólo escribir la variable de instancia.
El getter y el setter los genera automáticamente.

Nuestra clase quedaría ahora así:


class
 Golondrina {
     @Property int energia

     def volar(int kms) {
          energia -= kms
     }
}


De hecho este es el código que generará:

  1. private int _energia;
  2.  
  3. public int getEnergia() {
  4. return this._energia;
  5. }
  6.  
  7. public void setName(final int energia) {
  8. this._energia = energia;
  9. }
Nótese que al field le agrega un guión bajo como prefijo.Eso es porque en cualquier lado que escribamos "energia" ahora, en realidad va a llamar al getter.Si queremos acceder a la variable de instancia tenemos que hacer "_energia".En general eso sólo se requiere si estamos definiendo el getter o el setter manualmente nosotros (para no entrar en un ciclo infinito)

Shortcut para acceder a propiedades

Cuando usamos un objeto que tiene propiedades (par getter y setter), podemos cambiar un poco la sintaxis para que se vea más simple.
Ejemplo, en lugar de esto

val pepita = new Golondrina()

pepita.setEnergia(20)
println(pepita.getEnergia())

Podemos hacer esto:

val pepita = new Golondrina()

pepita.energia = 20       <-- cambiamos acá
println(pepita.energia)   <-- y acá

O sea una propiedad se puede acceder así

    objeto.propiedad

Y modificar así

    objeto.propiedad = valor

Ojo !  si bien parece que estamos accediendo diréctamente a la variable de instancia, no es así.
Xtend simplemente traduce esa sintaxis a la anterior. Es decir que en ambos casos estamos igualmente llamando al getter y al setter.
Es sólo un chiche sintáctico (evitamos los paréntesis y los prefijos "get"  y "set".


Eliminando Paréntesis

Incluso, cuando un método o constructor no recibe parámetros podemos evitar escribir los paréntesis.

val golondrina = new Golondrina      <-- sacamos acá

golondrina.reseteaEnergia         <--- antes teníamos que hacer golondrina.reseteaEnergia()


Constructores

Son casi iguales a los de Java, sólo que no tienen el nombre de la clase sino la palabra reservada new.
Ejemplo, lo que en Java se escribía así:

public class Golondrina {
    private int energia;

    public Golondrina() {
        this(100);
    }
    
    public Golondrina(int energia) {
        this.energia = energia;
    }

}

En xtend se escribe:

class Golondrina {
    @Property int energia

    new() {
        this(100)
    }
    
    new(int energia) {
        this.energia = energia
    }

}

Nótese que por default los constructores también son public, así que no hace falta declararlo.

(por si no se entendió, en este ejemplo tenemos dos constructores, y uno delega en el otro pasándo el valor "fijo" default de 100 puntos de energia)

Sobrescritura de métodos

Cuando extendemos una clase y queremos sobrescribir un método, no podemos definirlo con def sino que tenemos que usar la palabra reservada override

class GolondrinaEficiente extends Golondrina {

    override volar(int kms) {
        energia -= kms / 2
    }

}

Nota: existe el super igual que en java, para invocar un método de la superclase.


Archivos y clases

A diferencia de Java, en un archivo xtend se puede declarar más de una clase y/o interfaces.
Esto es ya que, con todas las cosas que vimos la sintaxis es mucho más concisa, y el código en general es más "corto". Con lo cual, se presta a escribir clases chiquitas, y tener un archivo por clase, a veces, tiende a ser molesto.

Ejemplo

package org.uqbar.ui.golondrinas

class Ave implements Voladora { 
    ... 
}
class Golondrina extends Ave {
    ...
}

interface Voladora {
    ...
}


Colecciones y Bloques

Xtend trabaja con las mismas clases de colecciones que ya vienen con Java, por ejempleo java.util.Collection, java.util.List, ArrayList, LinkedList, Map, etc.
Sin embargo, a través de un mecanismo más complejo que no vamos a ver en la materia (llamado extension methods), cuando usamos estas colecciones desde código xtend, vamos a ver que tienen más métodos !
Y son métodos bastante piola :)
Asumiendo que todos usaron Smalltalk en otra materia, van a encontrar muchos métodos que sí estaban en las colecciones de smalltalk, pero no en Java.

Éstos métodos en general reciben bloques (o también llamados closures) como parámetro.

Xtend soporta bloques.

Veamos ejemplos:

val col = #[10, 20, -2, -10]

val positivos = col.filter([int e | e > 0])

El método filter es similar al "select" de Pharo/Smalltalk. Es decir, que espera recibir un bloque de un único parámetro que retorna un booleano, indicando si el elemento cumple con la codición.

Acá vemos que la sintaxis de bloques en xtend es

   [ tipo parametro1, tipo parametro2, ...   |    código ]

El ejemplo anterior obtiene otra colección que tendrá sólo los números positivos de la original.

Incluso, utilizando la inferencia de tipos, podemos evitar declarar que el parámetro del bloque es de tipo "int".
Ya que la colección ya sabe que es de tipo Collection<int>, entonces solito el filter ya espera un bloque de tipo   (int)=>boolean

Queda así

val positivos = col.filter([e | e > 0])


Incluso, cuando un método recibe un único parámetro de tipo bloque, podemos evitar los paréntesis.
Quedanso así


val positivos = col.filter[e | e > 0]



Veamos otro ejemplo con objetos complejos, como nuestras Golondrinas

val conEnergiaPositiva = golondrinas.filter[ golondrina | golondrina.energia > 0 ]


Podemos reducir aún más el código, utilizando la idea del parámetro "implícito" o receptor implícito it.
En xtend, además del this que referencia a la instancia actual, existe otra variable similar llamada it

Este it, en el caso de un bloque representará el parámetro, siempre y cuando sea un bloque con un único parámetro.
Así que podemos evitar ponerle nombre al parámetro "golondrina" y en su lugar usar "it".

Quedaría así:

val conEnergiaPositiva = golondrinas.filter[ it.energia > 0 ]

Podemos reducir aún más el código, ya que el it se puede omitir, y dentro del bloque todos los mensajes serán enviados a it, es decir al parámetro.

Así que terminamos con esta forma:


val
 conEnergiaPositiva = golondrinas.filter[ energia > 0 ]


Todas expresiones equivalentes.
Siendo esta última la más concisa.

Bastante parecido a Smalltalk, no? ;)

Otros métodos de colecciones

Ahora que ya vimos la mecánica, vamos a pasar más rápido otros métodos útiles de las colecciones

val energias = golondrinas.map[ g | g.energia ]

O bien

val energias = golondrinas.map[ energia ]

Funciona como el "collect" de smalltalk.

ForEach

golondrinas.forEach[ g | g.volar() ]


O bien:

golondrinas.forEach[ volar ]

Ejecuta el bloque para cada elemento de la colección. No retorna nada.

ForAll

Funciona como el "collect" de smalltalk.

if ( golondrinas.forAll[ g | g.energia > 0 ]

O bien:

val booleano = golondrinas.forAll[ energia > 0 ]

Retorna true sólo si el bloque se cumple para todos los elementos


Comments