Tutorial de Scala para Programadores Java

Descargar como docx, pdf o txt
Descargar como docx, pdf o txt
Está en la página 1de 44

Tutorial de Scala para programadores Java

Introducción
Este documento provee una rápida introducción al lenguaje Scala como también a su compilador. Está
pensado para personas que ya poseen cierta experiencia en programación y quieren una vista rápida de lo que
pueden hacer con Scala. Se asume como un conocimiento básico de programación orientada a objetos,
especialmente en Java.

Un primer ejemplo
Como primer ejemplo, usaremos el programa Hola mundo estándar. No es muy fascinante, pero de esta
manera resulta fácil demostrar el uso de herramientas de Scala sin saber demasiado acerca del lenguaje.
Veamos como luce:
1. object HolaMundo {
2. def main(args: Array[String]) {
3. println("¡Hola, mundo!")
4. }
5. }
La estructura de este programa debería ser familiar para programadores Java: consiste de un método
llamado main que toma los argumentos de la línea de comando (un array de objetos String) como parámetro;
el cuerpo de este método consiste en una sola llamada al método predefinido println con el saludo amistoso
como argumento. El método main no retorna un valor (se puede entender como un procedimiento). Por lo
tanto, no es necesario que se declare un tipo retorno.
Lo que es menos familiar a los programadores Java es la declaración de object que contiene al
método main. Esa declaración introduce lo que es comúnmente conocido como objeto singleton, que es una
clase con una sola instancia. Por lo tanto, dicha construcción declara tanto una clase
llamada HolaMundo como una instancia de esa clase también llamada HolaMundo. Esta instancia es creada
bajo demanda, es decir, la primera vez que es utilizada.
El lector astuto notará que el método main no es declarado como static. Esto es así porque los miembros
estáticos (métodos o campos) no existen en Scala. En vez de definir miembros estáticos, el programador de
Scala declara estos miembros en un objeto singleton.

Compilando el ejemplo
Para compilar el ejemplo utilizaremos scalac, el compilador de Scala. scalac funciona como la mayoría de
los compiladores. Toma un archivo fuente como argumento, algunas opciones y produce uno o varios
archivos objeto. Los archivos objeto que produce son archivos class de Java estándar.
Si guardamos el programa anterior en un archivo llamado HolaMundo.scala, podemos compilarlo
ejecutando el siguiente comando (el símbolo mayor > representa el prompt del shell y no debe ser escrita):
1. > scalac HolaMundo.scala
Esto generará algunos archivos class en el directorio actual. Uno de ellos se llamará HolaMundo.class y
contiene una clase que puede ser directamente ejecutada utilizando el comando scala, como mostramos en la
siguiente sección.

Ejecutando el ejemplo
Una vez compilado, un programa Scala puede ser ejecutado utilizando el comando scala. Su uso es muy
similar al comando java utilizado para ejecutar programas Java, y acepta las mismas opciones. El ejemplo de
arriba puede ser ejecutado utilizando el siguiente comando, que produce la salida esperada:
1. > scala -classpath . HolaMundo
2.
3. ¡Hola, mundo!

Interacción con Java


Una de las fortalezas de Scala es que hace muy fácil interactuar con código Java. Todas las clases del
paquete java.lang son importadas por defecto, mientras otras necesitan ser importadas explícitamente.
Veamos un ejemplo que demuestra esto. Queremos obtener y formatear la fecha actual de acuerdo a
convenciones utilizadas en un país específico, por ejemplo, Francia.
Las librerías de clases de Java definen clases de utilería poderosas, como Date y DateFormat. Ya que Scala
interacciona fácilmente con Java, no es necesario implementar estas clases equivalentes en las librerías de
Scala –podemos simplemente importar las clases de los correspondientes paquetes de Java:
1. import java.util.{Date, Locale}
2. import java.text.DateFormat
3. import java.text.DateFormat._
4.
5. object FrenchDate {
6. def main(args: Array[String]) {
7. val ahora = new Date
8. val df = getDateInstance(LONG, Locale.FRANCE)
9. println(df format ahora)
10. }
11. }
Las declaraciones de importación de Scala lucen muy similares a las de Java, sin embargo, las primeras son
bastante más poderosas. Múltiples clases pueden ser importadas desde el mismo paquete al encerrarlas en
llaves como se muestra en la primera línea. Otra diferencia es que podemos importar todos los nombres de un
paquete o clase, utilizando el carácter guión bajo ( _) en vez del asterisco ( *). Eso es porque el asterisco es un
identificador válido en Scala (quiere decir que por ejemplo podemos nombrar a un método *), como veremos
más adelante.
La declaración import en la tercera línea por lo tanto importa todos los miembros de la clase DateFormat.
Esto hace que el método estático getDateInstance y el campo estático LONGsean directamente visibles.
Dentro del método main primero creamos una instancia de la clase Date la cual por defecto contiene la fecha
actual. A continuación, definimos un formateador de fechas utilizando el método
estático getDateInstance que importamos previamente. Finalmente, imprimimos la fecha actual
formateada de acuerdo a la instancia de DateFormat que fue “localizada”. Esta última línea muestra una
propiedad interesante de la sintaxis de Scala. Los métodos que toman un solo argumento pueden ser usados
con una sintaxis de infijo Es decir, la expresión
1. df format ahora
es solamente otra manera más corta de escribir la expresión:

1. df.format(ahora)
Esto parece tener como un detalle sintáctico menor, pero tiene importantes consecuencias, una de ellas la
exploraremos en la próxima sección.
Para concluir esta sección sobre la interacción con Java, es importante notar que es también posible heredar
de clases Java e implementar interfaces Java directamente en Scala.

Todo es un objeto
Scala es un lenguaje puramente orientado a objetos en el sentido de que todo es un objeto, incluyendo
números o funciones. Difiere de Java en este aspecto, ya que Java distingue tipos primitivos
(como boolean e int) de tipos referenciales, y no nos permite manipular las funciones como valores.

Los números son objetos


Ya que los números son objetos, estos también tienen métodos. De hecho, una expresión aritmética como la
siguiente:

1. 1 + 2 * 3 / x
Consiste exclusivamente de llamadas a métodos, porque es equivalente a la siguiente expresión, como vimos
en la sección anterior:

1. (1).+(((2).*(3))./(x))
Esto también indica que +, *, etc. son identificadores válidos en Scala.
Los paréntesis alrededor de los números en la segunda versión son necesarios porque el analizador léxico de
Scala usa la regla de “mayor coincidencia”. Por lo tanto partiría la siguiente expresión:

1. 1.+(2)
En estas partes: 1., +, y 2. La razón que esta regla es elegida es porque 1. es una coincidencia válida y es
mayor que 1, haciendo a este un Double en vez de un Int. Al escribir la expresión así:
1. (1).+(2)
previene que el 1 sea tomado como un Double.

Las funciones son objetos


Tal vez suene más sorprendente para los programadores Java, las funciones en Scala también son objetos. Por
lo tanto, es posible pasar funciones como argumentos, almacenarlas en variables, y retornarlas desde otras
funciones. Esta habilidad de manipular funciones como valores es una de los valores fundamentales de un
paradigma de programación muy interesante llamado programación funcional.
Como un ejemplo muy simple de por qué puede ser útil usar funciones como valores consideremos una
función temporizador (o timer, en inglés) cuyo propósito es realizar alguna acción cada un segundo. ¿Cómo
pasamos al temporizador la acción a realizar? Bastante lógico, como una función. Este simple concepto de
pasar funciones debería ser familiar para muchos programadores: es generalmente utilizado en código
relacionado con Interfaces gráficas de usuario (GUIs) para registrar “retrollamadas” (call-back en inglés) que
son invocadas cuando un evento ocurre.
En el siguiente programa, la función del temporizador se llama unaVezPorSegundo y recibe una función
call-back como argumento. El tipo de esta función es escrito de la siguiente manera: () => Unit y es el tipo
de todas las funciones que no toman argumentos ni retornan valores (el tipo Unit es similar a void en
Java/C/C++). La función principal de este programa simplemente invoca esta función temporizador con una
call-back que imprime una sentencia en la terminal. En otras palabras, este programa imprime
interminablemente la sentencia “El tiempo vuela como una flecha” cada segundo.
1. object Temporizador {
2. def unaVezPorSegundo(callback: () => Unit) {
3. while (true) { callback(); Thread sleep 1000 }
4. }
5. def tiempoVuela() {
6. println("El tiempo vuela como una flecha...")
7. }
8. def main(args: Array[String]) {
9. unaVezPorSegundo(tiempoVuela)
10. }
11. }
Nota: si nunca tuviste experiencias previas con programación funcional te recomiendo que te tomes unos
segundos para analizar cuando se utilizan paréntesis y cuando no en los lugares donde aparece *callback*.
Por ejemplo, dentro de la declaración de  unaVezPorSegundo no aparece, ya que se trata de la función
como un “valor”, a diferencia de cómo aparece dentro del método, ya que en ese caso se la está invocando
(por eso los paréntesis). Note that in order to print the string, we used the predefined
method println instead of using the one from System.out.

Funciones anónimas
El programa anterior es fácil de entender, pero puede ser refinado aún más. Primero que nada, es interesante
notar que la función tiempoVuela está definida solamente para ser pasada posteriormente a la
función unaVezPorSegundo. Tener que nombrar esa función, que es utilizada solamente una vez parece un
poco innecesario y sería bueno poder construirla justo cuando sea pasada a unaVezPorSegundo. Esto es
posible en Scala utilizando funciones anónimas, que son exactamente eso: funciones sin nombre. La versión
revisada de nuestro temporizador utilizando una función anónima luce así:
1. object TemporizadorAnonimo {
2. def unaVezPorSegundo(callback: () => Unit) {
3. while (true) { callback(); Thread sleep 1000 }
4. }
5. def main(args: Array[String]) {
6. unaVezPorSegundo(
7. () => println("El tiempo vuela como una flecha...")
8. )
9. }
10. }
La presencia de una función anónima en este ejemplo es revelada por la flecha a la derecha =>que separa los
argumentos de la función del cuerpo de esta. En este ejemplo, la lista de argumentos está vacía, como se ve
por el par de paréntesis vacíos a la izquierda de la flecha. El cuerpo de la función es el mismo que
en tiempoVuela del programa anterior.

Clases
Como hemos visto anteriormente, Scala es un lenguaje orientado a objetos, y como tal tiene el concepto de
Clase (en realidad existen lenguajes orientados a objetos que no cuentan con el concepto de clases, pero Scala
no es uno de ellos). Las clases en Scala son declaradas utilizando una sintaxis que es cercana a la de Java. Una
diferencia importante es que las clases en Scala pueden tener parámetros. Ilustramos esto en el siguiente
ejemplo, la definición de un número complejo:

1. class Complejo(real: Double, imaginaria: Double) {


2. def re() = real
3. def im() = imaginaria
4. }
Esta clase compleja toma dos argumentos, que son las partes reales e imaginarias de un número complejo.
Estos argumentos deben ser pasados cuando se crea una instancia de la clase Complejo, de la siguiente
manera:
1. new Complejo(1.5, 2.3)
La clase contiene dos métodos llamados re e im, que proveen acceso a las dos partes del número.
Debe notarse que el tipo de retorno de estos dos métodos no está expresado explícitamente. Será inferido
automáticamente por el compilador, que primero mira la parte derecha de estos métodos y puede deducir que
ambos retornan un valor de tipo Double.
El compilador no es siempre capaz de inferir los tipos como lo hace aquí, y desafortunadamente no existe una
regla simple para saber cuándo será y cuándo no. En la práctica, esto generalmente no es un problema ya que
el compilador se queja cuando no es capaz de inferir un tipo que no fue explícitamente fijado. Como regla
simple, los programadores de Scala novatos deberían tratar de omitir las declaraciones de tipos que parecen
ser simples de deducir del contexto y ver si el compilador no lanza errores. Después de algún tiempo, el
programador debería tener una buena idea de cuando omitir tipos y cuando explicitarlos.

Métodos sin argumentos


Un pequeño problema de los métodos re e im es que para poder llamarlos es necesario agregar un par de
paréntesis vacíos después de sus nombres, como muestra el siguiente ejemplo:
1. object NumerosComplejos {
2. def main(args: Array[String]) {
3. val c = new Complejo(1.2, 3.4)
4. println("Parte imaginaria: " + c.im())
5. }
6. }
Sería mejor poder acceder las partes imaginarias y reales como si fueran campos, sin poner los paréntesis
vacíos. Esto es perfectamente realizable en Scala, simplemente al definirlos como métodos sin argumentos.
Tales métodos difieren de los métodos con cero o más argumentos en que no tienen paréntesis después de su
nombre, tanto en la definición como en el uso. Nuestra clase Complejo puede ser reescrita así:
1. class Complejo(real: Double, imaginaria: Double) {
2. def re = real
3. def im = imaginaria
4. }

Herencia y sobreescritura
Todas las clases en Scala heredan de una superclase. Cuando ninguna superclase es especificada, como es el
caso de Complejo se utiliza implícitamente scala.AnyRef.
Es posible sobreescribir métodos heredados de una superclase en Scala. Aunque es necesario explicitar
específicamente que un método sobreescribe otro utilizando el modificador override, de manera de evitar
sobreescrituras accidentales. Como ejemplo, nuestra clase Complejo puede ser aumentada con la redefinición
del método toString heredado de Object.
1. class Complejo(real: Double, imaginaria: Double) {
2. def re = real
3. def im = imaginaria
4. override def toString() =
5. "" + re + (if (im < 0) "" else "+") + im + "i"
6. }

Clases Case y Reconocimiento de patrones


Un tipo de estructura de datos que aparece seguido en programas es el Árbol. Por ejemplo, los intérpretes y
compiladores usualmente representan los programas internamente como árboles; los documentos XML son
árboles; y muchos otros tipos de contenedores están basados en árboles, como los árboles rojo y negro.

Ahora examinaremos cómo estos árboles son representados y manipulados en Scala mediante un pequeño
programa que oficie de calculadora. El objetivo de este programa es manipular expresiones aritméticas
simples compuestas de sumas de enteros y variables. Dos ejemplos de estas expresiones pueden
ser: 1+2 y (x+x)+(7+y).
Primero tenemos que decidir una representación para tales expresiones. La más natural es un árbol, donde los
nodos son las operaciones (la adición en este caso) y las hojas son valores (constantes o variables).

En Java, un árbol así sería representado utilizando una superclase abstracta para los árboles, y una subclase
concreta por nodo u hoja. En un lenguaje de programación funcional uno utilizaría un tipo de dato algebraico
para el mismo propósito. Scala provee el concepto de clases case que está en el medio de los dos conceptos
anteriores. Aquí mostramos como pueden ser usadas para definir el tipo de los árboles en nuestro ejemplo:
1. abstract class Arbol
2. case class Sum(l: Arbol, r: Arbol) extends Arbol
3. case class Var(n: String) extends Arbol
4. case class Const(v: Int) extends Arbol
El hecho de que las clases Sum, Var y Const sean declaradas como clases case significa que dififieren de las
clases normales en varios aspectos:
 no es obligatorio utilizar la palabra clave new para crear instancias de estas clases (es decir, se puede
escribir Const(5) en lugar de new Const(5)),
 se crea automáticamente un “getter” (un método para obtener el valor) para los parámetros utilizados
en el constructor (por ejemplo es posible obtener el valor de v de una instancia cde la clase Const de
la siguiente manera: c.v),
 se proveen definiciones por defecto de los métodos equals y hashCode, que trabajan sobre la
estructura de las instancias y no sobre su identidad,
 se crea una definición por defecto del método toString que imprime el valor de una forma “tipo
código) (ej: la expresión del árbol x+1 se imprimiría Sum(Var(x),Const(1))),
 las instancias de estas clases pueden ser descompuestas mediante reconocimiento de
patrones (pattern matching) como veremos más abajo.
Ahora que hemos definido el tipo de datos para representar nuestra expresión aritmética podemos empezar
definiendo operaciones para manipularlas. Empezaremos con una función para evaluar una expresión en
un entorno. El objetivo del entorno es darle valores a las variables. Por ejemplo, la expresión x+1 evaluada en
un entorno que asocia el valor 5 a la variable x, escrito { x -> 5 }, da como resultado 6.
Por lo tanto, tenemos que encontrar una manera de representar entornos. ¡Podríamos por supuesto utilizar
alguna estructura de datos asociativa como una tabla hash, pero podemos directamente utilizar funciones! Un
entorno realmente no es nada más que una función la cual asocia valores a variables. El entorno  { x ->
5 } mostrado anteriormente puede ser fácilmente escrito de la siguiente manera en Scala:
1. { case "x" => 5 }
Esta notación define una función la cual, dado un string "x" como argumento retorna el entero 5, y falla con
una excepción si no fuera así.
Antes de escribir la función evaluadora, démosle un nombre al tipo de los entornos. Podríamos por supuesto
simplemente utilizar String => Int para los entornos, pero simplifica el programa introducir un nombre
para este tipo, y hace que los futuros cambios sean más fáciles. Esto lo realizamos de la siguiente manera:
1. type Entorno = String => Int
De ahora en más, el tipo Entorno puede ser usado como un alias del tipo de funciones definidas
de String a Int.
Ahora podemos dar la definición de la función evaluadora. Conceptualmente, es muy sencillo: el valor de una
suma de dos expresiones es simplemente la suma de los valores de estas expresiones; el valor de una variable
es obtenido directamente del entorno; y el valor de una constante es la constante en sí misma. Expresar esto
en Scala no resulta para nada difícil:

1. def eval(a: Arbol, ent: Entorno): Int = a match {


2. case Sum(i, d) => eval(i, ent) + eval(d, env)
3. case Var(n) => ent(n)
4. case Const(v) => v
5. }
Esta función evaluadora función realizando un reconocimiento de patrones (pattern matching) en el árbol a.
Intuitivamente, el significado de la definición de arriba debería estar claro:
1. Primero comprueba si el árbol t es una Sum, y si lo es, asocia el sub-arbol izquierdo a una nueva
variable llamada i y el sub-arbol derecho a la variable r, y después procede con la evaluación de la
expresión que sigue a la flecha (=>); esta expresión puede (y hace) uso de las variables asociadas por
el patrón que aparece del lado izquierdo de la flecha.
2. si la primera comprobación (la de Sum) no prospera, es decir que el árbol no es una Sum, sigue de
largo y comprueba si a es un Var; si lo es, asocia el nombre contenido en el nodo Var a la
variable n y procede con la parte derecha de la expresión.
3. si la segunda comprobación también falla, resulta que a no es un Sum ni un Var, por lo tanto,
comprueba que sea un Const, y si lo es, asocia el valor contenido en el nodo Const a la variable v y
procede con el lado derecho.
4. finalmente, si todas las comprobaciones fallan, una excepción es lanzada para dar cuenta el fallo de la
expresión; esto puede pasar solo si existen más subclases de Arbol.
Hemos visto que la idea básica del reconocimiento de patrones es intentar coincidir un valor con una serie de
patrones, y tan pronto como un patrón coincida, extraer y nombrar las varias partes del valor para finalmente
evaluar algo de código que típicamente hace uso de esas partes nombradas.
Un programador con experiencia en orientación a objetos puede preguntarse por qué no definimos eval como
un método de la clase Arbol y sus subclases. En realidad, podríamos haberlo hecho, ya que Scala permite la
definición de métodos en clases case tal como en clases normales. Por lo tanto, decidir en usar reconocimiento
de patrones o métodos es una cuestión de gustos, pero también tiene grandes implicancias en cuanto a la
extensibilidad:
 cuando usamos métodos, es fácil añadir un nuevo tipo de nodo ya que esto puede ser realizado
simplemente al definir una nueva subclase de Arbol; por otro lado, añadir una nueva operación para
manipular el árbol es tedioso, ya que requiere la modificación en todas las subclases.
 cuando utilizamos reconocimiento de patrones esta situación es inversa: agregar un nuevo tipo de
nodo requiere la modificación de todas las funciones que hacen reconocimiento de patrones sobre el
árbol, para tomar en cuenta un nuevo nodo; pero por otro lado agregar una nueva operación fácil,
solamente definiendolo como una función independiente.

Para explorar un poco más esto de pattern matching definamos otra operación aritmética: derivación
simbólica. El lector recordará las siguientes reglas sobre esta operación:
1. la derivada de una suma es la suma de las derivadas,
2. la derivada de una variable v es uno (1) si v es la variable relativa a la cual la derivada toma lugar, y
cero (0) de otra manera,
3. la derivada de una constante es cero (0).
Estas reglas pueden ser traducidas casi literalmente en código Sclaa, para obtener la siguiente definición.

1. def derivada(a: Arbol, v: String): Arbol = a match {


2. case Sum(l, r) => Sum(derivada(l, v), derivada(r, v))
3. case Var(n) if (v == n) => Const(1)
4. case _ => Const(0)
5. }
Esta función introduce dos nuevos conceptos relacionados al pattern matching. Primero que nada, la
expresión case para variables tienen una guarda, una expresión siguiendo la palabra clave if. Esta guarda
previene que el patrón concuerde al menos que la expresión sea verdadera. Aquí es usada para asegurarse que
retornamos la constante 1 solo si el nombre de la variable siendo derivada es el mismo que la variable
derivada v. El segundo concepto nuevo usado aquí es el comodín, escrito con el guión bajo _, que coincide
con cualquier valor que aparezca, sin darle un nombre.
No hemos explorado el completo poder del pattern matching aún, pero nos detendremos aquí para mantener
este documento corto. Todavía nos queda pendiente ver cómo funcionan las dos funciones de arriba en un
ejemplo real. Para ese propósito, escribamos una función main simple que realice algunas operaciones sobre
la expresión (x+x)+(7+y): primero computa su valor en el entorno { x -> 5, y -> 7 }  y después
computa su derivada con respecto a x y después a y.
1. def main(args: Array[String]) {
2. val exp: Arbol = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y")))
3. val ent: Entonrno = { case "x" => 5 case "y" => 7 }
4. println("Expresión: " + exp)
5. println("Evaluación con x=5, y=7: " + eval(exp, ent))
6. println("Derivada con respecto a x:\n " + derivada(exp, "x"))
7. println("Derivada con respecto a y:\n " + derivada(exp, "y"))
8. }
Al ejecutar este programa obtenemos el siguiente resultado:

1. Expresión: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y)))
2. Evaluación con x=5, y=7: 24
3. Derivada con respecto a x:
4. Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0)))
5. Derivada con respecto a y:
6. Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1)))
Al examinar la salida vemos que el resultado de la derivada debería ser simplificado antes de ser presentado al
usuario. Definir una función de simplificación básica utilizando reconocimiento de patrones es un problema
interesante (y, por no decir complejo, que necesita una solución astuta), lo dejamos para un ejercicio para el
lector.

Traits
Nota: La palabra Trait(/treɪt/, pronunciado Treit) puede ser traducida literalmente como “Rasgo”. De todas
maneras, decido utilizar la notación original por ser un concepto muy arraigado a Scala
Aparte de poder heredar código de una super clase, una clase en Scala puede también importar código de uno
o varios traits.
Tal vez la forma más fácil para un programador Java de entender qué son los traits es verlos como interfaces
que también pueden contener código. En Scala, cuando una clase hereda de un trait, implementa la interface
de ese trait, y hereda todo el código contenido en el trait.

Para ver la utilidad de los traits, veamos un ejemplo clásico: objetos ordenados. Generalmente es útil tener la
posibilidad de comparar objetos de una clase dada entre ellos, por ejemplo, para ordenarlos. En Java, los
objetos que son comparables implementan la interfaz Comparable. En Scala, podemos hacer algo un poco
mejor que en Java al definir un trait equivalente Comparable que invocará a Ord.
Cuando comparamos objetos podemos utilizar seis predicados distintos: menor, menor o igual, igual, distinto,
mayor o igual y mayor. De todas maneras, definir todos estos es fastidioso, especialmente que cuatro de estos
pueden ser expresados en base a los otros dos. Esto es, dados los predicados “igual” y “menor” (por ejemplo),
uno puede expresar los otros. En Scala, todas estas observaciones pueden ser fácilmente capturadas mediante
la siguiente declaración de un Trait:

1. trait Ord {
2. def < (that: Any): Boolean
3. def <=(that: Any): Boolean = (this < that) || (this == that)
4. def > (that: Any): Boolean = !(this <= that)
5. def >=(that: Any): Boolean = !(this < that)
6. }
Esta definición crea un nuevo tipo llamado Ord el cual juega el mismo rol que la interfaz Comparable, como
también provee implementaciones de tres predicados en términos de un cuarto, abstracto. Los predicados para
igualidad y su inverso (distinto, no igual) no aparecen aquí ya que por defecto están presenten en todos los
objetos.
El tipo Any el cual es usado arriba es el supertipo de todos los otros tipos en Scala. Puede ser visto como una
versión más general del tipo Object en Java, ya que Any también es supertipo de Int, Float, etc. cosa que
no se cumple en Java (int por ejemplo es un tipo primitivo).
Para hacer a un objeto de la clase comparable es suficiente definir los predicados que comprueban la igualdad
y la inferioridad y mezclar la clase Ord de arriba. Como un ejemplo, definamos una clase Fecha que
representa fechas en el calendario gregoriano.
1. class Fecha(d: Int, m: Int, a: Int) extends Ord {
2. def anno = a
3. def mes = m
4. def dia = d
5. override def toString(): String = anno + "-" + mes + "-" + dia
La parte importante aquí es la declaración extends Ord la cual sigue al nombre de la clase y los parámetros.
Declara que la clase Fecha hereda del trait Ord.
Después redefinimos el método equals, heredado de Object, para comparar correctamente fechas mediante
sus campos individuales. La implementación por defecto de equals no es utilizable, porque como en Java,
compara los objetos físicamente. Por lo tanto, llegamos a esto:
1. override def equals(that: Any): Boolean =
2. that.isInstanceOf[Fecha] && {
3. val o = that.asInstanceOf[Fecha]
4. o.dia== dia && o.mes == mes && o.anno== anno
5. }
Este método utiliza el método predefinido isInstanceOf (“es instancia de”) y asInstanceOf(“como
instancia de”). El primero isInstanceOf se corresponde con el operador java instanceOf y
retorna true si y solo si el objeto en el cual es aplicado es una instancia del tipo dado. El
segundo, asInstanceOf, corresponde al operador de casteo en Java: si el objeto es una instancia de un tipo
dado, esta es vista como tal, de otra manera se lanza una excepción ClassCastException.
Finalmente, el último método para definir es el predicado que comprueba la inferioridad. Este hace uso de
otro método predefinido, error que lanza una excepción con el mensaje de error provisto.
1. def <(that: Any): Boolean = {
2. if (!that.isInstanceOf[Fecha])
3. error("no se puede comparar" + that + " y una fecha")
4.
5. val o = that.asInstanceOf[Fecha]
6. (anno < o.anno) ||
7. (anno== o.anno && (mes < o.mes ||
8. (mes == o.mes && dia < o.dia)))
9. }
Esto completa la definición de la clase Fecha. Las instancias de esta clase pueden ser vistas tanto como
fechas o como objetos comparables. Además, todas ellas definen los seis predicados de comparación
mencionados arriba: equals y < porque aparecen directamente en la definición de la clase Fecha y los otros
porque son heredados del trait Ord.
Los traits son útiles en muchas otras más situaciones que las aquí mostrada, pero discutir sus aplicaciones está
fuera del alcance de este documento.

Tipos Genéricos
Nota: El diseñador de los tipos genéricos en Java fue nada más ni nada menos que Martin Odersky, el
diseñador de Scala.
La última característica de Scala que exploraremos en este tutorial es la de los tipos genéricos. Los
programadores de Java deben estar bien al tanto de los problemas que genera la falta de genéricos en su
lenguaje, lo cual es solucionado en Java 1.5.

Los tipos genéricos proveen al programador la habilidad de escribir código parametrizado por tipos. Por
ejemplo, escribir una librería para listas enlazadas se enfrenta al problema de decidir qué tipo darles a los
elementos de la lista. Ya que esta lista está pensada para ser usada en diferentes contextos, no es posible
decidir que el tipo de elementos sea, digamos, Int. Esto sería completamente arbitrario y muy restrictivo.
Los programadores Java cuentan como último recurso con Object, que es el supertipo de todos los objetos.
Esta solución de todas maneras está lejos de ser ideal, ya que no funciona con tipos primitivos
(int, long, float, etc.) e implica que el programador tenga que realizar muchos casteos de tipos en su
programa.
Scala hace posible definir clases genéricas (y métodos) para resolver este problema. Examinemos esto con un
ejemplo del contenedor más simple posible: una referencia, que puede estar tanto vacía como apuntar a un
objeto de algún tipo.

1. class Referencia[T] {
2. private var contenido: T = _
3. def set(valor: T) { contenido = valor }
4. def get: T = contenido
5. }
La clase Referencia es parametrizada por un tipo llamado T, que es el tipo de sus elementos. Este tipo es
usado en el cuerpo de la clase como el tipo de la variable contenido, el argumento del método set y el tipo
de retorno del método get.
El ejemplo anterior introduce a las variables en Scala, que no deberían requerir mayor explicación. Es
interesante notar que el valor inicial dado a la variable contenido es _, que representa un valor por defecto.
Este valor por defecto es 0 para tipos numéricos, false para tipos Boolean,() para el
tipo Unit y null para el resto de los objetos.
Para utilizar esta clase Referencia, uno necesita especificar qué tipo utilizar por el parámetro T, es decir, el
tipo del elemento contenido por la referencia. Por ejemplo, para crear y utilizar una referencia que contenga
un entero, podríamos escribir lo siguiente:
1. object ReferenciaEntero {
2. def main(args: Array[String]) {
3. val ref = new Referencia[Int]
4. ref.set(13)
5. println("La referencia tiene la mitad de " + (ref.get * 2))
6. }
7. }
Como puede verse en el ejemplo, no es necesario castear el valor retornado por el método get antes de
usarlo como un entero. Tampoco es posible almacenar otra cosa que no sea un entero en esa referencia en
particular, ya que fue declarada como contenedora de un entero.

Conclusión
Scala es un lenguaje tremendamente poderoso que ha sabido heredar las mejores cosas de cada uno de los
lenguajes más exitosos que se han conocido. Java no es la excepción, y comparte muchas cosas con este. La
diferencia que vemos es que, para cada uno de los conceptos de Java, Scala los aumenta, refina y mejora.
Poder aprender todas las características de Scala nos equipa con más y mejores herramientas a la hora de
escribir nuestros programas. Si bien la programación funcional no ha sido una característica de Java, el
programador experimentado puede notar la falta de soporte de este paradigma en múltiples ocasiones. El solo
pensar en el código necesario para proveer a un JButton con el código que debe ejecutar al ser presionado
nos muestra lo necesario que sería contar con herramientas funcionales. Recomendamos entonces tratar de ir
incorporando estas características, por más que sea difícil para el programador Java al estar tan acostumbrado
al paradigma imperativo de este lenguaje.
Este documento dio una rápida introducción al lenguaje Scala y presento algunos ejemplos básicos. El lector
interesado puede seguir, por ejemplo, leyendo el Tutorial de Scala que figura en el sitio de documentación,
o Scala by Example (en inglés). También puede consultar la especificación del lenguaje cuando lo desee.
Introduction
Scala es un lenguaje de programación moderno multi-paradigma diseñado para expresar patrones de
programación comunes de una forma concisa, elegante, y de tipado seguro. Integra fácilmente características
de lenguajes orientados a objetos y funcionales.

Scala es orientado a objetos


Scala es un lenguaje puramente orientado a objetos en el sentido de que todo es un objeto. Los tipos y
comportamientos de objetos son descritos por clases y traits (que podría ser traducido como un “rasgo”). Las
clases pueden ser extendidas a través de subclases y un mecanismo flexible de composición mezclada que
provee un claro remplazo a la herencia múltiple.

Scala es funcional
Scala es también un lenguaje funcional en el sentido que toda función es un valor. Scala provee una sintaxis
ligera para definir funciones anónimas. Soporta funciones de primer orden, permite que las funciones
sean anidadas, y soporta currying. Las clases caso de Scala y las construcciones incorporadas al lenguaje
para reconocimiento de patrones modelan tipos algebráicos usados en muchos lenguajes de programación
funcionales.
Además, la noción de reconocimiento de patrones de Scala se puede extender naturalmente al procesamiento
de datos XML con la ayuda de patrones de expresiones regulares. En este contexto, seq
comprehensions resultan útiles para formular consultas. Estas características hacen a Scala ideal para
desarrollar aplicaciones como Web Services.

Scala estáticamente tipado


Scala cuenta con un expresivo sistema de tipado que fuerza estáticamente las abstracciones a ser usadas en
una manera coherente y segura. En particular, el sistema de tipado soporta: * Clases genéricas * anotaciones
variables, * límites de tipado superiores e inferiores, * clases internas y tipos abstractos como miembros de
objetos, * tipos compuestos * auto-referencias explicitamente tipadas * vistas * métodos polimórficos
El mecanismo de inferencia de tipos locales se encarga de que el usuario no tengan que anotar el programa
con información redundante de tipado. Combinadas, estas características proveen una base poderosa para el
reuso seguro de abstracciones de programación y para la extensión segura (en cuanto a tipos) de software.

Scala es extensible
En la práctica el desarrollo de aplicaciones específicas para un dominio generalmente requiere de “Lenguajes
de dominio específico” (DSL). Scala provee una única combinación de mecanismos del lenguaje que
simplifican la creación de construcciones propias del lenguaje en forma de librerías: * cualquier método
puede ser usado como un operador de infijo o postfijo * las closures son construidas automáticamente
dependiendo del tipo esperado (tipos objetivo).
El uso conjunto de ambas características facilita la definición de nuevas sentencias sin tener que extender la
sintaxis y sin usar facciones de meta-programación como tipo macros.

Scala está diseñado para interoperar bien con el popular Entorno de ejecución de Java 2 (JRE). En particular,
la interacción con el lenguaje orientado a objetos Java es muy sencillo. Scala tiene el mismo esquema de
compilación (compilación separada, carga de clases dinámica) que java y permite acceder a los miles de
librerías de gran calidad existentes.
Tipos Abstractos
En Scala, las cases son parametrizadas con valores (los parámetros de construcción) y con tipos (si las clases
son genéricas). Por razones de consistencia, no es posible tener solo valores como miembros de objetos; tanto
los tipos como los valores son miembros de objetos. Además, ambos tipos de miembros pueden ser concretos
y abstractos. A continuación un ejemplo el cual define de forma conjunta una asignación de valor tardía y un
tipo abstracto como miembros del trait Buffer.
1. trait Buffer {
2. type T
3. val element: T
4. }
Los tipos abstractos son tipos los cuales su identidad no es precisamente conocida. En el ejemplo anterior, lo
único que sabemos es que cada objeto de la clase Buffer tiene un miembro de tipo T, pero la definición de la
clase Buffer no revela qué tipo concreto se corresponde con el tipo  T. Tal como las definiciones de valores,
es posible sobrescribir las definiciones de tipos en subclases. Esto permite revelar más información acerca de
un tipo abstracto al acotar el tipo ligado (el cual describe las posibles instancias concretas del tipo abstracto).
En el siguiente programa derivamos la clase SeqBuffer la cual nos permite almacenar solamente sequencias
en el buffer al estipular que el tipo T tiene que ser un subtipo de Seq[U] para un nuevo tipo abstracto U:
1. abstract class SeqBuffer extends Buffer {
2. type U
3. type T <: Seq[U]
4. def length = element.length
5. }
Traits o clases con miembros de tipos abstractos son generalmente usados en combinación con instancias de
clases anónimas. Para ilustrar este concepto veremos un programa el cual trata con un buffer de sequencia que
se remite a una lista de enteros.
1. abstract class IntSeqBuffer extends SeqBuffer {
2. type U = Int
3. }
4.
5. object AbstractTypeTest1 extends App {
6. def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer =
7. new IntSeqBuffer {
8. type T = List[U]
9. val element = List(elem1, elem2)
10. }
11. val buf = newIntSeqBuf(7, 8)
12. println("length = " + buf.length)
13. println("content = " + buf.element)
14. }
El tipo retornado por el método newIntSeqBuf está ligado a la especialización del trait Buffer en el cual el
tipo U es ahora equivalente a Int. Existe un tipo alias similar en la instancia de la clase anónima dentro del
cuerpo del método newIntSeqBuf. En ese lugar se crea una nueva instancia de  IntSeqBuffer en la cual el
tipo T está ligado a List[Int].
Es necesario notar que generalmente es posible transformar un tipo abstracto en un tipo paramétrico de una
clase y viceversa. A continuación, se muestra una versión del código anterior el cual solo usa tipos
paramétricos.

1. abstract class Buffer[+T] {


2. val element: T
3. }
4. abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] {
5. def length = element.length
6. }
7. object AbstractTypeTest2 extends App {
8. def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] =
9. new SeqBuffer[Int, List[Int]] {
10. val element = List(e1, e2)
11. }
12. val buf = newIntSeqBuf(7, 8)
13. println("length = " + buf.length)
14. println("content = " + buf.element)
15. }
Nótese que es necesario usar variance annotations aquí; de otra manera no sería posible ocultar el tipo
implementado por la secuencia concreta del objeto retornado por  newIntSeqBuf. Además, existen casos en
los cuales no es posible remplazar tipos abstractos con tipos parametrizados.

Anotaciones
Las anotaciones sirven para asociar meta-información con definiciones.
Una anotación simple tiene la forma @C o @C(a1, .., an). Aquí, C es un constructor de la clase C, que
debe extender de la clase scala.Annotation. Todos los argumentos de construcción dados a1, ..,
an deben ser expresiones constantes (es decir, expresiones de números literales, strings, clases,
enumeraciones de Java y arrays de una dimensión de estos valores).
Una anotación se aplica a la primer definición o declaración que la sigue. Más de una anotación puede
preceder una definición o declaración. El orden en que es dado estas anotaciones no importa.

El significado de las anotaciones depende de la implementación. En la plataforma de Java, las siguientes


anotaciones de Scala tienen un significado estandar.

Scala Java

scala.SerialVersionUID serialVersionUID (campo, variable)

scala.cloneable java.lang.Cloneable

scala.deprecated java.lang.Deprecated

scala.inline (desde 2.6.0) sin equivalente


Scala Java

scala.native (desde 2.6.0) native (palabra clave)

scala.remote java.rmi.Remote

scala.serializable java.io.Serializable

scala.throws throws (palabra clave)

scala.transient transient (palabra clave)

scala.unchecked (desde 2.4.0) sin equivalente

scala.volatile volatile (palabra clave)

scala.reflect.BeanProperty Design pattern

En el siguiente ejemplo agregamos la anotación throws a la definición del método read de manera de


capturar la excepción lanzada en el programa principal de Java.
El compilador de Java comprueba que un programa contenga manejadores para excepciones
comprobadas al analizar cuales de esas excepciones comprobadas pueden llegar a lanzarse en la ejecución
de un método o un constructor. Por cada excepción comprobada que sea un posible resultado, la
cláusula throws debe para ese método o constructor debe ser mencionada en la clase de esa excepción o una
de las superclases. Ya que Scala no tiene excepciones comprobadas, los métodos en Scala deben ser
anotados con una o más anotaciones  throws para que el código Java pueda capturar las excepciones
lanzadas por un método de Scala.

1. package examples
2. import java.io._
3. class Reader(fname: String) {
4. private val in = new BufferedReader(new FileReader(fname))
5. @throws(classOf[IOException])
6. def read() = in.read()
7. }
El siguiente programa de Java imprime en consola los contenidos del archivo cuyo nombre es pasado como
primer argumento al método main.
1. package test;
2. import examples.Reader; // Scala class !!
3. public class AnnotaTest {
4. public static void main(String[] args) {
5. try {
6. Reader in = new Reader(args[0]);
7. int c;
8. while ((c = in.read()) != -1) {
9. System.out.print((char) c);
10. }
11. } catch (java.io.IOException e) {
12. System.out.println(e.getMessage());
13. }
14. }
15. }
Si comentamos la anotación throws en la clase Reader se produce el siguiente error cuando se intenta
compilar el programa principal de Java:
1. Main.java:11: exception java.io.IOException is never thrown in body of
2. corresponding try statement
3. } catch (java.io.IOException e) {
4. ^
5. 1 error

Anotaciones en Java
Nota: Asegurate de usar la opción -target:jvm-1.5 con anotaciones de Java.
Java 1.5 introdujo metadata definida por el usuario en la forma de anotaciones. Una característica
fundamental de las anotaciones es que se basan en pares nombre-valor específicos para inicializar sus
elementos. Por ejemplo, si necesitamos una anotación para rastrear el código de alguna clase debemos
definirlo así:
1. @interface Source {
2. public String URL();
3. public String mail();
4. }
Y después utilizarlo de la siguiente manera

1. @Source(URL = "http://coders.com/",
2. mail = "support@coders.com")
3. public class MyClass extends HisClass ...
Una anotación en Scala se asemeja a una invocación a un constructor. Para instanciar una anotación de Java
es necesario usar los argumentos nombrados:

1. @Source(URL = "http://coders.com/",
2. mail = "support@coders.com")
3. class MyScalaClass ...
Esta sintaxis es bastante tediosa si la anotación contiene solo un elemento (sin un valor por defecto) por lo
tanto, por convención, si el nombre es especificado como value puede ser utilizado en Java usando una
sintaxis similar a la de los constructores:
1. @interface SourceURL {
2. public String value();
3. public String mail() default "";
4. }
Y podemos aplicarlo así:

1. @SourceURL("http://coders.com/")
2. public class MyClass extends HisClass ...
En este caso, Scala provee la misma posibilidad:

1. @SourceURL("http://coders.com/")
2. class MyScalaClass ...
El elemento mail fue especificado con un valor por defecto (mediante la cláusula default) por lo tanto no
necesitamos proveer explicitamente un valor para este. De todas maneras, si necesitamos pasarle un valor no
podemos mezclar los dos estilos en Java:
1. @SourceURL(value = "http://coders.com/",
2. mail = "support@coders.com")
3. public class MyClass extends HisClass ...
Scala provee más flexibilidad en este caso:

1. @SourceURL("http://coders.com/",
2. mail = "support@coders.com")
3. class MyScalaClass ...

Clases
En Scala, las clases son plantillas estáticas que pueden ser instanciadas por muchos objetos en tiempo de
ejecución. Aquí se presenta una clase la cual define la clase Point:
1. class Point(xc: Int, yc: Int) {
2. var x: Int = xc
3. var y: Int = yc
4. def move(dx: Int, dy: Int) {
5. x = x + dx
6. y = y + dy
7. }
8. override def toString(): String = "(" + x + ", " + y + ")";
9. }
Esta clase define dos variables  x e y, y dos métodos: move y toString. El método move recibe dos
argumentos de tipo entero, pero no retorna ningún valor (implícitamente se retorna el tipo Unit, el cual se
corresponde a void en lenguajes tipo Java). toString, por otro lado, no recibe ningún parámetro, pero
retorna un valor tipo String. Ya que toString sobreescribe el método toString predefinido en una
superclase, tiene que ser anotado con override.
Las clases en Scala son parametrizadas con argumentos constructores (inicializadores). En el código anterior
se definen dos argumentos contructores, xc y yc; ambos son visibles en toda la clase. En nuestro ejemplo son
utilizados para inicializar las variables x e y.
Para instanciar una clase es necesario usar la primitiva new, como se muestra en el siguiente ejemplo:
1. object Classes {
2. def main(args: Array[String]) {
3. val pt = new Point(1, 2)
4. println(pt)
5. pt.move(10, 10)
6. println(pt)
7. }
8. }
El programa define una aplicación ejecutable a través del método  main del objeto singleton Classes. El
método main crea un nuevo Point y lo almacena en pt. Note que valores definidos con la
signatura  val  son distintos de los definidos con   var  (véase la clase  Point  arriba) ya que los primeros
(val) no permiten reasignaciones; es decir, que el valor es una constante.
Aquí se muestra la salida del programa:

1. (1, 2)
2. (11, 12)

Clases Case
Scala da soporte a la noción de clases caso (en inglés case classes, desde ahora clases Case). Las clases Case
son clases regulares las cuales exportan sus parámetros constructores y a su vez proveen una descomposición
recursiva de sí mismas a través de reconocimiento de patrones.
A continuación, se muestra un ejemplo para una jerarquía de clases la cual consiste de una super clase
abstracta llamada Term y tres clases concretas: Var, Fun y App.
1. abstract class Term
2. case class Var(name: String) extends Term
3. case class Fun(arg: String, body: Term) extends Term
4. case class App(f: Term, v: Term) extends Term
Esta jerarquía de clases puede ser usada para representar términos de cálculo lambda no tipado. Para facilitar
la construcción de instancias de clases Case, Scala no requiere que se utilice la primitiva new. Simplemente es
posible utilizar el nombre de la clase como una llamada a una función.
Aquí un ejemplo:

1. Fun("x", Fun("y", App(Var("x"), Var("y"))))


Los parámetros constructores de las clases Case son tratados como valores públicos y pueden ser accedidos
directamente.

1. val x = Var("x")
2. println(x.name)
Para cada una de las clases Case el compilador de Scala genera el método equals el cual implementa la
igualdad estructural y un método toString. Por ejemplo:
1. val x1 = Var("x")
2. val x2 = Var("x")
3. val y1 = Var("y")
4. println("" + x1 + " == " + x2 + " => " + (x1 == x2))
5. println("" + x1 + " == " + y1 + " => " + (x1 == y1))
imprime

1. Var(x) == Var(x) => true


2. Var(x) == Var(y) => false
Solo tiene sentido definir una clase Case si el reconocimiento de patrones es usado para descomponer la
estructura de los datos de la clase. El siguiente objeto define define una función de impresión elegante (en
inglés pretty) que imprime en pantalla nuestra representación del cálculo lambda:
1. object TermTest extends scala.App {
2. def printTerm(term: Term) {
3. term match {
4. case Var(n) =>
5. print(n)
6. case Fun(x, b) =>
7. print("^" + x + ".")
8. printTerm(b)
9. case App(f, v) =>
10. print("(")
11. printTerm(f)
12. print(" ")
13. printTerm(v)
14. print(")")
15. }
16. }
17. def isIdentityFun(term: Term): Boolean = term match {
18. case Fun(x, Var(y)) if x == y => true
19. case _ => false
20. }
21. val id = Fun("x", Var("x"))
22. val t = Fun("x", Fun("y", App(Var("x"), Var("y"))))
23. printTerm(t)
24. println
25. println(isIdentityFun(id))
26. println(isIdentityFun(t))
27. }
En nuestro ejemplo, la función printTerm es expresada como una sentencia basada en reconocimiento de
patrones, la cual comienza con la palabra reservada match y consiste en secuencias de sentencias tipo case
PatronBuscado => Código que se ejecuta.
El programa de arriba también define una función isIdentityFun la cual comprueba si un término dado se
corresponde con una función identidad simple. Ese ejemplo utiliza patrones y guardas más avanzados
(obsrvese la guarda if x==y). Tras reconocer un patrón con un valor dado, la guarda (definida después de la
palabra clave if) es evaluada. Si retorna true (verdadero), el reconocimiento es exitoso; de no ser así, falla y
se intenta con el siguiente patrón.

Tipos Compuestos
Algunas veces es necesario expresar que el tipo de un objeto es un subtipo de varios otros tipos. En Scala esto
puede ser expresado con la ayuda de tipos compuestos, los cuales pueden entenderse como la intersección de
otros tipos.
Suponga que tenemos dos traits Cloneable y Resetable:
1. trait Cloneable extends java.lang.Cloneable {
2. override def clone(): Cloneable = {
3. super.clone().asInstanceOf[Cloneable]
4. }
5. }
6. trait Resetable {
7. def reset: Unit
8. }
Ahora suponga que queremos escribir una función cloneAndReset la cual recibe un objeto, lo clona y
resetea el objeto original:
1. def cloneAndReset(obj: ?): Cloneable = {
2. val cloned = obj.clone()
3. obj.reset
4. cloned
5. }
La pregunta que surge es cuál es el tipo del parámetro obj. Si este fuera Cloneable entonces el objeto puede
ser clonado mediante el método clone, pero no puede usarse el método reset; Si
fuera Resetable podríamos resetearlo mediante el método reset, pero no sería posible clonarlo. Para evitar
casteos (refundiciones, en inglés casting) de tipos en situaciones como la descrita, podemos especificar que
el tipo del objeto obj sea tanto Clonable como Resetable. En tal caso estaríamos creando un tipo
compuesto; de la siguiente manera:
1. def cloneAndReset(obj: Cloneable with Resetable): Cloneable = {
2. //...
3. }
Los tipos compuestos pueden crearse a partir de varios tipos de objeto y pueden tener un refinamiento el cual
puede ser usado para acotar la signatura los miembros del objeto existente.
La forma general es: A with B with C ... { refinamiento }

Sequencias por Comprensión


Scala cuenta con una notación ligera para expresar sequencias por comprensión (sequence comprehensions).
Las comprensiones tienen la forma for (enumeradores) yield e, dondeenumeradores se refiere a una
lista de enumeradores separados por el símbolo punto y coma (;). Un enumerador puede ser tanto un
generador el cual introduce nuevas variables, o un filtro. La comprensión evalúa el cuerpo e por cada paso (o
ciclo) generado por los enumeradores y retorna una secuencia de estos valores.
Aquí hay un ejemplo:

1. object ComprehensionTest1 extends App {


2. def pares(desde: Int, hasta: Int): List[Int] =
3. for (i <- List.range(desde, hasta) if i % 2 == 0) yield i
4. Console.println(pares(0, 20))
5. }
La expresión for en la función introduce una nueva variable i de tipo Int la cual es subsecuentemente atada
a todos los valores de la lista List(desde, desde + 1, ..., hasta - 1) . La guarda if i % 2 ==
0 filtra los números impares por lo que el cuerpo (que solo consiste de la expresión i) es solamente evaluado
para números pares. Consecuentemente toda la expresión for retorna una lista de números pares.
El programa produce los siguientes valores

1. List(0, 2, 4, 6, 8, 10, 12, 14, 16, 18)


Aquí se muestra un ejemplo más complicado que computa todos los pares de números entre  0 y n-1 cuya
suma es igual a un número dado v:
1. object ComprehensionTest2 extends App {
2. def foo(n: Int, v: Int) =
3. for (i <- 0 until n;
4. j <- i until n if i + j == v) yield
5. Pair(i, j);
6. foo(20, 32) foreach {
7. case (i, j) =>
8. println("(" + i + ", " + j + ")")
9. }
10. }
Este ejemplo muestra que las comprensiones no están restringidas solo a listas. El programa anterior usa
iteradores en su lugar. Cualquier tipo de datos que soporte las operaciones filterWith, map,
y flatMap (con los tipos apropiados) puede ser usado en la comprensión de secuencias.
Esta es la salida del programa:

1. (13, 19)
2. (14, 18)
3. (15, 17)
4. (16, 16)
Existe también una forma especial de comprensión de secuencias la cual retorna Unit. En este caso las
variables que son creadas por la lista de generadores y filtros son usados para realizar tareas con efectos
colaterales (modificaciones de algún tipo). El programador tiene que omitir la palabra reservada yield para
usar una comprensión de este tipo.
1. object ComprehensionTest3 extends App {
2. for (i <- Iterator.range(0, 20);
3. j <- Iterator.range(i, 20) if i + j == 32)
4. println("(" + i + ", " + j + ")")
5. }

Objetos Extractores
En Scala pueden ser definidos patrones independientemente de las clases Caso (en inglés case classes, desde
ahora clases Case). Para este fin exite un método llamado  unapply que proveera el ya dicho extractor. Por
ejemplo, en el código siguiente se define el objeto extractor Twice
1. object Twice {
2. def apply(x: Int): Int = x * 2
3. def unapply(z: Int): Option[Int] = if (z%2 == 0) Some(z/2) else None
4. }
5.
6. object TwiceTest extends App {
7. val x = Twice(21)
8. x match { case Twice(n) => Console.println(n) } // imprime 21
9. }
Hay dos convenciones sintácticas que entran en juego aquí:

El patrón case Twice(n) causará la invocación del método  Twice.unapply, el cual es usado para


reconocer cualquier número par; el valor de retorno de  unapply indica si el argumento produjo una
coincidencia o no, y cualquier otro sub valor que pueda ser usado para un siguiente reconocimiento. Aquí, el
sub-valor es z/2.
El método apply no es necesario para reconocimiento de patrones. Solamente es usado para proveer un
constructor. val x = Twice(21) se puede expandir como val x = Twice.apply(21).
El tipo de retorno de un método unapply debería ser elegido de la siguiente manera: * Si es solamente una
comprobación, retornar un Boolean. Por ejemplo, case esPar() * Si retorna un único sub valor del tipo T,
retornar un Option[T] * Si quiere retornar varios sub valoresT1,...,Tn, es necesario agruparlos en una
tupla de valores opcionales Option[(T1,...,Tn)].
Algunas veces, el número de sub valores es fijo y nos gustaría retornar una secuencia. Por esta razón, siempre
es posible definir patrones a través de unapplySeq. El último sub valor de tipo Tntiene que ser Seq[S]. Este
mecanismo es usado por ejemplo en el patrón case List(x1, ..., xn).

Clases genéricas
Tal como en Java 5 (JDK 1.5), Scala provee soporte nativo para clases parametrizados con tipos. Eso es
llamado clases genéricas y son especialmente importantes para el desarrollo de clases tipo colección.
A continuación, se muestra un ejemplo:

1. class Stack[T] {
2. var elems: List[T] = Nil
3. def push(x: T) { elems = x :: elems }
4. def top: T = elems.head
5. def pop() { elems = elems.tail }
6. }
La clase Stack modela una pila mutable que contiene elementos de un tipo arbitrario T (se dice, “una pila de
elementos T). Los parámetros de tipos nos aseguran que solo elementos legales (o sea, del tipo T) sean
insertados en la pila (apilados). De forma similar, con los parámetros de tipo podemos expresar que el
método top solo devolverá elementos de un tipo dado (en este caso T).
Aquí se muestra un ejemplo del uso de dicha pila:

1. object GenericsTest extends App {


2. val stack = new Stack[Int]
3. stack.push(1)
4. stack.push('a')
5. println(stack.top)
6. stack.pop()
7. println(stack.top)
8. }
La salida del programa sería:

1. 97
2. 1
Nota: los subtipos de tipos genéricos son *invariante*. Esto significa que si tenemos una pila de caracteres
del tipo  Stack[Char], esta no puede ser usada como una pila de enteros tipo Stack[Int]. Esto no sería
razonable ya que nos permitiría introducir elementos enteros en la pila de caracteres. Para
concluir,  Stack[T] es solamente un subtipo de  Stack[S] si y solo si  S = T. Ya que esto puede llegar a
ser bastante restrictivo, Scala ofrece un mecanismo de anotación de parámetros de tipo para controlar el
comportamiento de subtipos de tipos genéricos.

Parámetros implícitos
Un método con parámetros implícitos puede ser aplicado a argumentos tal como un método normal. En este
caso la etiqueta implicit no tiene efecto. De todas maneras, si a un método le faltan argumentos para sus
parámetros implícitos, tales argumentos serán automáticamente provistos.
Los argumentos reales que son elegibles para ser pasados a un parámetro implícito están contenidos en dos
categorías: * Primera, son elegibles todos los identificadores x que puedan ser accedidos en el momento de la
llamada al método sin ningún prefijo y que denotan una definición implícita o un parámetro implícito. *
Segunda, además son elegibles todos los miembros de modulos companion (ver [objetos companion]
(singleton-objects.html)) del tipo de parámetro implicito que tienen la etiqueta implicit.
En el siguiente ejemplo definimos un método sum el cual computa la suma de una lista de elementos usando
las operaciones add y unit de Monoid. Note que los valores implícitos no pueden ser de nivel superior (top-
level), deben ser miembros de una plantilla.
abstract class SemiGroup[A] {
def add(x: A, y: A): A
}
abstract class Monoid[A] extends SemiGroup[A] {
def unit: A
}
object ImplicitTest extends App {
implicit object StringMonoid extends Monoid[String] {
def add(x: String, y: String): String = x concat y
def unit: String = ""
}
implicit object IntMonoid extends Monoid[Int] {
def add(x: Int, y: Int): Int = x + y
def unit: Int = 0
}
def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
if (xs.isEmpty) m.unit
else m.add(xs.head, sum(xs.tail))

println(sum(List(1, 2, 3)))
println(sum(List("a", "b", "c")))
}
Esta es la salida del programa:
6
abc

Clases Internas
En Scala es posible que las clases tengan como miembro otras clases. A diferencia de lenguajes
similares a Java donde ese tipo de clases internas son miembros de las clases que las envuelven,
en Scala esas clases internas están ligadas al objeto externo. Para ilustrar esta diferencia, vamos a
mostrar rápidamente una implementación del tipo grafo:

1. class Graph {
2. class Node {
3. var connectedNodes: List[Node] = Nil
4. def connectTo(node: Node) {
5. if (connectedNodes.find(node.equals).isEmpty) {
6. connectedNodes = node :: connectedNodes
7. }
8. }
9. }
10. var nodes: List[Node] = Nil
11. def newNode: Node = {
12. val res = new Node
13. nodes = res :: nodes
14. res
15. }
16. }
En nuestro programa, los grafos son representados mediante una lista de nodos. Estos nodos son
objetos de la clase interna Node. Cada nodo tiene una lista de vecinos que se almacena en la
listaconnectedNodes. Ahora podemos crear un grafo con algunos nodos y conectarlos
incrementalmente:
1. object GraphTest extends App {
2. val g = new Graph
3. val n1 = g.newNode
4. val n2 = g.newNode
5. val n3 = g.newNode
6. n1.connectTo(n2)
7. n3.connectTo(n1)
8. }
Ahora vamos a completar el ejemplo con información relacionada al tipado para definir
explicitamente de qué tipo son las entidades anteriormente definidas:

1. object GraphTest extends App {


2. val g: Graph = new Graph
3. val n1: g.Node = g.newNode
4. val n2: g.Node = g.newNode
5. val n3: g.Node = g.newNode
6. n1.connectTo(n2)
7. n3.connectTo(n1)
8. }
El código anterior muestra que al tipo del nodo le es prefijado con la instancia superior (que en
nuestro ejemplo es g). Si ahora tenemos dos grafos, el sistema de tipado de Scala no nos permite
mezclar nodos definidos en un grafo con nodos definidos en otro, ya que los nodos del otro grafo
tienen un tipo diferente.
Aquí está el programa ilegal:

1. object IllegalGraphTest extends App {


2. val g: Graph = new Graph
3. val n1: g.Node = g.newNode
4. val n2: g.Node = g.newNode
5. n1.connectTo(n2) // legal
6. val h: Graph = new Graph
7. val n3: h.Node = h.newNode
8. n1.connectTo(n3) // ilegal!
9. }
Por favor note que en Java la última linea del ejemplo anterior hubiese sido correcta. Para los
nodos de ambos grafos, Java asignaría el mismo tipo Graph.Node; es decir, Node es prefijado con
la clase Graph. En Scala un tipo similar también puede ser definido, pero es escrito Graph#Node. Si
queremos que sea posible conectar nodos de distintos grafos, es necesario modificar la
implementación inicial del grafo de la siguiente manera:
1. class Graph {
2. class Node {
3. var connectedNodes: List[Graph#Node] = Nil // Graph#Node en lugar de
Node
4. def connectTo(node: Graph#Node) {
5. if (connectedNodes.find(node.equals).isEmpty) {
6. connectedNodes = node :: connectedNodes
7. }
8. }
9. }
10. var nodes: List[Node] = Nil
11. def newNode: Node = {
12. val res = new Node
13. nodes = res :: nodes
14. res
15. }
16. }
Por favor note que este programa no nos permite relacionar un nodo con dos grafos diferentes. Si también
quisiéramos eliminar esta restricción, sería necesario cambiar el tipo de la variablenodes a  Graph#Node.

Composición de clases mixin


Nota de traducción: La palabra  mixin puede ser traducida como mezcla, dando título a esta sección de:
Composición de clases Mezcla, pero es preferible utilizar la notación original
A diferencia de lenguajes que solo soportan herencia simple, Scala tiene una notación más general
de la reutilización de clases. Scala hace posible reutilizar la nueva definición de miembros de una
clase (es decir, el delta en relación a la superclase) en la definición de una nueva clase. Esto es
expresado como una composición de clases mixin. Considere la siguiente abstracción para iteradores.
1. abstract class AbsIterator {
2. type T
3. def hasNext: Boolean
4. def next: T
5. }
A continuación, considere una clase mezcla la cual extiende AbsIterator con un método
foreach el cual aplica una función dada a cada elemento retornado por el iterador. Para definir una
clase que puede usarse como una clase mezcla usamos la palabra clave trait.
1. trait RichIterator extends AbsIterator {
2. def foreach(f: T => Unit) { while (hasNext) f(next) }
3. }
Aquí se muestra una clase iterador concreta, la cual retorna caracteres sucesivos de una cadena
de caracteres dada:

1. class StringIterator(s: String) extends AbsIterator {


2. type T = Char
3. private var i = 0
4. def hasNext = i < s.length()
5. def next = { val ch = s charAt i; i += 1; ch }
6. }
Nos gustaría combinar la funcionalidad de StringIterator y RichIterator en una sola clase.
Solo con herencia simple e interfaces esto es imposible, ya que ambas clases contienen
implementaciones para sus miembros. Scala nos ayuda con sus compisiciones de clases mezcladas.
Permite a los programadores reutilizar el delta de la definición de una clase, esto es, todas las
nuevas definiciones que no son heredadas. Este mecanismo hace posible
combinarStringIterator con RichIterator, como es hecho en el siguiente programa, el cual
imprime una columna de todos los caracteres de una cadena de caracteres dada.
1. object StringIteratorTest {
2. def main(args: Array[String]) {
3. class Iter extends StringIterator(args(0)) with RichIterator
4. val iter = new Iter
5. iter foreach println
6. }
7. }
La clase Iter en la función main es construida de una composición mixin de los
padresStringIterator y RichIterator con la palabra clave with. El primera padre es llamado
lasuperclase de Iter, mientras el segundo padre (y cualquier otro que exista) es llamada un mixin.

Singleton Objects
Métodos y valores que no están asociados con instancias individuales de una clase se denominanobjetos
singleton y se denotan con la palabra reservada object en vez de class.
1. package test
2.
3. object Blah {
4. def sum(l: List[Int]): Int = l.sum
5. }
Este método sum está disponible de manera global, y puede ser referenciado, o importado,
comotest.Blah.sum.
Los objetos singleton son una especie de mezcla entre la definición de una clase de utilización única, la cual
no pueden ser instanciada directamente, y un miembro val. De hecho, de la misma menera que los val, los
objetos singleton pueden ser definidos como miembros de un trait o de una clase, aunque esto no es muy
frecuente.
Un objeto singleton puede extender clases y traits. De hecho, una clase Case sin parámetros de tipo generará
por defecto un objeto singleton del mismo nombre, con una Función* trait implementada.

Acompañantes
La mayoría de los objetos singleton no están solos, sino que en realidad están asociados con clases del mismo
nombre. El “objeto singleton del mismo nombre” de una case Case, mencionada anteriormente es un ejemplo
de esto. Cuando esto sucede, el objeto singleton es llamado el objeto acompañante de la clase, y la clase
es a su vez llamada la clase acompañante del objeto.
Scaladoc proporciona un soporte especial para ir y venir entre una clase y su acompañante: Si el gran círculo
conteniendo la “C” u la “O” tiene su borde inferior doblado hacia adentro, es posible hacer click en el círculo
para ir a su acompañante.
Una clase y su objeto acompañante, si existe, deben estar definidos en el mismo archivo fuente. Como por
ejemplo:

1. class IntPair(val x: Int, val y: Int)


2.
3. object IntPair {
4. import math.Ordering
5.
6. implicit def ipord: Ordering[IntPair] =
7. Ordering.by(ip => (ip.x, ip.y))
8. }
Es común ver instancias de clases tipo como valores implícitos, (ipord en el ejemplo anterior) definida en el
acompañante cuando se sigue el patron de clases tipo. Esto es debido a que los miembros del acompañante se
incluyen en la búsqueda de implícitos por defecto.

Notas para los programadores Java


static no es una palabra reservada en Scala. En cambio, todos los miembros que serían estáticos, incluso las
clases, van en los objetos acompañantes. Estos, pueden ser referenciados usando la misma sintaxis,
importados de manera individual o en grupo, etc.
Frecuentemente, los programadores Java, definen miembros estáticos, incluso definidos como private, como
ayudas en la implementacion de los miembros de la instancia. Estos elementos también van en el objeto
acompañante. Un patrón comúnmente utilizado es de importar los miembros del objeto acompañante en la
clase, como, por ejemplo:
1. class X {
2. import X._
3.
4. def blah = foo
5. }
6.
7. object X {
8. private def foo = 42
9. }
Esto permite ilustrar otra característica: en el contexto de un private, una clase y su acompañante son
amigos. El objeto X puede acceder miembros de la clase X, y vice versa. Para hacer un
miembro realmente privado para uno u otro, utilice private[this].
Para conveniencia de Java, los métodos que incluyen var y val, definidos directamente en un objeto
singleton también tienen un método estático definido en la clase acompañante, llamado static forwarder.
Otros miembros son accesibles por medio del campo estático X$.MODULE$ para elobjeto X.
Si todos los elementos se mueven al objeto acompañante y se descubre que lo que queda es una clase que no
se quiere instanciar, entonces simplemente bórrela. Los static forwarder de todas formas van a ser creados.

Funciones Anidadas
En scala es posible anidar definiciones de funciones. El siguiente objeto provee una función filter para
extraer valores de una lista de enteros que están por debajo de un valor determinado:
object FilterTest extends App {
def filter(xs: List[Int], threshold: Int) = {
def process(ys: List[Int]): List[Int] =
if (ys.isEmpty) ys
else if (ys.head < threshold) ys.head :: process(ys.tail)
else process(ys.tail)
process(xs)
}
println(filter(List(1, 9, 2, 8, 3, 7, 4), 5))
}
Nota: la función anidada  process  utiliza la variable  threshold  definida en el ámbito externo como un
parámetro de  filter.
La salida del programa es:
List(1,2,3,4)

Sintaxis de funciones anónimas


Scala provee una sintaxis relativamente liviana para definir funciones anónimas. La siguiente expresión crea
una función incrementadora para números enteros:

1. (x: Int) => x + 1


El código anterior es una forma compacta para la definición de la siguiente clase anónima:

1. new Function1[Int, Int] {


2. def apply(x: Int): Int = x + 1
3. }
También es posible definir funciones con múltiples parámetros:

1. (x: Int, y: Int) => "(" + x + ", " + y + ")"


o sin parámetros:

1. () => { System.getProperty("user.dir") }
Existe también una forma simple para escribir los tipos de las funciones. A continuación, se muestran los
tipos de las trés funciones escritas anteriormente:

1. Int => Int


2. (Int, Int) => String
3. () => String
La sintaxis anterior es la forma sintética de escribir los siguientes tipos:

1. Function1[Int, Int]
2. Function2[Int, Int, String]
3. Function0[String]

Currying
Nota de traducción: Currying es una técnica de programación funcional nombrada en
honor al matemático y lógico Haskell Curry. Es por eso que no se intentará hacer ninguna
traducción sobre el término Currying. Entiendase este como una acción, técnica base de
PF. Como una nota al paso, el lenguaje de programación Haskell debe su nombre a este
eximio matemático.
Los métodos pueden definir múltiples listas de parámetros. Cuando un método es invocado con un número
menor de listas de parámetros, en su lugar se devolverá una función que toma las listas faltantes como sus
argumentos.

Aquí se muestra un ejemplo:

1. object CurryTest extends App {


2.
3. def filter(xs: List[Int], p: Int => Boolean): List[Int] =
4. if (xs.isEmpty) xs
5. else if (p(xs.head)) xs.head :: filter(xs.tail, p)
6. else filter(xs.tail, p)
7.
8. def modN(n: Int)(x: Int) = ((x % n) == 0)
9.
10. val nums = List(1, 2, 3, 4, 5, 6, 7, 8)
11. println(filter(nums, modN(2)))
12. println(filter(nums, modN(3)))
13. }
Nota: el método  modN  está parcialmente aplicado en las dos llamadas a  filter; esto
significa que solo su primer argumento es realmente aplicado. El
término  modN(2)  devuelve una función de tipo  Int => Boolean  y es por eso un posible
candidato para el segundo argumento de la funciónfilter
Aquí se muestra la salida del programa anterior:

1. List(2,4,6,8)
2. List(3,6)

Construcción de closures automáticas


Scala permite pasar funciones sin parámetros como parámetros de un método. Cuando un método así es
invocado, los parámetros reales de la función enviada sin parámetros no son evaluados y una función
“nularia” (de aridad cero, 0-aria, o sin parámetros) es pasada en su lugar. Esta función encapsula el
comportamiento del parámetro correspondiente (comúnmente conocido como “llamada por nombre”).

Para aclarar un poco esto aquí se muestra un ejemplo:

1. object TargetTest1 extends App {


2. def whileLoop(cond: => Boolean)(body: => Unit): Unit =
3. if (cond) {
4. body
5. whileLoop(cond)(body)
6. }
7. var i = 10
8. whileLoop (i > 0) {
9. println(i)
10. i -= 1
11. }
12. }
La función whileLoop recibe dos parámetros cond y body. Cuando la función es llamada, los parámetros
reales no son evaluados en ese momento. Pero cuando los parámetros son utilizados en el cuerpo de la
función whileLoop, las funciones nularias creadas implícitamente serán evaluadas en su lugar. Así, nuestro
método whileLoop implementa un bucle tipo Java mediante una implementación recursiva.
Es posible combinar el uso de operadores de infijo y postfijo (infix/postfix) con este mecanismo para crear
declaraciones más complejas (con una sintaxis agradable).
Aquí mostramos la implementación de una declaración tipo repetir-a-menos-que (repetir el bucle a no ser que
se cumpla X condición):

1. object TargetTest2 extends App {


2. def loop(body: => Unit): LoopUnlessCond =
3. new LoopUnlessCond(body)
4. protected class LoopUnlessCond(body: => Unit) {
5. def unless(cond: => Boolean) {
6. body
7. if (!cond) unless(cond)
8. }
9. }
10. var i = 10
11. loop {
12. println("i = " + i)
13. i -= 1
14. } unless (i == 0)
15. }
La función loop solo acepta el cuerpo de un bucle y retorna una instancia de la clase LoopUnlessCond (la
cual encapsula el cuerpo del objeto). Es importante notar que en este punto el cuerpo del bucle no ha sido
evaluado aún. La clase LoopUnlessCond tiene un método unless el cual puede ser usado como
un operador de infijo (infix). De esta manera podemos lograr una sintaxis muy natural para nuestro nuevo
bucle repetir { <estas declaraciones> a_menos_que ( <condición>).
A continuación, se expone el resultado de la ejecución de TargetTest2:
1. i = 10
2. i = 9
3. i = 8
4. i = 7
5. i = 6
6. i = 5
7. i = 4
8. i = 3
9. i = 2
10. i = 1

Operadores
el cual reciba un solo parámetro puede ser usado como un operador de infijo (infix). Aquí se muestra la
definición de la clase MyBool, la cual define tres métodos and, or, y negate.
class MyBool(x: Boolean) {
def and(that: MyBool): MyBool = if (x) that else this
def or(that: MyBool): MyBool = if (x) this else that
def negate: MyBool = new MyBool(!x)
}
Ahora es posible utilizar and y or como operadores de infijo:
def not(x: MyBool) = x negate; // punto y coma necesario aquí
def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y)
Como muestra la primera línea del código anterior, es también posible utilizar métodos nularios (que no
reciban parámetros) como operadores de postfijo. La segunda línea define la función xorutilizando los
métodos andy or como también la función not. En este ejemplo el uso de losoperadores de postfijo ayuda a
crear una definición del método xor más fácil de leer.
Para demostrar esto se muestra el código correspondiente a las funciones anteriores pero escritas en una
notación orientada a objetos más tradicional:
def not(x: MyBool) = x.negate; // punto y coma necesario aquí
def xor(x: MyBool, y: MyBool) = x.or(y).and(x.and(y).negate)

Funciones de orden superior


Scala permite la definición de funciones de orden superior. Estas funciones son las que toman otras funciones
como parámetros, o las cuales el resultado es una función. Aquí mostramos una función apply la cual toma
otra función f y un valor v como parámetros y aplica la función f av:
1. def apply(f: Int => String, v: Int) = f(v)
Nota: los métodos son automáticamente tomados como funciones si el contexto lo requiere.
Otro ejemplo:

1. class Decorator(left: String, right: String) {


2. def layout[A](x: A) = left + x.toString() + right
3. }
4.
5. object FunTest extends App {
6. def apply(f: Int => String, v: Int) = f(v)
7. val decorator = new Decorator("[", "]")
8. println(apply(decorator.layout, 7))
9. }
La ejecución da como valor el siguiente resultado:

1. [7]
En este ejemplo, el método decorator.layout es coaccionado automáticamente a un valor del tipo Int =>
String como es requerido por el método apply. Por favor note que el método decorator.layout es
un método polimórfico (esto es, se abstrae de algunos de sus tipos) y el compilador de Scala primero tiene que
instanciar correctamente el tipo del método.

Reconocimiento de patrones
Nota de traducción: Es difícil encontrar en nuestro idioma una palabra que se relacione directamente con el
significado de  match en inglés. Podemos entender a  match como “coincidir” o “concordar” con algo. En
algunos lugares se utiliza la palabra  machear, aunque esta no existe en nuestro idioma con el sentido que se
le da en este texto, sino que se utiliza como traducción dematch.
Scala tiene incorporado un mecanismo general de reconocimiento de patrones. Este permite identificar
cualquier tipo de datos una política primero-encontrado. Aquí se muestra un pequeño ejemplo el cual muestra
cómo coincidir un valor entero:

1. object MatchTest1 extends App {


2. def matchTest(x: Int): String = x match {
3. case 1 => "one"
4. case 2 => "two"
5. case _ => "many"
6. }
7. println(matchTest(3))
8. }
El bloque con las sentencias case define una función la cual mapea enteros a cadenas de caracteres (strings).
La palabra reservada match provee una manera conveniente de aplicar una función (como la función anterior)
a un objeto.
Aquí se muestra un ejemplo el cual coincide un valor contra un patrón de diferentes tipos:

1. object MatchTest2 extends App {


2. def matchTest(x: Any): Any = x match {
3. case 1 => "one"
4. case "two" => 2
5. case y: Int => "scala.Int"
6. }
7. println(matchTest("two"))
8. }
El primer case coincide si x se refiere a un valor entero 1. El segundo case coincide si x es igual al
string "two". El tercero consiste en un patrón tipado (se provee un tipo); se produce una coincidencia contra
cualquier entero que se provea y además se liga la variable y al valor pasado de tipo entero.
El reconocimiento de patrones en Scala es más útil para hacer coincidir tipos algebráicos expresados
mediante clases case. Scala también permite la definición de patrones independientemente de las clases Case,
a través del método unapply de objetos extractores.

Métodos polimórficos
Los métodos en Scala pueden ser parametrizados tanto con valores como con tipos. Como a nivel de clase,
parámetros de valores son encerrados en un par de paréntesis, mientras que los parámetros de tipo son
declarados dentro de un par de corchetes.

Aquí hay un ejemplo:

1. object PolyTest extends App {


2. def dup[T](x: T, n: Int): List[T] =
3. if (n == 0) Nil
4. else x :: dup(x, n - 1)
5. println(dup[Int](3, 4)) // linea 5
6. println(dup("three", 3)) // linea 6
7. }
El método dup en el objeto PolyTest es parametrizado con el tipo T y con los parámetros x: Ty n: Int.
Cuando el método dup es llamado, el programador provee los parámetros requeridos (vea la línea 5 del
programa anterior), pero como se muestra en la linea 6 no es necesario que se provea el parámetro de
tipo T explícitamente. El sistema de tipado de Scala puede inferir estos tipos. Esto es realizado a través de la
observación del tipo de los parámetros pasados y del contexto donde el método es invocado.
Por favor note que el trait App está diseñado para escribir programas cortos de pruebas. Debe ser evitado en
código en producción (para versiones de Scala 2.8.x y anteriores) ya que puede afectar la habilidad de la JVM
de optimizar el código resultante; por favor use def main() en su lugar.

Patrones basados en expresiones regulares


Patrones de secuencias que ignoran a la derecha
Los patrones de secuencias que ignoran a la derecha son una característica útil para separar cualquier dato que
sea tanto un subtipo de Seq[A] o una clase case con un parámetro iterador formal, como, por ejemplo
1. Elem(prefix:String, label:String, attrs:MetaData, scp:NamespaceBinding,
children:Node*)
En esos casos, Scala permite a los patrones que utilicen el cómodin _* en la posición más a la derecha que
tomen lugar para secuencias arbitrariamente largas. El siguiente ejemplo demuestra un reconocimiento de
patrones el cual identifica un prefijo de una secuencia y liga el resto a la variable rest.
1. object RegExpTest1 extends App {
2. def containsScala(x: String): Boolean = {
3. val z: Seq[Char] = x
4. z match {
5. case Seq('s','c','a','l','a', rest @ _*) =>
6. println("rest is "+rest)
7. true
8. case Seq(_*) =>
9. false
10. }
11. }
12. }
A diferencia de versiones previas de Scala, ya no está permitido tener expresiones regulares arbitrarias, por
las siguientes razones.

Patrones generales de expresiones regulares ( RegExp) temporariamente

retirados de Scala
Desde que descubrimos un problema en la precisión, esta característica está temporariamente retirada del
lenguaje. Si existiese una petición de parte de la comunidad de usuarios, podríamos llegar a reactivarla de una
forma mejorada.

De acuerdo a nuestra opinión los patrones basados en expresiones regulares no resultaron útiles para el
procesamiento de XML. En la vida real, las aplicaciones que procesan XML, XPath parece una opción mucho
mejor. Cuando descubrimos que nuestra traducción de los patrones para expresiones regulares tenía algunos
errores para patrones raros y poco usados, aunque difícil de excluir, decidimos que sería tiempo de simplificar
el lenguaje.

Traits
Nota de traducción: La palabra  trait en inglés puede traducirse literalmente como  rasgo o
característica. Preferimos la designación original trait por ser una característica muy natural de Scala.
De forma similar a las interfaces de Java, los traits son usados para definir tipos de objetos al especificar el
comportamiento mediante los métodos provistos. A diferencia de Java, Scala permite a los traits ser
parcialmente implementados, esto es, es posible definir implementaciones por defecto para algunos métodos.
En contraste con las clases, los traits no pueden tener parámetros de constructor. A continuación, se muestra
un ejemplo:

1. trait Similarity {
2. def isSimilar(x: Any): Boolean
3. def isNotSimilar(x: Any): Boolean = !isSimilar(x)
4. }
Este trait consiste de dos métodos isSimilar y isNotSimilar. Mientras isSimilar no provee una
implementación concreta del método (es abstracto en la terminología Java), el método isNotSimilar define
una implementación concreta. Consecuentemente, las clases que integren este trait solo tienen que proveer
una implementación concreta para isSimilar. El comportamiento de isNotSimilar es directamente
heredado del trait. Los traits típicamente son integrados a una clase (u otros traits) mediante una Composición
de clases mixin:
1. class Point(xc: Int, yc: Int) extends Similarity {
2. var x: Int = xc
3. var y: Int = yc
4. def isSimilar(obj: Any) =
5. obj.isInstanceOf[Point] &&
6. obj.asInstanceOf[Point].x == x
7. }
8. object TraitsTest extends Application {
9. val p1 = new Point(2, 3)
10. val p2 = new Point(2, 4)
11. val p3 = new Point(3, 3)
12. println(p1.isNotSimilar(p2))
13. println(p1.isNotSimilar(p3))
14. println(p1.isNotSimilar(2))
15. }
Esta es la salida del programa:

1. false
2. true
3. true

Límite de tipado superior


En Scala, los parámetros de tipo y los tipos abstractos pueden ser restringidos por un límite de tipado. Tales
límites de tipado limitan los valores concretos de las variables de tipo y posiblemente revelan más
información acerca de los miembros de tales tipos. Un límite de tipado superior T <: A declara que la
variable de tipo T es un subtipo del tipo A. Aquí se muestra un ejemplo el cual se basa en un límite de tipado
superior para la implementación del método polimórfico findSimilar:
1. trait Similar {
2. def isSimilar(x: Any): Boolean
3. }
4. case class MyInt(x: Int) extends Similar {
5. def isSimilar(m: Any): Boolean =
6. m.isInstanceOf[MyInt] &&
7. m.asInstanceOf[MyInt].x == x
8. }
9. object UpperBoundTest extends App {
10. def findSimilar[T <: Similar](e: T, xs: List[T]): Boolean =
11. if (xs.isEmpty) false
12. else if (e.isSimilar(xs.head)) true
13. else findSimilar[T](e, xs.tail)
14. val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3))
15. println(findSimilar[MyInt](MyInt(4), list))
16. println(findSimilar[MyInt](MyInt(2), list))
17. }
Sin la anotación del límite de tipado superior no sería posible llamar al método isSimilar en el
método findSimilar.

Límite de tipado inferior


Mientras que los límites de tipado superior limitan el tipo de un subtipo de otro tipo, los límites de tipado
inferior declaran que un tipo sea un supertipo de otro tipo. El término T >: A expresa que el parámetro de
tipo T o el tipo abstracto T se refiera a un supertipo del tipo A
Aquí se muestra un ejemplo donde esto es de utilidad:

1. case class ListNode[T](h: T, t: ListNode[T]) {


2. def head: T = h
3. def tail: ListNode[T] = t
4. def prepend(elem: T): ListNode[T] =
5. ListNode(elem, this)
6. }
El programa mostrado implementa una lista enlazada con una operación prepend (agregar al principio).
Desafortunadamente este tipo es invariante en el parámetro de tipo de la clase ListNode; esto es, el
tipo ListNode[String] no es un subtipo de ListNode[Object]. Con la ayuda de anotaciones de
varianza es posible expresar tal semantica de subtipos:
1. case class ListNode[+T](h: T, t: ListNode[T]) { ... } // No compila
Desafortunadamente, este programa no compila porque una anotación covariante es solo posible si el tipo de
la variable es usado solo en posiciones covariantes. Ya que la variable de tipo T aparece como un parámetro
de tipo en el método prepend, esta regla se rompe. Con la ayuda de un límite de tipado inferior, sin embargo,
podemos implementar un método prepend donde T solo aparezca en posiciones covariantes.
Este es el código correspondiente:

1. case class ListNode[+T](h: T, t: ListNode[T]) {


2. def head: T = h
3. def tail: ListNode[T] = t
4. def prepend[U >: T](elem: U): ListNode[U] =
5. ListNode(elem, this)
6. }
Nota: el nuevo método  prepend tiene un tipo un poco menos restrictivo. Esto permite, por ejemplo, agregar
un objeto de un supertipo a una lista ya creada. La lista resultante será una lista de este supertipo.
Este código ilustra el concepto:

1. object LowerBoundTest extends App {


2. val empty: ListNode[Null] = ListNode(null, null)
3. val strList: ListNode[String] = empty.prepend("hello")
4. .prepend("world")
5. val anyList: ListNode[Any] = strList.prepend(12345)
6. }

Autorefrencias explicitamente tipadas


Cuando se está construyendo software extensible, algunas veces resulta útil declarar el tipo de la
variable this explícitamente. Para motivar esto, realizaremos una pequeña representación de una
estructura de datos Grafo, en Scala.
Aquí hay una definición que sirve para describir un grafo:

1. abstract class Grafo {


2. type Vertice
3. type Nodo <: NodoIntf
4. abstract class NodoIntf {
5. def conectarCon(nodo: Nodo): Vertice
6. }
7. def nodos: List[Nodo]
8. def vertices: List[Vertice]
9. def agregarNodo: Nodo
10. }
Los grafos consisten de una lista de nodos y vértices (o aristas en alguna bibliografía) donde tanto
el tipo nodo, como el vértice fueron declarados abstractos. El uso de  tipos abstractos permite las
implementaciones del trait Grafo proveer sus propias clases concretas para nodos y vértices.
Además, existe un método agregarNodo para agregar nuevos nodos al grafo. Los nodos se
conectan entre sí utilizando el método conectarCon.
Una posible implementación de la clase Grafoes dada en el siguiente programa:
1. abstract class GrafoDirigido extends Grafo {
2. type Vertice <: VerticeImpl
3. class VerticeImpl(origen: Nodo, dest: Nodo) {
4. def desde = origen
5. def hasta = dest
6. }
7. class NodoImpl extends NodoIntf {
8. def conectarCon(nodo: Nodo): Vertice = {
9. val vertice = nuevoVertice(this, nodo)
10. vertices = vertice :: vertices
11. vertice
12. }
13. }
14. protected def nuevoNodo: Nodo
15. protected def nuevoVertice(desde: Nodo, hasta: Nodo): Vertice
16. var nodos: List[Nodo] = Nil
17. var vertices: List[Vertice] = Nil
18. def agregarNodo: Nodo = {
19. val nodo = nuevoNodo
20. nodos = nodo :: nodos
21. nodo
22. }
23. }
La clase GrafoDirigido especializa la clase Grafo al proveer una implementación parcial. La
implementación es solamente parcial, porque queremos que sea posible extender  GrafoDirigido
aun más. Por lo tanto, esta clase deja todos los detalles de implementación abiertos y así tanto los
tipos vértice como nodo son abstractos. De todas maneras, la clase  GrafoDirigido revela
algunos detalles adicionales sobre la implementación del tipo vértice al acotar el límite a la
claseVerticeImpl. Además, tenemos algunas implementaciones preliminares de vértices y nodos
representados por las clases VerticeImpl y NodoImpl.
Ya que es necesario crear nuevos objetos nodo y vértice con nuestra implementación parcial del
grafo, también debimos agregar los métodos constructores  nuevoNodo y nuevoVertice. Los
métodos agregarNodo y conectarCon están ambos definidos en términos de estos métodos
constructores. Una mirada más cercana a la implementación del método  conectarCon revela que
para crear un vértice es necesario pasar la auto-referencia  this al método constructor newEdge.
Pero a this en ese contexto le es asignado el tipo  NodoImpl, por lo tanto, no es compatible con el
tipo Nodo el cual es requerido por el correspondiente método constructor. Como consecuencia, el
programa superior no está bien definido y compilador mostrará un mensaje de error.
En Scala es posible atar a una clase otro tipo (que será implementado en el futuro) al darle su
propia auto-referencia this el otro tipo explicitamente. Podemos usar este mecanismo para
arreglar nuestro código de arriba. El tipo the  this explícito es especificado dentro del cuerpo de la
clase GrafoDirigido.
Este es el progama arreglado:

1. abstract class GrafoDirigido extends Grafo {


2. ...
3. class NodoImpl extends NodoIntf {
4. self: Nodo =>
5. def conectarCon(nodo: Nodo): Vertice = {
6. val vertice = nuevoVertice(this, nodo) // ahora legal
7. vertices = vertice :: vertices
8. vertice
9. }
10. }
11. ...
12. }
En esta nueva definición de la clase  NodoImpl, this tiene el tipo Nodo. Ya que Nodo es abstracta y
por lo tanto todavía no sabemos si NodoImpl es realmente un subtipo de Nodo, el sistema de
tipado de Scala no permitirá instanciar esta clase. Pero, de todas maneras, estipulamos con esta
anotación explicita de tipo que, en algún momento en el tiempo, una subclase de NodeImpl tiene
que denotar un subtipo del tipo Nodo de forma de ser instanciable.
Aquí presentamos una especialización concreta de GrafoDirigido donde todos los miembros
abstractos son definidos:
1. class GrafoDirigidoConcreto extends GrafoDirigido {
2. type Vertice = VerticeImpl
3. type Nodo = NodoImpl
4. protected def nuevoNodo: Nodo = new NodoImpl
5. protected def nuevoVertice(d: Nodo, h: Node): Vertice =
6. new VerticeImpl(d, h)
7. }
Por favor nótese que en esta clase nos es posible instanciar  NodoImpl porque ahora sabemos
queNodoImpl denota a un subtipo de Nodo (que es simplemente un alias para NodoImpl).
Aquí hay un ejemplo de uso de la clase GrafoDirigidoConcreto:
1. object GraphTest extends App {
2. val g: Grafo = new GrafoDirigidoConcreto
3. val n1 = g.agregarNodo
4. val n2 = g.agregarNodo
5. val n3 = g.agregarNodo
6. n1.conectarCon(n2)
7. n2.conectarCon(n3)
8. n1.conectarCon(n3)
9. }

Inferencia de tipos Local


Scala tiene incorporado un mecanismo de inferencia de tipos el cual permite al programador omitir ciertos
tipos de anotaciones. Por ejemplo, generalmente no es necesario especificar el tipo de una variable, ya que el
compilador puede deducir el tipo mediante la expresión de inicialización de la variable. También puede
generalmente omitirse los tipos de retorno de métodos ya que se corresponden con el tipo del cuerpo, que es
inferido por el compilador.

Aquí hay un ejemplo:

1. object InferenceTest1 extends App {


2. val x = 1 + 2 * 3 // el tipo de x es Int
3. val y = x.toString() // el tipo de y es String
4. def succ(x: Int) = x + 1 // el método succ retorna valores Int
5. }
Para métodos recursivos, el compilador no es capaz de inferir el tipo resultado. A continuación mostramos un
programa el cual falla por esa razón:

1. object InferenceTest2 {
2. def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1)
3. }
Tampoco es obligatorio especificar el tipo de los parámetros cuando se trate de métodos polimórficos o sean
instanciadas clases genéricas. El compilador de Scala inferirá esos tipos de parámetros faltantes mediante el
contexto y de los tipos de los parámetros reales del método/constructor.
Aquí se muestra un ejemplo que ilustra esto:

1. case class MyPair[A, B](x: A, y: B);


2. object InferenceTest3 extends App {
3. def id[T](x: T) = x
4. val p = MyPair(1, "scala") // tipo: MyPair[Int, String]
5. val q = id(1) // tipo: Int
6. }
Las últimas dos lineas de este programa son equivalentes al siguiente código, donde todos los tipos inferidos
son especificados explicitamente:

1. val x: MyPair[Int, String] = MyPair[Int, String](1, "scala")


2. val y: Int = id[Int](1)
En algunas situaciones puede ser bastante peligroso confiar en el mecanismo de inferencia de tipos de Scala,
como se ilustra en el siguiente ejemplo:

1. object InferenceTest4 {
2. var obj = null
3. obj = new Object()
4. }
Este programa no compila porque el tipo inferido para la variable obj es Null. Ya que el único valor de ese
tipo es null, es imposible hacer que esta variable refiera a otro valor.

Tipos Unificados
A diferencia de Java, todos los valores en Scala son objetos (incluyendo valores numéricos y funciones).
Dado que Scala está basado en clases, todos los valores son instancias de una clase. El diagrama siguiente
ilustra esta jerarquía de clases:

Jerarquía de clases en Scala


La superclase de todas las clases, scala.Any, tiene dos subclases
directas, scala.AnyVal yscala.AnyRef que representan dos mundos de clases muy distintos: clases para
valores y clases para referencias. Todas las clases para valores están predefinidas; se corresponden con los
tipos primitivos de los lenguajes tipo Java. Todas las otras clases definen tipos referenciables. Las clases
definidas por el usuario son definidas como tipos referenciables por defecto, es decir, siempre
(indirectamente) extienden de scala.AnyRef. Toda clase definida por usuario en Scala extiende
implicitamente el trait scala.ScalaObject. Clases pertenecientes a la infraestructura en la cual Scala esté
corriendo (ejemplo, el ambiente de ejecución de Java) no extienden de scala.ScalaObject. Si Scala es
usado en el contexto de un ambiente de ejecución de Java, entonces scala.AnyRef corresponde
a java.lang.Object. Por favor note que el diagrama superior también muestra conversiones implícitas
llamadas viestas entre las clases para valores.
Aquí se muestra un ejemplo que demuestra que tanto valores numéricos, de caracteres, buleanos y funciones
son objetos, tal como cualquier otro objeto:

1. object UnifiedTypes extends App {


2. val set = new scala.collection.mutable.LinkedHashSet[Any]
3. set += "This is a string" // suma un String
4. set += 732 // suma un número
5. set += 'c' // suma un caracter
6. set += true // suma un valor booleano
7. set += main _ // suma la función main
8. val iter: Iterator[Any] = set.iterator
9. while (iter.hasNext) {
10. println(iter.next.toString())
11. }
12. }
El programa declara una aplicación UnifiedTypes en forma de un objeto singleton de primer nivel con un
método main. La aplicación define una variable local set (un conjunto), la cual se refiere a una instancia de
la clase LinkedHashSet[Any]. El programa suma varios elementos a este conjunto. Los elementos tienen
que cumplir con el tipo declarado para los elementos del conjunto, que es Any. Al final, una representación en
texto (cadena de caracteres, o string) es impresa en pantalla.
Aquí se muestra la salida del programa:

1. This is a string
2. 732
3. c
4. true
5. <function>

Varianzas
Scala soporta anotaciones de varianza para parámetros de tipo para  clases genéricas. A diferencia de Java 5
(JDK 1.5), las anotaciones de varianza pueden ser agregadas cuando una abstracción de clase es definidia,
mientras que en Java 5, las anotaciones de varianza son dadas por los clientes cuando una albstracción de
clase es usada.
En el artículo sobre clases genéricas dimos un ejemplo de una pila mutable. Explicamos que el tipo definido
por la clase Stack[T] es objeto de subtipos invariantes con respecto al parámetro de tipo. Esto puede
restringir el reuso de la abstracción (la clase). Ahora derivaremos una implementación funcional (es decir,
inmutable) para pilas que no tienen esta restricción. Nótese que este es un ejemplo avanzado que combina el
uso de métodos polimórficos, límites de tipado inferiores, y anotaciones de parámetros de tipo covariante de
una forma no trivial. Además hacemos uso declases internas para encadenar los elementos de la pila sin
enlaces explícitos.
class Stack[+A] {
def push[B >: A](elem: B): Stack[B] = new Stack[B] {
override def top: B = elem
override def pop: Stack[B] = Stack.this
override def toString() = elem.toString() + " " +
Stack.this.toString()
}
def top: A = sys.error("no element on stack")
def pop: Stack[A] = sys.error("no element on stack")
override def toString() = ""
}

object VariancesTest extends App {


var s: Stack[Any] = new Stack().push("hello");
s = s.push(new Object())
s = s.push(7)
println(s)
}
La anotación +T declara que el tipo T sea utilizado solamente en posiciones covariantes. De forma similar,  -
T declara que T sea usado en posiciones contravariantes. Para parámetros de tipo covariantes obtenemos una
relación de subtipo covariante con respecto al parámetro de tipo. Para nuestro ejemplo, esto significa
que Stack[T] es un subtipo de Stack[S] si T es un subtipo deS. Lo contrario se cumple para parámetros de
tipo que son etiquetados con un signo -.
Para el ejemplo de la pila deberíamos haber usado el parámetro de tipo covariante  T en una posición
contravariante para que nos sea posible definir el método  push. Ya que deseamos que existan subtipos
covariantes para las pilas, utilizamos un truco y utilizamos un parámetro de tipo abstracto en el método  push.
De esta forma obtenemos un método polimórfico en el cual utilizamos el tipo del elemento  T como límite
inferior de la variable de tipo de  push. Esto tiene el efecto de sincronizar la varianza de  T con su declaración
como un parámetro de tipo covariante. Ahora las pilas son covariantes, y nuestra solución permite por
ejemplo apilar un String en una pila de enteros (Int). El resultado será una pila de tipo  Stack[Any]; por lo
tanto solo si el resultado es utilizado en un contexto donde se esperan pilas de enteros se detectará un error.
De otra forma, simplemente se obtiene una pila con un tipo más general.

Vistas
Parámetros implícitos y métodos también pueden definir conversiones implícitas llamadas vistas. Una vista de
tipo S a T es definida por un valor implícito que tiene una función del tipo S => T, o por un método implícito
convertible a un valor de tal tipo.
Las vistas son aplicadas en dos situaciones: * Si una expresión e es de tipo S, y S no se ajusta al tipo esperado
de la expresión T. * En una selección e.m con e de tipo T, si el selector m no es un miembro de T.
En el primer caso, una vista v es buscada la cual sea aplicable a e y cuyo tipo resultado se ajusta a T. En el
segundo caso, una vista v es buscada para la cual sea aplicable a e y cuyor resultado contenga un miembro
llamado m.
La siguiente operación entre las dos listas xs y ys de tipo List[Int] es legal:
1. xs <= ys
asumiendo que los métodos implícitos list2ordered e int2ordered definidos abajo estén en el alcance
de la operación:
1. implicit def list2ordered[A](x: List[A])
2. (implicit elem2ordered: a => Ordered[A]): Ordered[List[A]] =
3. new Ordered[List[A]] { /* .. */ }
4.
5. implicit def int2ordered(x: Int): Ordered[Int] =
6. new Ordered[Int] { /* .. */ }
La función list2ordered puede ser también expresada con el uso de un límite de vista por un parámetro de
tipo:
1. implicit def list2ordered[A <% Ordered[A]](x: List[A]): Ordered[List[A]] =
...
El compilador de Scala genera código equivalente a la definición de list2ordered vista anteriormente.
El objeto scala.Predef importado implicitamente declara varios tipos predefinidos (ej. Pair) y métodos
(ej. assert) pero también varias vistas. El siguiente ejemplo muestra una idea de la vista
predefinida charWrapper:
1. final class RichChar(c: Char) {
2. def isDigit: Boolean = Character.isDigit(c)
3. // isLetter, isWhitespace, etc.
4. }
5. object RichCharTest {
6. implicit def charWrapper(c: char) = new RichChar(c)
7. def main(args: Array[String]) {
8. println('0'.isDigit)
9. }
10. }

Procesamiento de documentos XML


Scala ha sido usado para crear, parsear y procesar de forma fácil documentos XML. Datos XML pueden ser
representados en Scala tanto usando una representación genérica, o con una representación específica. Este
último es soportado por la herramienta de data-bindingschema2src.

Representación en ejecución
Los datos en XML son representados como árboles etiquetados. A partir de Scala 1.2 (versiones previas
debían usar la opción -Xmarkup), es posible crear convenientemente tales nodos etiquetados utilizando
sintaxis XML.

Considera el siguiente documento XMl:

1. <html>
2. <head>
3. <title>Hello XHTML world</title>
4. </head>
5. <body>
6. <h1>Hello world</h1>
7. <p><a href="http://scala-lang.org/">Scala</a> talks XHTML</p>
8. </body>
9. </html>
Este documento puede ser creado por el siguiente programa en Scala:

1. object XMLTest1 extends App {


2. val page =
3. <html>
4. <head>
5. <title>Hello XHTML world</title>
6. </head>
7. <body>
8. <h1>Hello world</h1>
9. <p><a href="scala-lang.org">Scala</a> talks XHTML</p>
10. </body>
11. </html>;
12. println(page.toString())
13. }
Es posible mezclar expresiones Scala y XML:

1. object XMLTest2 extends App {


2. import scala.xml._
3. val df = java.text.DateFormat.getDateInstance()
4. val dateString = df.format(new java.util.Date())
5. def theDate(name: String) =
6. <dateMsg addressedTo={ name }>
7. Hello, { name }! Today is { dateString }
8. </dateMsg>;
9. println(theDate("John Doe").toString())
10. }

Data Binding
En muchos casos se tiene un DTD para los documentos XML que se quieren procesar. En este caso se quieren
crear clases especiales para esto, y algo de código para parsear y guardar el XML. Scala tiene una ingeniosa
herramienta que transforma tus DTDs en una colección de definiciones de clases en Scala que hacen todo el
trabajo.

También podría gustarte

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy