Guía didáctica donde el programador pueda aprender cómo utilizar las ventajas de un lenguaje de programación multiparadigma como es Scala para la resolución de los diferentes problemas que se puedan plantear.
En primer lugar, se ha realizado una breve introducción a Scala, presentando los conceptos básicos
de los lenguajes de programación.
A continuación, se ha llevado a cabo un análisis de Scala como lenguaje orientado a objetos puro,
introduciendo la jerarquía de clases y conceptos como polimorfismo, genericidad, acotación de
tipos y varianza.
Asimismo, se describe el paradigma funcional utilizando Scala y se enseñan conceptos de la
programación a través del estilo funcional. Se ha detallado la implementación de las estructuras
básicas de la programación, aprovechando las mismas para el aprendizaje de la programación
funcional.
Por otro lado, se analiza la programación monádica a través de las mónadas más populares del
lenguaje ya que, como es conocido, es posible unificar los estilos imperativos y orientado a objetos
con el funcional puro usando mónadas.
En la guía se incluye algunas de las soluciones más populares para realizar pruebas al código
realizado en Scala y se emplean algunas de estas soluciones para realizar pruebas en las estructuras de datos.
Scala propone una solución basada en el paso asíncrono de mensajes inmutables para resolver la
problemática de la concurrencia. El programador también podrá encontrar dentro de este material
didáctico una introducción al modelo de actores de Scala, así como la biblioteca Akka.
Al final de esta guía el programador podrá encontrar las soluciones a los ejercicios propuestos a lo
largo de la misma.
Parámetros de Perforación y Voladura. para Plataformas
Programación funcional en Scala
1.
2.
3. UNIVERSIDAD DE MÁLAGA
ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA
INFORMÁTICA
INGENIERO EN INFORMÁTICA
PROGRAMACIÓN FUNCIONAL EN SCALA
(FUNCTIONAL PROGRAMMING IN SCALA)
Realizado por
RUBÉN PÉREZ LUJANO
Dirigido por
JOSÉ ENRIQUE GALLARDO RUIZ
Departamento
LENGUAJES Y CIENCIAS DE LA COMPUTACIÓN
MÁLAGA, SEPTIEMBRE 2016
7. Agradecimientos
Después de recorrer un largo camino ha llegado el momento de parar, coger aire, mirar hacia
atrás un instante y dar las gracias a todas esas personas que en algún momento han formado
parte del mismo, que han compartido los mejores momentos, que me han ayudado a mirar hacia
delante a pesar de las adversidades y junto a las que me gustaría continuar recorriendo el camino
de la vida.
Quisiera comenzar dando las gracias a D. José Enrique Gallardo Ruiz, tutor del proyecto,
por su comprensión, dedicación y por el gran esfuerzo realizado, así como a D. Blas Carlos
Ruiz Jiménez, un gran profesor y la persona con la que comencé mi proyecto.
Gracias a Irene, mi mujer, por saber sacarme una sonrisa en esos días grises, por tenderme la
mano y ayudar a levantarme en los días más oscuros y por darme ánimos para continuar cuando
más duro se hacía el camino. Gracias por hacerme el hombre más feliz del mundo. Gracias por
apostar por mí. Tú siempre has sido y serás mi apuesta.
Gracias a Carmen, mi madre, por los valores que me ha transmitido, por todo lo que me ha
enseñado durante la vida y por creer en mí hasta el final. Gracias por esa canción inolvidable.
Gracias a Lidia, mi hermana, por su cariño, su ayuda, sus bromas y por confiar ciegamente
en mí. Gracias por todos y cada uno de los momentos que hemos vivido.
Gracias a mis titos, Juan y Paqui, por ofrecer siempre todo su apoyo y demostrarme que
siempre podré contar con ellos.
Gracias a mis amigos Nacho y Jesús, con los que he compartido algunos de los mejores
momentos de este camino. Gracias por vuestros consejos, por esas tardes de risas en el salón.
Gracias a Dña. Lidia Fuentes por ofrecerme esa beca en el momento que más lo necesitaba
y a Dña. Mariam Cobaleda por todos los buenos consejos que me dio.
Gracias a todos los miembros de los centros de día para personas mayores de Estepona y
Coín por acogerme en vuestra familia y por todos los buenos momentos que compartimos.
Para finalizar, aunque no los mencione de una forma explícita, quiero dar las gracias a mis
compañeros de universidad y a mis compañeros de piso.
A todos, eternamente agradecido.
8.
9. Introducción
La elección de un lenguaje para introducir la programación a los alumnos de las actuales
ingenierías en informática es una decisión trascendental; esta elección está ligada a la pregunta:
¿qué características se deben exigir a un “primer” lenguaje para describir de forma limpia y
sencilla los conceptos de la programación?
Hoy en día es comúnmente aceptado entre los profesionales de la enseñanza (y entre los alum-
nos) que hay dos paradigmas esenciales que simplifican los conceptos de la programación, y
que debe conocer un futuro informático: el funcional y el orientado a objetos. Sin embargo es
difícil encontrar un lenguaje que integre ambos paradigmas de forma sencilla si se parte de la
base de que tal lenguaje será el primer contacto de un estudiante con la programación. Además
de esto, se quiere una buena elección desde el punto de vista del programador profesional; es
decir, tal lenguaje debe facilitar de forma “natural” el aprendizaje de los principales lenguajes
con los que se enfrentará el futuro informático profesional.
Entre la oferta actual de lenguajes hay uno que cada vez toma más adeptos en el mundo edu-
cativo: el lenguaje Scala. Scala es un lenguaje de programación multiparadigma diseñado para
expresar patrones comunes de programación en forma concisa, elegante y con tipos seguros.
Integra sutilmente características de lenguajes funcionales y orientados a objetos. La imple-
mentación actual corre en la máquina virtual de Java y es compatible con las aplicaciones Java
existentes; por ello el uso de Scala como un primer lenguaje será un puente importante con el
mundo de la programación profesional.
El trabajo en Scala surge a partir de un esfuerzo de investigación para desarrollar un mejor
soporte de los lenguajes de programación para la composición de software. Hay dos hipótesis
que se desea validar con el experimento Scala. Primera, se postula que un lenguaje de progra-
mación para la composición de software necesita ser escalable en el sentido de que los mismos
conceptos pueden describir tanto partes pequeñas como grandes. Por tanto, los autores se han
concentrado en los mecanismos para la abstracción, composición y descomposición en vez de
añadir un conjunto grande de primitivas que pueden ser útiles para los componentes a algún
nivel de escala, pero no a otro nivel. Segundo, se postula, que el soporte escalable para los
componentes puede ser previsto por un lenguaje de programación que unifica y generaliza la
programación orientada a objetos y la funcional. Para los lenguajes con tipos estáticos, de los
que Scala es un ejemplo, estos dos paradigmas estaban hasta ahora en gran medida separados.
El principal objetivo de este PFC es desarrollar un material didáctico a modo de una guía
donde el programador (tanto el alumno como el profesor) pueda ver de forma clara y conci-
sa, tras una primera toma de contacto con el lenguaje, las ventajas de utilizar un lenguaje de
programación multiparadigma como Scala en la resolución de los diferentes problemas que se
puedan plantear.
En el Capítulo 1: Scala « página 1 » se realiza una breve introducción a Scala, se presenta
el lenguaje y se analizan conceptos básicos de los lenguajes de programación como los tipos de
datos básicos, los operadores, las estructuras de control o la evaluación en Scala.
Página I
10. II
En el Capítulo 2: Programación Orientada a Objetos en Scala « página 31 » se realiza un
análisis de Scala como un lenguaje orientado a objetos puro, se presenta la jerarquía de clases
y conceptos como polimorfismo, genericidad, acotación de tipos y varianza en Scala.
En las primeras secciones del Capítulo 3: Programación Funcional en Scala « página 53 »
se describe el paradigma funcional utilizando Scala y se enseñan conceptos de la programación
a través del estilo funcional. Posteriormente, se detalla la implementación en Scala de las es-
tructuras básicas de la programación, aprovechando dichas estructuras para el aprendizaje de
la programación funcional. Para finalizar el capítulo se hace un repaso por las colecciones que
Scala ofrece al programador.
Es conocido que es posible unificar los estilos imperativos y orientado a objetos con el fun-
cional puro a través de mónadas, pero el uso de éstas no es apropiado para un curso introductorio
a la programación. Después de haber estudiado previamente los aspectos fundamentales de la
programación funcional, en el Capítulo 4: Programación Funcional Avanzada en Scala « página
129 » se analizan conceptos más complejos de este paradigma, como la programación monádica
a través de las mónadas más populares del lenguaje.
En el Capítulo 5: Tests en Scala « página 155 » se presentan brevemente algunas de las
soluciones más populares para realizar pruebas al código realizado en Scala.
Scala propone una solución basada en el paso asíncrono de mensajes inmutables para resol-
ver la problemática de la concurrencia. El modelo de actores de Scala, así como la biblioteca
Akka, se presentan en el Capítulo 6: Concurrencia en Scala. Modelo de actores « página 165 ».
En el Capítulo 7: Conclusiones « página 185 » se justifica razonadamente el uso de Scala
como lenguaje de programación adecuado para ser utilizado dentro del ámbito de la docencia.
Finalmente, en el Capítulo 8: Solución a los ejercicios propuestos « página 189 » se pueden
encontrar las soluciones de los ejercicios propuestos a lo largo de la guía.
17. Capítulo 1
Scala
1.1. Introducción
El nombre Scala significa: diseñado para crecer con la demanda de sus usuarios (Scala-
ble language). Scala es un lenguaje de programación multi-paradigma diseñado para expresar
patrones comunes de programación de forma concisa, elegante y con tipos estáticos. Integra
elegantemente características de lenguajes funcionales y orientados a objetos, lo cual hace que
la escalabilidad sea una de las principales características del lenguaje. La implementación ac-
tual corre en la máquina virtual de Java y es compatible con las aplicaciones existentes en Java.
[7]
Su creador, Martin Odersky1
, y su equipo comenzaron el desarrollo de este nuevo lengua-
je de código abierto en el año 2001, en el laboratorio de métodos de programación en École
Polytechnique Fédérale de Lausanne (EPFL).
Scala hizo su aparición pública sobre la plataforma Máquina Virtual de Java (JVM) en enero
de 2004 y unos meses después haría lo propio sobre la plataforma .NET.
Aunque se trata de un elemento relativamente novedoso dentro del espacio de los lenguajes
de programación, ha adquirido una notable popularidad y las ventajas que ofrece han hecho ya
que cada vez más empresas apuesten por Scala ( Twitter, Linked-in, Foursquare, The Guardian,
...). Las características de Scala le hacen un lenguaje ideal para ser utilizado en centros de
computación, centros de hosting,..., en los que las aplicaciones se ejecutan de forma parale-
la en los supercomputadores, servidores,...que los conforman. Además, Scala es uno de los
lenguajes de programación que se utilizan para desarrollar aplicaciones para los dispositivos
Android.
“ Si le das a una persona un pescado, comerá un día. Si enseñas a pescar a una
persona, comerá toda su vida. Si das herramientas a una persona, puede construirse
1
Martin Odersky es profesor de la EPFL en Lausanne, Suiza y ha trabajado en los lenguajes de programación
durante la mayor parte de su carrera. Estudió programación estructurada y orientada a objetos como estudiante
de doctorado de Niklaus Wirth. Después, mientras trabajaba en IBM y en la Universidad de Yale se enamoró de
la programación funcional. Cuando apareció Java, comenzó a añadir construcciones de programación funcionales
para la nueva plataforma, lo cual dio lugar a Pizza y GJ y eventualmente a Java 5 con los genéricos. Durante ese
tiempo también desarrolló javac, el compilador de referencia actual para Java.
En los últimos 10 años, Martin ha centrado su trabajo en la unificación de los paradigmas de programación
funcional y programación orientada a objetos en el lenguaje Scala. Scala pasó rápidamente del laboratorio de
investigación a convertirse en una herramienta de código abierto y en un lenguaje industrial. Odersky ahora es el
encargado de supervisar el desarrollo de Scala como jefe del grupo de programación de la EPFL y como presidente
de la empresa Typesafe.
Página 1
18. una caña de pescar, ¡y muchas otras herramientas!. Incluso podrá construir una
máquina para fabricar más cañas y así podrá ayudar a otras personas a pescar.
Ahora debemos conceptualizar el diseño de un lenguaje de programación co-
mo un patrón para diseñar lenguajes de programación, como una herramienta para
hacer más herramientas del mismo tipo.
El diseño de un lenguaje de programación debe ser un patrón, un patrón de
crecimiento, un patrón para desarrollar el patrón para la definición de patrones que
los programadores puedan usar en su trabajo y para alcanzar su objetivo.” [1]
1.1.1. Scala. Un lenguaje escalable
Uno de los principales objetivos del diseño de Scala es la construcción de un lenguaje que
permita el crecimiento y la escalabilidad en función de las exigencias del desarrollador. Scala
puede ser utilizado como lenguaje de scripting, así como también se puede adoptar en el proceso
de construcción de aplicaciones empresariales. La conjunción de su abstracción de componen-
tes, su sintaxis reducida, el soporte para la programación orientada a objetos y la programación
funcional han contribuido a que el lenguaje sea más escalable y haga de la escalabilidad una de
sus principales características.
1.1.2. Paradigmas de la programación
Dentro de la programación se pueden distinguir tres paradigmas:
Programación imperativa.
Programación lógica.
Programación funcional.
La programación imperativa pura está limitada por “el cuello de botella de Von Neuman”,
término que fue acuñado por John Backus en su conferencia de la concesión del Premio Turing
por la Asociación para la Maquinaria Computacional (ACM) en 1977. Según Backus:
“Seguramente debe haber una manera menos primitiva de realizar grandes cam-
bios en la memoria, que empujando tantas palabras hacia un lado y otro del cuello
de botella de Von Neumann. No sólo es un cuello de botella para el tráfico de datos,
sino que, más importante, es un cuello de botella intelectual que nos ha mantenido
atados al pensamiento de “una palabra a la vez” en lugar de fomentarnos el pensar
en unidades conceptuales mayores. Entonces la programación es básicamente la
planificación del enorme tráfico de palabras que cruzan el cuello de botella de Von
Neumann, y gran parte de ese tráfico no concierne a los propios datos, sino a dónde
encontrar éstos”
En la actualidad, la programación funcional y la programación orientada a objetos (POO)2
se preocupan mucho menos de “empujar un gran número de palabras hacia un lado y otro” que
otros lenguajes anteriores (como por ejemplo Fortran), pero internamente, esto sigue siendo
2
El paradigma de la POO se considera ortogonal a los paradigmas de programación funcional, programación
imperativa y programación lógica.
Página 2
19. lo que hacen durante gran parte del tiempo los computadores, incluso los supercomputadores
altamente paralelos3
.[26]
Del cuello de botella intelectual criticado por Backus subyace la necesidad de disponer de
otras técnicas para definir abstracciones de alto nivel como son los conjuntos, los polinomios,
las formas geométricas, las cadenas de caracteres, los documentos,...para lo que idealmente se
deberían de desarrollar teorías de conjuntos, formas, cadenas de caracteres, etc.
1.1.2.1. Scala. Un lenguaje multiparadigma
Scala ha sido el primero en incorporar y unificar la programación funcional y la programa-
ción orientada a objetos en un lenguaje estáticamente tipado, donde la clase de cada instancia
se conoce en tiempo de compilación, así como la disponibilidad de cualquier método de una
instancia dada. La pregunta es: ¿por qué se necesita más de un estilo de programación?.
El objetivo principal de la computación multiparadigma es ofrecer un determinado conjunto
de mecanismos de resolución de problemas de modo que los desarrolladores puedan seleccionar
la técnica que mejor se adapte a las características del problema que se está tratando de resolver.
1.1.3. Preparación del sistema
1.1.3.1. Descargar Scala
Para disponer de la última versión de Scala sólo habrá que acceder a la sección “Download”
en la web oficial: http://www.scala-lang.org/download/
Para trabajar con Scala, sólo será necesario un editor de texto y un terminal.
También es posible trabajar con un Entorno de Desarrollo Integrado (IDE), ya que existen
plugins para Eclipse, IntelliJ IDEA o Netbeans.4
1.1.3.2. Herramientas de Scala
Las herramientas de línea de comandos de Scala se encuentran dentro de la carpeta bin de
la instalación.
El compilador de Scala: scalac
El compilador scalac transforma un programa de Scala en archivos .class que podrán ser
utilizados por la JVM. El nombre del archivo no tiene que corresponder con el nombre de la
clase.
Sintaxis: scalac [opciones ...] [archivos fuente ...]
Opciones:
La opción -classpath (-cp) indica al compilador donde encontrar los archivos fuente
La opción -d indica al compilador donde colocar los archivos .class
La opción -target indica al compilador qué versión de máquina virtual usar
3
Un estudio de referencia de base de datos, realizado a partir de 1996, encontró que tres de cada cuatro ciclos
de la unidad central de procesamiento (CPU) se dedican a la espera de memoria.
4
En los próximos capítulos se usará tanto el intérprete de Scala como el IDE Eclipse junto con el plugin
ScalaIDE disponible en la web http://scala-ide.org/.
Página 3
20. El intérprete de código: scala
La instrucción scala puede operar en tres modos:
Puede ejecutar clases compiladas
Puede ejecutar archivos fuente (scripts – Secuencia de instrucciones almacenadas en un
fichero que se ejecutan normalmente de forma interpretada. –)
Puede operar en forma interactiva, es decir como intérprete
Sintaxis: scala [opciones ...] [script u objeto] [argumentos]
Si no se especifica un script o un objeto, la herramienta funciona como un evaluador de
expresiones interactivo. En cambio, si se especifica un script, el comando scala lo compilará y
lo ejecutará. Si se especifica el nombre de una clase, scala la ejecuta.
Algunos comandos importantes:
:help muestra mensaje de ayuda
:quit termina la ejecución del intérprete
:load carga comandos de un archivo
Scala como lenguaje compilado
Como primer ejemplo, se puede observar la definición del programa estándar “Hola mun-
do”.
1 object HolaMundo {
2 def main(args: Array[String]) {
3 println("Hola, mundo!")
4 }
5 }
Algoritmo 1.1: Hola Mundo
La estructura de este programa debería resultar familiar para los lectores que ya conozcan Java.
Consiste de un método llamado main que toma los argumentos de la línea de comandos (un
vector, array en inglés, de objetos del tipo String) como parámetro. El cuerpo de este método
presenta una sola llamada al método predefinido println con el saludo como argumento. El
método main no devuelve un valor, por lo tanto no es necesario que se declare un tipo retorno.
Lo que es menos familiar es la declaración de objetos que contienen al método main. Esta
declaración introduce lo que es comúnmente conocido como singleton objects (objeto single-
ton), 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.
Se puede advertir que el método main no está declarado como estático. Esto es así porque
los miembros estáticos (métodos o campos) no existen en Scala. En vez de definir miembros
estáticos, en Scala se declararán estos miembros en un objeto singleton.
Compilando el ejemplo
Para compilar el ejemplo utilizaremos scalac, el compilador de Scala. El comando scalac
funciona como la mayoría de los compiladores: toma un archivo fuente como argumento, al-
gunas opciones y produce uno o varios archivos objeto. Los archivos objeto que produce son
archivos class para la JVM.
Página 4
21. Si guardamos el programa anterior en un archivo llamado HolaMundo.scala, podemos com-
pilarlo ejecutando el siguiente comando:
$ scalac HolaMundo.scala
Esto generará algunos archivos class en el directorio actual. Uno de ellos se llamará Hola-
Mundo.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, como
se puede comprobar, produce la salida esperada:
$ scala HolaMundo
Hola, mundo!
Scala como lenguaje interpretado desde un script
Como ya se ha introducido anteriormente, es posible ejecutar archivos fuente haciendo uso
del comando scala. A continuación se muestra como crear un script básico llamado hola.scala:
1 println("Hola mundo, desde un script!")
Ejecutando el ejemplo desde la línea de comandos se comprueba que el resultado obtenido
es el esperado:
$>scala hola.scala
Hola mundo, desde un script!
Desde la línea de comandos se le pueden pasar argumentos a los scripts mediante el vector
de argumentos args. En Scala, el acceso a los elementos de un vector se realiza especificando
el índice entre paréntesis (no entre corchetes como en Java)5
. Se ha definido el siguiente script,
llamado holaarg.scala, con el objetivo de probar el funcionamiento del paso de argumentos
desde la línea de comandos:
1 println("Hola, "+ args(0) +"!")
Si se ejecuta:
$> scala holaarg.scala pepe
En este comando, “pepe” se pasa como argumento desde la línea de comandos, el cual se
accede desde el script mediante args(0). La salida obtenida es la que se esperaba:
Hola, pepe!
Scala como lenguaje interpretado desde un intérprete
Para lanzar el intérprete de Scala, se tiene que abrir una ventana de terminal y teclear scala.
Aparecerá el prompt del intérprete de Scala a la espera de recibir expresiones. Funciona, al igual
que el intérprete de Scheme o Haskell, mediante el Bucle Leer-Evaluar-Imprimir (REPL).
El intérprete de Scala será muy utilizado durante el capítulo dedicado a la programación
funcional, por lo que se recomienda familiarizarse con el mismo.
5
Notación que es homogénea para las demás estructuras, incluso las estructuras definidas por el usuario.
Página 5
22. 1.2. Conceptos básicos
1.2.1. Elementos de un lenguaje de programación
Un lenguaje de programación debe proveer:
expresiones primitivas que representen los elementos más simples
maneras de combinar expresiones
maneras de abstraer expresiones, lo cual introducirá un nombre para esta expresión y nos
permitirá hacer referencia a la expresión.
1.2.2. Elementos básicos en Scala
1.2.2.1. Tipos de datos básicos en Scala
En Scala se pueden encontrar los mismos tipos de datos básicos que en Java. El tamaño que
ocupa cada uno de ellos en memoria, así como la precisión de los tipos primitivos de Scala,
también se corresponden con los de Java. Aunque se hable de tipos de datos, en Scala todos los
tipos de datos son clases. En la tabla 1.1 se muestran los tipos básicos en Scala.
Tipo de dato Tamaño Rango Ejemplo
Byte 8 bits con signo [−128, 127] 38
Short 16 bits con signo [−32768, 32767] 23
Int 32 bits con signo [−231
, 231
− 1] 45
Long 64 bits con signo [−263
, 263
− 1] 3434115
Float 32 bits con signo [−3,4028 ∗ 1038
, 3,4028 ∗ 1038
1.38
Double 64 bits con signo [−1,7977 ∗ 10308
, 1,7977 ∗ 10308
] 54.37
Boolean true o false true
Char 16 bits con signo [0, 216
− 1] ’F’
String secuencia de caracteres Cadena de caracteres "hola mundo!"
Tabla 1.1: Tipos de datos primitivos y tamaño en Scala
Los tipos Byte, Short, Int y Long reciben el nombre de tipos enteros. Los tipos enteros junto
con los tipos Float y Double son llamados tipos numéricos.
Excepto String, que es del paquete java.lang, el resto se encuentran en el paquete Scala.
Todos se importan automáticamente.
Literales de tipos básicos en Scala
Las reglas sobre literales que usa Scala son bastante simples e intuitivas. A continuación se
verán las principales características de los literales básicos en Scala.
Literales enteros
Los mayoría de literales enteros que utilicemos serán de tipo Int. Los literales enteros
también podrán ser de tipo Long añadiendo el sufijo L o l al final del literal. A continuación se
muestran algunos ejemplos:
Página 6
23. scala> 5
res0: Int = 5
scala> 777L
res3: Long = 777
scala> 0xFFAFAFA5
res2: Int = -5263451
scala> 0777L
<console>:1: error: Non-zero integral values may not have a leading zero.
0777L
^
Los literales enteros no podrán tener el cero como primer dígito6
, excepto el entero 0 y
aquellos representados en notación hexadecimal. Los enteros representados en notación hexa-
decimal comienzan por 0x o 0X, pudiendo estar seguido por dígitos entre 0 y 9 o letras entre A
y F (en mayúscula o minúscula).
Literales en punto flotante
Los literales en punto flotante serán del tipo de datos Float cuando se añada el sufijo F o
f. En otro caso serán de tipo Double. Estos literales sí podrán contener uno o varios ceros como
primeros dígitos.
scala> 0.0
res0: Double = 0.0
scala> 01.2
res1: Double = 1.2
scala> 01.2F
res2: Float = 1.2
scala> 00045.34
res3: Double = 45.34
Literales lógicos
Los literales lógicos o booleanos son aquellos que pertenecen a la clase Boolean y que sólo
pueden tener uno de los dos valores booleanos: true o false.
Literales de tipo símbolo
Aunque el tipo de datos Symbol (scala.Symbol) no se considera un tipo básico de Scala,
merece la pena tenerlo en cuenta. Los símbolos serán cadenas de caracteres no vacías precedidas
del prefijo ’. La clase case Symbol está definida de la siguiente forma:
1 package scala
2 final case class Symbol private (name: String) {
3 override def toString: String = "’" + name
4 }
Ejemplo:
6
En versiones anteriores de Scala, cuando un entero comenzaba por cero era porque el número estaba en base
8 y, por tanto, sólo podía estar seguido de los dígitos comprendidos entre 0 y 7.
Página 7
24. scala> ’miSimbolo
res4: Symbol = ’miSimbolo
Literales de tipo carácter
Un literal de tipo carácter consiste en un carácter encerrado entre comillas simples que
representará un carácter representable o un carácter de escape7
. El tipo de datos de los literales
de tipo carácter es Char. El estándar de codificación de caracteres utilizado para representar los
caracteres es Unicode. También podremos indicar el código unicode del carácter que queramos
representar encerrado entre comillas simples. Veamos algunos ejemplos:
scala> ’u0050’
res5: Char = P
scala> ’S’
res6: Char = S
scala> ’n’
res7: Char =
Literales de tipo cadena de caracteres
Un literal del tipo cadena de caracteres será una secuencia de caracteres encerradas entre
comillas dobles cuyo tipo de datos será String. Los caracteres que conformen la cadena de
caracteres podrán ser tanto caracteres representables, como caracteres de escape. Por ejemplo:
scala> "Hola Mundo!"
res8: String = Hola Mundo!
scala> "Hola " Mundo!"
res9: String = Hola " Mundo!
Los literales de tipo cadena de caracteres también pueden ser multilínea en cuyo ca-
so estarán encerrados entre tres comillas dobles. En este caso, la cadena de caracteres podrá
estar formada por los caracteres representables, caracteres de escape o por caracteres no repre-
sentables como salto de línea o cualquier otro carácter especial como comillas dobles, barra
inversa,...con la única salvedad de que sólo podrá haber tres o más comillas dobles al final.
Veamos algún ejemplo:
scala> """y dijo:
| "mi nombre es Bond, James Bond"
| ///"""
res10: String =
y dijo:
"mi nombre es Bond, James Bond"
///
Caracteres de escape
Los caracteres de escape que se muestran en la tabla 1.2 son reconocidos tanto en los
literales de caracteres como en los literales de cadenas de caracteres.
7
También conocidos como secuencia de escape
Página 8
25. Carácter de escape Unicode Descripción
b u0008 Retroceso BS
t u0009 Tabulador horizontal HT
n u000A Salto de línea LF
f u000C Salto de página FF
r u000D Retorno de carro CR
" u0022 Comillas dobles
’ u0027 Comilla simple
u005c Barra inversa
Tabla 1.2: Caracteres de escape reconocidos por Char y String
En versiones anteriores de Scala era posible definir cualquier carácter que tuviera un código
unicode entre 0 y 255, indicando el código en base octal. Esto se indicaba precediendo al código
octal de . Por ejemplo 150 representaría el carácter h. Esta representación está depreciada en
la actualidad, siendo sustituida por la representación en base hexadecimal de los caracteres.
scala> "150"
<console>:1: warning: Octal escape literals are deprecated, use u0068 instead.
"150"
^
res11: String = h
scala> "u0068"
res12: String = h
Expresiones de tipos básicos en Scala
Las expresiones en Scala pueden estar formadas por:
Un literal válido de cada uno de los tipo de datos básicos vistos en la tabla 1.1. Por
ejemplo: 1, 2.5, 3.74E10f, true, false, ’a’...
Un operador 8
junto con dos operandos (operadores binarios) o un operando (operadores
unarios) del tipo de datos compatible con el operador.
El último valor evaluado en un bloque (en la llamada a una función o método).
Expresiones aritméticas en Scala
Las expresiones aritméticas son aquellas que, tras ser evaluadas, devuelven un valor nu-
mérico. Los tipos de datos Byte, Short, Int y Long serán utilizados para representar valores
numéricos de tipo entero. Con los tipos de datos Float y Double se representarán valores de
tipo real.
El tipo de datos devuelto por defecto al evaluar una expresión que represente un número
entero es Int mientras que Double será el tipo de datos por defecto devuelto al evaluar una
expresión que represente un número real.
scala> 1.2
res0: Double = 1.2
scala> 5
res1: Int = 5
8
Los operadores se verán en la Subsubsección 1.2.2.2: Operadores « página 10 »
Página 9
26. Expresiones booleanas
Una expresión booleana puede estar compuesta por un literal, por uno de los operadores
lógicos mostrados en la tabla 1.6 o ser el valor devuelto por las operaciones usuales de com-
paración que se muestran en la tabla 1.5 de operadores relacionales en Scala. Una expresión
booleana también puede ser el resultado de la evaluación de cualquier expresión booleana co-
mo, por ejemplo, el resultado de una función o método.
1.2.2.2. Operadores
Los operadores se utilizarán para combinar expresiones de los tipos de datos básicos. En
Scala los operadores son en realidad métodos y, por tanto, se reducen a la llamada a un método
de un objeto de una de clases de los tipos de datos básicos. Es decir, 1 + 2 realmente invoca
al método + del objeto 1 con el parámetro 2: (1).+(2). Es más, en la clase Int hay diferentes
definiciones del método + (método sobrecargado) que difieren las unas de las otras en el tipo
del parámetro con el que se invocan y que nos permiten realizar la suma de objetos de diferentes
tipos enteros. Por ejemplo, existe otro método + en la clase Int que recibe como parámetro un
objeto de tipo Long y devuelve otro objeto de tipo Long.
Los operadores se pueden clasificar, atendiendo a su notación en:
Operadores infijos. Son operadores binarios en los que el método que se va a invocar
se ubica entre sus dos operandos. Un ejemplo de operador infijo podría ser el operador
aritmético + de los tipos numéricos.
Operadores prefijos. Son operadores unarios en los que el nombre del método se sitúa
delante del objeto que invocará al método. Como por ejemplo: !b, -5,...
Operadores postfijos9
. Son aquellos operadores unarios en los que el nombre del método
se sitúa detrás del objeto que invocará al método. Por ejemplo: 5 abs, 2345 toLong.
Infijos, Prefijos y Postfijos
En Scala los operadores no tienen una sintaxis especial10
, como ocurre en otros lenguajes
de programación como Haskell, por lo que cualquier método puede ser un operador. Lo que
convertirá un método en un operador será la forma de usar el mismo. Por ejemplo, si se escribe
1.+(2), + no será un operador. Pero si se escribe 1 + 2, + sí será un operador.
Existe la posibilidad de definir nuevos operadores prefijos definiendo métodos que comien-
cen por unary_ y estén seguidos por un identificador válido. Por ejemplo, si en un tipo de datos
se define un método unary_!, una instancia de este tipo de datos podrá invocar ! en notación
prefija. Scala transforma las llamadas a operadores prefijos en llamadas al método unary_. Por
ejemplo, la expresión !p es transformada por Scala en la invocación de p.unary_!.
Los operadores postfijos son métodos que no reciben parámetros y, por convenio, no es
necesario el uso de paréntesis11
9
Los operadores postfijos tienen que ser habilitados -visibles- importando scala.language.postfixOps o indican-
do al compilador la opción -language:postfixOps
10
Excepto los operadores prefijos en los que los identificadores que pueden ser usados en este tipo de operadores
son +, -, ! y ~.
11
Excepto si el método presenta efectos colaterales, como println(), en cuyo caso sí habrá que poner los parén-
tesis.
Página 10
27. Los operadores infijos son aquellos métodos que reciben un argumento. Los operadores
infijos se ubican entre sus dos operandos (como el operador +, en el que el primer operando
será el objeto que invoca al método y el segundo operando es el argumento que recibe).
Prioridad y Asociatividad de los operadores
Cuando en una expresión aparecen varios operadores, la prioridad de los operadores nos
indicará el orden de evaluación de las diferentes partes que componen la expresión, es decir, qué
partes de la expresión son evaluadas antes. Por ejemplo, el resultado de evaluar la expresión 100
- 40 * 2 es 20, no 120, ya que el operador * tiene mayor prioridad que el operador +. El resultado
de la anterior expresión es el mismo que si se evalúa la expresión 100 - (40 * 2). Si se quisiera
cambiar el orden de evaluación anterior se debería escribir la expresión: (100 - 40) * 2, cuyo
resultado sería 120.
Scala determina la prioridad de los operadores basándose en el primer carácter de los méto-
dos usados con notación de operador. La excepción a esta regla es el operador de menor priori-
dad en Scala: el operador de asignación (los operadores que terminan con el carácter ’=’). Así,
con la excepción ya comentada de los operadores de asignación, la precedencia de operadores
se determinará según el primer carácter, tal como se muestra en la tabla 1.3, donde encontramos
la prioridad de los operadores básicos, de forma que los operadores de mayor prioridad se en-
cuentran en la parte superior de la tabla y los operadores de menor prioridad en la parte inferior.
La prioridad de cualquier operador definido por el usuario se definirá por el primer carácter
empleando según esta misma tabla.
Tipo Operador Asociatividad
Postfijos (), [],... Izquierda
Unarios ! ˆ Derecha
Multiplicativos * / % Izquierda
Aditivos + - Izquierda
Binario : Izquierda
Binario = ! Izquierda
Desplazamiento >> >>> << Izquierda
Relación <><= >= Izquierda
Igualdad == != Izquierda
Bit a bit AND & Izquierda
Bit a bit XOR ˆ Izquierda
Bit a bit OR | Izquierda
AND lógico && Izquierda
OR lógico || Izquierda
Todas las letras Izquierda
Asignación = += -= *= /= %= >>= <<= &= ˆ= |= Derecha
Coma , Izquierda
Tabla 1.3: Prioridad y asociatividad de los operadores
Cuando se encuentran en una expresión varios operadores con la misma prioridad, será
otra de las características de los operadores, la asociatividad, la encargada de indicar cómo
se agrupan los operadores y, por tanto, como se evaluará la expresión. La asociatividad de un
operador en Scala se determina por el último carácter del operador. Si el último carácter de
Página 11
28. un operador binario es :, el operador tendrá asociatividad derecha, lo que quiere decir que
los métodos que terminen en : serán invocados por el operando situado en el lado derecho del
operador y se les pasará como parámetro el operando izquierdo. Es decir, si se tiene la expresión
x /: y, será equivalente a y./:(x). En cualquier otro caso, el operador presentará asociatividad
izquierda.
La asociatividad de un operador no influye en el orden de evaluación de los operandos,
que siempre será de izquierda a derecha. En la anterior expresión, x/:y, primero se evaluará el
operando x y después el operando y. Es decir, la expresión es tratada como el siguiente bloque:
1 {val t=x;y./:(x)}
Como se ha dicho anteriormente, la asociatividad determinará como se agrupan los opera-
dores en caso de que aparezcan en una expresión varios operadores con la misma prioridad. Si
los métodos terminan en ’:’, se agruparán de derecha a izquierda, mientras que en cualquier otro
caso se agruparán de izquierda a derecha. Si se tienen las expresiones a * b * c y x /: y /: z, se
evaluarán como (a * b) * c y x /: (y /: z), respectivamente. A pesar de conocer las reglas que
determinan la prioridad de los operadores y la asociatividad de los mismos, es aconsejable usar
paréntesis para aclarar cuales son los operadores que actúan sobre cada una de las expresiones.
Operadores aritméticos
Los operadores aritméticos son aquellos que operan con expresiones enteras o reales, es
decir, con objetos de tipo Short, Int, Double o Long. En Scala se pueden distinguir dos tipos de
operadores aritméticos: unarios y binarios. En la tabla 1.4 se muestran los diferentes operadores
aritméticos presentes en Scala.
Descripción Operador Tipo
Signo positivo + unario
Signo negativo - unario
Suma + binario
Resta - binario
División / binario
Producto * binario
Resto % binario
Tabla 1.4: Operadores aritméticos en Scala
Todos los operadores admiten expresiones enteras y reales. En el caso de los operadores
binarios, si los dos operandos son enteros o reales el resultado de la operación será un valor
entero o real respectivamente. Si uno de sus operandos es entero y el otro real entonces el
resultado devuelto será de tipo real.
Los operadores aritméticos también servirán para combinar expresiones de tipo carácter. En
este caso, Scala tomará el valor decimal del código unicode que represente el carácter de cada
uno de los operadores.
scala> 1.5 * 3
res2: Double = 4.5
scala> 5 * 3
res3: Int = 15
Página 12
29. Operadores relacionales
El uso de operadores relacionales permite comparar expresiones de tipos de datos com-
patibles, devolviendo un resultado de tipo booleano: true o false. Scala soporta los operadores
relacionales que se muestran en la tabla 1.5. Los operadores relacionales son binarios.
Descripción Operador
Menor <
Menor/Igual <=
Mayor >
Mayor/Igual >=
Distinto !=
Igual ==
Tabla 1.5: Operadores relacionales en Scala
Como se puede apreciar en el siguiente ejemplo, los resultados obtenidos después de com-
parar diferentes expresiones numéricas (reales o enteras) son los esperados:
scala> 12.0 * 3 == 3 * 12
res4: Boolean = true
scala> 3.0f < 7
res5: Boolean = true
scala> "cadena" == "cadena"
res6: Boolean = true
En cambio, si lo que pretende es comparar dos expresiones booleanas se deberá tener en
cuenta que el valor false se considera menor que el valor true.
scala> 13<10 < (11==11.0)
res7: Boolean = true
Cuando se comparen expresiones del tipo de datos Char o String se deberá tener en cuenta
que se basan en el valor decimal del código unicode de cada carácter12
. En el caso de expresiones
del tipo de datos String, cuando los caracteres situados en la primera posición de las cadenas que
se estén comparando sean iguales (mismo código unicode) se comparará el segundo carácter de
ambas cadenas y así sucesivamente hasta que se encuentre el primer par de caracteres distintos,
ubicados en la misma posición en ambas cadenas, o hasta que la cadena cuya longitud sea
menor se termine, en cuyo caso la cadena de menor longitud será menor que la cadena de mayor
longitud. A continuación se muestran unos ejemplos en el intérprete de Scala que pueden ayudar
a aclarar estos conceptos:
scala> "hola mundo" < "hola mundo scala!"
res8: Boolean = true
scala> ’a’<’b’
res9: Boolean = true
scala> "hola"<"hola"
res10: Boolean = false
scala> "hola"<="hola"
res11: Boolean = true
scala> "hola" <= "Hola"
res12: Boolean = false
12
En Scala, una expresión de tipo String y otra de tipo Char no se pueden comparar.
Página 13
30. En otros lenguajes de programación como Java, el operador relacional == se utiliza pa-
ra comparar tipos primitivos (comparando si los valores son iguales, como en Scala) y tipos
referenciados (comparando igualdad referencial). En Scala se puede comparar la igualdad refe-
rencial de tipos referenciados usando eq y neq.
Operadores lógicos
Los operadores lógicos permitirán combinar expresiones lógicas. Las expresiones lógicas
son todas aquellas expresiones que tras ser evaluadas se obtiene: verdadero o falso.
Scala soporta los operadores lógicos que se muestran en la tabla 1.6.
Descripción Operador Tipo
AND lógico && binario
OR lógico || binario
NOT lógico ! unario
Tabla 1.6: Operadores lógicos en Scala
Operadores bit a bit.
Los operadores bit a bit trabajan sobre cadenas de bits aplicando el operador en cada uno
de los bits de los operadores. Las tablas de verdad para los operadores &, | y ˆ son las que se
muestran en la tabla 1.7
p q p& q p ˆ q p | q
0 0 0 0 0
0 1 0 1 1
1 0 1 1 0
1 1 0 1 1
Tabla 1.7: Tabla de verdad de los operadores bit a bit &, | y ˆ .
Los operadores bit a bit soportados por Scala se muestran en la tabla 1.8.
Operador Descripción Ejemplo
& AND binario a & b
| OR binario a | b
ˆ XOR binario 45
˜ Complemento a uno (intercambio de bits) ˜a
<< Desplazamiento binario a la izquierda a <<2
>> Desplazamiento binario a la derecho a >>2
>>> Desplazamiento a la derecha con relleno de ceros a >>>2
Tabla 1.8: Operadores bit a bit.
Página 14
31. Operadores de igualdad.
Scala soporta los operadores de asignación mostrados en la tabla 1.9.
Operador Descripción Ejemplo
= Operador de asignación simple C = A + B
+= Suma y asignación A += B equivalente a A = A + B
-= Resta y asignación A -= B equivalente a A = A - B
*= Multiplicación y asignación A *= B equivalente a A = A * B
/= División y asignación A /= B equivalente a A = A / B
%= Módulo y asignación A %=B equivalente a A = A % B
<<= Desplazamiento a la izquierda y asignación A <<= 2 equivalente a A = A <<2
&= Bit a bit AND y asignación A &= 2 equivalente a A = A & 2
ˆ= Bit a bit XOR y asignación A ˆ= 2 equivalente a A = A ˆ 2
|= Bit a bit OR y asignación A |= 2 es equivalente a A = A | 2
Tabla 1.9: Operadores de asignación.
1.2.2.3. Nombrar expresiones
Es posible nombrar una expresión con la palabra reservada def y utilizar su nombre (identi-
ficador) en lugar de la expresión:
scala> def scale = 5
scale: Int
scala> 7 * scale
res4: Int = 35
scala> def pi = 3.141592653589793
pi: Double
scala> def radius = 10
radius: Int
scala> 2 * pi * radius
res5: Double = 62.83185307179586
def es una primitiva declarativa: le da un nombre a una expresión, pero no la evalúa.
scala> def r=8/0
r: Int
scala> r
java.lang.ArithmeticException: / by zero
at .r(<console>:7)
... 33 elided
Se puede observar que la definición de r no da error. El error se produce en el momento que
se evalúa por primera vez r.
Es lo contrario que la forma especial define de Scheme, que se utiliza para asociar nombres
con expresiones, y en primer lugar se evalúa la expresión. En realidad, nombrar una expresión
sólo aportará azúcar sintáctico, permitiendo crear funciones y evitando escribir la expresión
lambda asociada. Es decir, es lo mismo que crear una función en Scheme sin argumentos.
Alternativamente, se verá como Scala permite una definición de variables utilizando val o
var.
1.2.2.4. Variables
Las variables están encapsuladas en objetos, es decir, no pueden existir por si mismas. Las
variables son referencias a instancias de clases.
Página 15
32. Para definir una variable se requiere:
Definir su mutabilidad
Definir el identificador
Definir el tipo (opcional)
Definir un valor inicial
Sintaxis para definir una nueva variable:
<var | val> < identificador> : < Tipo> = < _ | Valor Inicial>
La nueva variable podrá ser un literal o el resultado de la evaluación de una expresión, una
función o el valor devuelto por un método. Pero también se podrán definir funciones, métodos,
bloques, etc.
Por ejemplo:
1 val x : Int = 1
Se usa la palabra reservada val para indicar que la referencia no puede reasignarse, mientras
que se utiliza var para indicar que sí puede reasignarse. La variable val es similar a una variable
final en Java: una vez inicializada, no se podrá reasignar. Una variable var, por el contrario, se
puede reasignar múltiples veces.
Todas las variables o referencias en Scala tienen un tipo, debido a que el lenguaje es estric-
tamente tipado. Sin embargo, los tipos pueden en muchos casos omitirse, porque el compilador
de Scala tiene inferencia de tipos. Por ejemplo, las siguientes definiciones son equivalentes:
scala> var x : Int = 1
scala> var x = 1
Otros ejemplos:
val msg = "Hola mundo!"
msg: java.lang-String = Hola mundo!
En este ejemplo se aprecia que Scala tiene inferencia de tipos, es decir, al haber inicializado
la variable msg con una cadena, Scala asocia el tipo de msg al tipo de datos String. Si se intenta
modificar el valor de msg, no se podrá y obteniéndose un error ya que se ha definido como val:
scala> msg = "Hasta luego!"
<console>:5: error: reassignment to val
msg = "Hasta luego!"
Si se imprime por pantalla el valor de msg:
scala> println(msg)
Hola mundo!
Si se quisiera reasignar el valor de una variable habría que utilizar la palabra reservada var:
scala> var saludo = "Hola mundo!"
saludo: java.lang.String = Hola mundo!
scala> saludo = "Hasta luego!"
saludo: java.lang.String = Hasta luego!
Aunque todas las declaraciones de variables podrían realizarse utilizando var, se recomienda
encarecidamente usar val cuando la variable no vaya a mutar.
Página 16
33. 1.2.3. Uso del carácter punto y coma (;) en Scala
En Scala, el uso del punto y coma al final de una línea de programa es opcional en la
mayoría de las ocasiones, siendo recomendado omitir el mismo siempre y cuando su uso no sea
obligatorio. Se podría escribir:
1 def pi = 3.14159;
aunque muchos programadores omitirán el uso del (;) y simplemente escribirán:
1 def pi = 3.14159
¿Cuándo es obligatorio el uso del punto y coma?
El uso del punto y coma es obligatorio para separar instrucciones escritas en la misma línea.
1 def y = x - 1; y + y
Uso del punto y coma y operadores infijos en Scala.
Uno de los problemas derivados de omitir el uso del punto y coma en Scala es cómo escribir
expresiones que ocupen varias líneas. Si se escribiera:
Expresión larga
+ otra expresión larga
sería interpretado por Scala como dos expresiones. Si lo que se quiere es que se interprete como
una única expresión, se podría hacer de dos formas:
Se podría encerrar una expresión de varias líneas entre paréntesis, dando por hecho que
no se usará el punto y coma en éstas líneas:
(Expresión larga
+ otra expresión larga)
Se podría escribir el operador al final de la línea, indicándole así a Scala que la expresión
no está finalizada:
Expresión larga +
otra expresión larga
Por tanto, por norma general, los saltos de línea serán tratados como puntos y coma, salvo
que algunas de las siguientes condiciones sea cierta:
La línea en cuestión finaliza con una palabra que no puede actuar como final de sentencia,
como por ejemplo un espacio (“ ”) o los operadores infijos.
La siguiente línea comienza con una palabra que no puede actuar como inicio de sentencia
La línea termina dentro de paréntesis (...) o corchetes [...], puesto que éstos últimos no
pueden contener múltiples sentencias
Página 17
34. 1.3. Bloques en Scala
Un bloque en Scala estará encerrado entre llaves { ...}. Dentro de un bloque se podrán
encontrar definiciones o expresiones. El último elemento de un bloque será una expresión que
definirá el valor del bloque, la cual podrá estar precedida por definiciones auxiliares.
Los bloques también son expresiones en sí mismos, por lo que un bloque podrá aparecer en
cualquier lugar en el que pueda aparecer una expresión. Ejemplo:
1 { val x = f(3)
2 x * x
3 }
1.3.1. Visibilidad y bloques en Scala
Scala sigue las reglas de ámbito habituales en lenguajes como C o Java. Las definiciones
realizadas dentro de un bloque sólo serán visibles dentro del mismo y ocultan las definiciones
realizadas fuera del bloque que tengan el mismo nombre.
Las definiciones realizadas en bloques externos estarán visibles dentro de un bloque siempre
y cuando no sean ocultadas por otras definiciones con el mismo nombre en el bloque.
Ejemplo:
1 val x = 10
2 def f(y: Int)=y+1
3 val result = {
4 val x = f(3)
5 x * x
6 } + x
El resultado de la ejecución de este código será 26
1.4. Evaluación en Scala
1.4.1. Evaluación de expresiones
Para evaluar una expresión no primitiva:
1. Se toma el operador con mayor prioridad (ver la Sección 1.2.2.2: Prioridad y Asociati-
vidad de los operadores « página 11 »), o en caso de que todos los operadores tenga la
misma prioridad se toma el operador situado más a la izquierda,
2. Se evalúa sus operandos (de izquierda a derecha),
3. Se aplica el operador a los operandos.
Un nombre se evalúa reemplazando el mismo por su definición. El proceso de evaluación fina-
liza cuando se obtiene un valor.
Ejemplo:
(2 ∗ pi) ∗ radius
(2 ∗ 3,14159) ∗ radius
6,28318 ∗ radius
Página 18
35. 6,28318 ∗ 10
62,8318
1.4.2. Evaluación de funciones
La evaluación de funciones parametrizadas es similar a la de operadores:
1. Se evalúan los parámetros de la función de izquierda a derecha,
2. Se reemplaza la llamada a la función por la definición de la misma y, al mismo tiempo,
3. Se reemplazan los parámetros formales de la función por el valor de sus argumentos.
Este sistema de evaluación de expresiones es llamado modelo de sustitución, cuya idea
gira en torno a que lo que hace una evaluación es reducir una expresión a su valor y puede ser
aplicado a todas las expresiones (siempre y cuando no tengan efectos laterales). El modelo de
sustitución está formalizado en el λ-cálculo. Esta estrategia de evaluación es conocida como
evaluación estricta o Call by Value.
Existe otra alternativa: no evaluar los argumentos antes de reemplazar la función por la
definición de la misma, llamada evaluación no estricta o Call by Name.
1.4.3. Sistema de Evaluación de Scala
Scala usa evaluación estricta normalmente, pero se puede indicar explícitamente que se
desea que algún argumento de una función use evaluación no estricta. Para esto último, se
pondrá => delante del tipo del parámetro que se desee evaluar siguiendo esta estrategia.
Ejemplo:
1 def miConst (x : Int, y: =>Int) = x
Algoritmo 1.2: Función con dos parámetros. El primero es evaluado por valor y el segundo por
nombre
1.4.3.1. Valores de las definiciones
Anteriormente se ha dicho que los parámetros de las funciones pueden ser pasados por valor
(evaluación estricta) o por nombre (evaluación no estricta).
La misma distinción se puede aplicar a las definiciones. La forma def es por nombre (eva-
luación no estricta) y la forma val es por valor (evaluación estricta).
1.4.3.2. Evaluación de Booleanos
En la tabla 1.10 aparecen representadas las reglas de reducción para expresiones booleanas.
Página 19
36. !true → false
!false → true
true && e → e
false && e → false
true || e → true
false || e → e
Tabla 1.10: Reglas de reducción para expresiones booleanas
En la tabla 1.10 se puede apreciar que && y || no siempre necesitan que el operando derecho
sea evaluado. En estos casos, se dirá que estas expresiones usan una evaluación en cortocir-
cuito.
1.4.4. Ámbito y visibilidad de las variables
El funcionamiento de los ámbitos en Scala es el siguiente:
Una invocación a una función crea un nuevo ámbito en el que se evalúa el cuerpo de la
función. Es el ámbito de evaluación de la función.
El ámbito de evaluación se crea dentro del ámbito en el que se definió la función a la que
se invoca.
Los argumentos de la función son variables no mutables locales de este nuevo ámbito que
quedan ligadas a los parámetros que se utilizan en la llamada.
En el nuevo ámbito se pueden definir variables locales.
En el nuevo ámbito se pueden obtener el valor de variables del ámbito padre.
Primer ejemplo:
Supongamos el siguiente código en Scala:
1 def f(x: Int, y: Int): Int = {
2 val z = 5
3 x+y+z
4 }
5 def g(z: Int): Int = {
6 val x = 10
7 z+x
8 }
9 f(g(3),g(5))
Se definen dos funciones f y g, dentro de cada una de las cuales hay declaradas distintas
variables locales. Las funciones f y g devuelven una suma de los parámetros con la variable
local. En la última línea de código se realiza una invocación a f con los resultados devueltos por
dos invocaciones a g .
¿Cuántos ámbitos locales se crean? ¿En qué orden?
1. En primer lugar se realizan las invocaciones a g . Cada una crea un ámbito local en el que
se evalúa la función. Las invocaciones devuelven 13 y 15, respectivamente.
Página 20
37. 2. Después se realiza la invocación a f con esos valores 13 y 15. Esta invocación vuelve a
crear un ámbito local en el que se evalúa la expresión x+y+z , devolviendo 33.
Por ahora, todo es bastante normal. La diversión empezará cuando se construyan funciones
anónimas durante la ejecución de otras funciones, lo cual permitirá la definición de cierres.
1.5. Estructuras de control en Scala
Las estructuras de control permiten controlar el flujo de ejecución de las sentencias de un
programa, método, bloque, etc. Siguiendo el flujo natural, las sentencias que componen un pro-
grama se ejecutan secuencialmente una tras de otra según estén definidas13
(primero se ejecutará
la primera sentencia, después la segunda ..., y así hasta la última sentencia). Las sentencias de
control de flujo se emplean en los programas para ejecutar sentencias condicionalmente, repetir
un conjunto de sentencias o, en general, cambiar el flujo secuencial de ejecución. Las estructuras
de control se dividen en tres grandes categorías en función del flujo de ejecución:
Estructuras secuenciales
Estructuras condicionales
Estructuras iterativas
Hasta el momento se ha visto el flujo secuencial. Cada una de la sentencias que se utilizan
en Scala están separadas por el carácter punto y coma (véase el uso del carácter punto y coma
en Scala en la Subsección 1.2.3: Uso del carácter punto y coma (;) en Scala « página 17 »). El
uso de estructuras secuenciales puede ser suficiente para la resolución de programas sencillos,
pero para la resolución de programas de tipo general se necesitará controlar las sentencias que
se ejecutan haciendo uso de estructuras condicionales e iterativas.
1.5.1. Estructuras condicionales
Se utilizarán las estructuras condicionales para determinar si un bloque debe de ser ejecu-
tado o no, en función de una condición lógica o booleana.
1.5.1.1. La sentencia if
Una sentencia if consiste en una expresión booleana seguida de un bloque de expresiones.
Si la expresión booleana se evalúa a cierta, entonces se ejecutará el bloque de expresiones. En
caso contrario no se ejecutará el bloque de expresiones. La sintaxis de una sentencia if es:
1 if (expresion_booleana) {
2 // Sentencias que se ejecutaran si la
3 // expresion booleana se evalua a true
4 }
Algoritmo 1.3: Sintaxis sentencia if
Ejemplo:
13
Cuando se escribe un programa, se introduce la secuencia de sentencias dentro de un archivo. Sin sentencias
de control del flujo, el intérprete ejecuta las sentencias conforme aparecen en el programa de principio a fin.
Página 21
38. 1 def mayorQueCinco(x: Int) = if (x > 5) { println(" El argumento
",x " es mayor que 5");}
Algoritmo 1.4: Expresión condicional if
La sentencia if / else
La sentencia if puede ir seguida de la una declaración else y un bloque de expresiones.
El bloque de expresiones que sigue a la declaración else se ejecutará en el caso de que la
expresión booleana del if sea falsa. Scala tiene la expresión condicional if-else para expresar la
elección entre dos alternativas (parecida a if-else de Java pero usada para expresiones, no para
instrucciones).
La sintaxis de una sentencia if / else es:
1 if (expresion_booleana) {
2 // Sentencias que se ejecutaran si la
3 // expresion booleana se evalua a true
4 } else {
5 // Sentencias que se ejecutaran si la
6 // expresion booleana se evalua a false
7 }
Algoritmo 1.5: Sintaxis sentencia if / else
Ejemplo:
1 def abs(x: Int) = if (x > 0) x else -x
Algoritmo 1.6: Expresiones condicionales. Función valor absoluto
La sentencia if...else if ...else
La expresión if puede ir seguida por una declaración else if, lo cual nos será de gran ayuda
para testear varias opciones haciendo uso de una única sentencia if.
Cuando se haga uso de la sentencia if...else if...else habrá que tener en cuenta algunos
puntos importantes:
Una sentencia if podrá tener cero o una declaración else, la cual estará declarada siempre
al final de una sentencia if.
Una sentencia if podrá tener cero o varias declaraciones else if, y siempre deberán prece-
der a la declaración else (si hubiera).
Si la evaluación de la expresión booleana de la sentencia if es true, ninguna de las otras
expresiones booleanas de las declaraciones else if serán evaluadas, y tampoco se ejecutará
el bloque de la declaración else.
En el algoritmo Algoritmo 1.7: Sintaxis sentencia if / else if / else « página 23 » se muestra
la sintaxis de una sentencia if / else if / else.
Página 22
39. 1 if (expresion_booleana1) {
2 // Sentencias que se ejecutaran si la
3 // expresion booleana 1 se evalua a true
4 } else if (expresion_boolena2){
5 // Sentencias que se ejecutaran si la
6 // expresion booleana 2 se evalua a true
7 } else if (expresion_boolena3){
8 // Sentencias que se ejecutaran si la
9 // expresion booleana 3 se evalua a true
10 }
11 else {
12 // Sentencias que se ejecutaran si ninguna
13 // de las anteriores expresiones booleanas
14 // se evaluan a true
15 }
Algoritmo 1.7: Sintaxis sentencia if / else if / else
Estructuras condicionales anidadas
Scala permite que las sentencias if, if/else, if/else if/else se puedan anidar. Ejemplo:
1 var x = 30;
2 var y = 10;
3
4 if( x == 30 ){
5 if( y == 10 ){
6 println("X = 30 and Y = 10");
7 }
8 }
Algoritmo 1.8: Sentencias if anidadas
1.5.2. Estructuras iterativas
Hasta el momento se ha visto como el uso de la sentencia condicional permite dejar de
ejecutar algunas sentencias dispuestas en un programa, en función del resultado de la evaluación
de una expresión booleana. Pero el flujo del programa, en cualquier caso, siempre avanza hacia
adelante y nunca se vuelve a ejecutar una sentencia ejecutada anteriormente.
Las sentencias iterativas permitirán iterar un bloque de sentencias, es decir, ejecutar un
bloque de sentencias mientras la condición especificada sea cierta. A este tipo de sentencias se
les denomina bucles y al bloque de sentencias se les denominará cuerpo del bucle.
En la tabla 1.11 se puede observar los diferentes tipos de bucles que presenta Scala para dar
respuesta a las necesidades de iteración de los programadores.
Página 23
40. Bucle Descripción
while Repite una sentencia o un bloque de sentencias siempre que la condición
sea verdadera. Se evalúa la condición antes de ejecutar el cuerpo del bucle
do...while Igual que el bucle while pero la evaluación de la condición se produce
después de la ejecución del bucle.
for El cuerpo del bucle se ejecuta un número determinado de veces. Presenta
un sintaxis que abrevia el código que maneja la variable del bucle.
Tabla 1.11: Tipos de bucles en Scala
1.5.2.1. Bucles while
Un bucle while repetirá sucesivamente un bloque de sentencias mientras la condición da-
da sea cierta. Un bucle while tiene una condición de control o expresión lógica que ha de ir
encerrada entre paréntesis y es la encargada de controlar la secuencia de repetición.
El punto clave de los bucles while es el hecho de que la evaluación de la condición se realiza
antes de que se ejecute el cuerpo del bucle, por lo que si la condición se evalúa a falsa, el cuerpo
del bucle no se ejecutaría ninguna vez.
La sintaxis de un bucle while es:
1 while (condicion) {
2 // Sentencias que se ejecutaran mientras
3 // la condicion sea cierta
4 }
Algoritmo 1.9: Sintaxis bucles while
Hay que hacer notar que si la condición es cierta inicialmente, la sentencia while no termi-
nará nunca (bucle infinito) a menos que en el cuerpo de la misma se modifique de alguna forma
la condición de control del bucle.
Ejemplo:
1 def printUntil(x: Int) = {
2 //variable local
3 var s = 0;
4 while (s <= x){
5 println("Valor: " + s);
6 s += 1;
7 }
8 }
Algoritmo 1.10: Ejemplo de bucle while.
1.5.2.2. Bucles do...while
Al igual que los bucles while, los bucles do...while repetirán un bloque de sentencias hasta
que la condición de control del bucle sea falsa. Por tanto, al igual que en el bucle while, el cuerpo
del bucle se ejecuta mientras la expresión lógica sea cierta. Los bucles do...while también se
denominan post-prueba ya que, a diferencia de los bucles while, los bucles do...while evalúan
Página 24
41. la condición después de ejecutar el cuerpo del bucle, motivo por el cual este tipo de bucles
garantizan la ejecución del cuerpo del bucle al menos una vez.
La sintaxis de un bucle while es:
1 do {
2 // Sentencias que se ejecutaran mientras
3 // la condicion sea cierta
4 } while (condicion);
Algoritmo 1.11: Sintaxis bucles do... while.
Se puede observar que la expresión lógica o booleana aparece al final del bucle por lo que
el cuerpo del bucle se ejecutará al menos una vez. Si la condición se evalúa a cierta, el flujo
de control saltará hasta la declaración do y el cuerpo del bucle se ejecutará nuevamente. Este
proceso se repetirá hasta que la condición se evalúe a falsa.
Ejemplo:
1 def printUntil2(x: Int) = {
2 //variable local
3 var s = 0;
4 do {
5 println("Valor: " + s);
6 s += 1;
7 }while (s <= x)
8 }
Algoritmo 1.12: Ejemplo de bucle do... while.
1.5.2.3. Bucles for
Los bucles for son sentencias que nos permitirán escribir eficientemente bucles que ten-
gan que repetirse un número determinado de veces. En Scala podemos encontrar las siguientes
variantes de bucles for:
Bucles for con rangos
Bucles for con colecciones
Bucles for con filtros
A continuación se verán los conceptos básicos de este tipo de bucles aunque, la especificidad
de su implementación en Scala y su utilidad harán, al contrario de lo que se podría imaginar,
que este tipo de bucles sean también muy útiles en programación funcional, capítulo en el cual
se estudiarán con mayor profundidad (véase el Capítulo 3: Programación Funcional en Scala «
página 53 »).
Bucles for con rangos
La forma más fácil de definir bucles for es haciendo uso del tipo de datos Range definido
en Scala. El bucle se repetirá tantas veces como valores contenga el tipo de datos Range dado
(véase la Subsubsección 3.11.3.1: Definición de rangos en Scala. La clase Range « página 107
»).
La sintaxis más simple de bucles for con rangos es:
Página 25
42. 1 for (a <- Range) {
2 // Sentencias que se ejecutaran tantas veces
3 // como valores contenga el tipo de datos
4 // Range dado
5 }
Algoritmo 1.13: Sintaxis bucles for con rangos.
Range puede ser un rango de números, normalmente representado de la forma i to j o i until j.
Más adelante veremos este tipo de datos en mayor profundidad.
El operador flecha izquierda (<-) recibe el nombre de generador, ya que es el encargado de
generar los diferentes valores del rango.
La variable de control de bucle (a) se inicializará con el primer valor del rango e irá
tomando, en cada iteración, los diferentes valores del mismo.
Ejemplo:
1 def printUntil3(x: Int) = {
2 //variable local
3 for (a <- 0 to x) {
4 println("Valor: " + a);
5 }
6 }
Algoritmo 1.14: Ejemplo de bucle for con rangos.
Dentro de los bucles for se pueden utilizar varios rangos, separando cada uno de ellos por el
carácter punto y coma (;). En este caso el bucle iterará sobre todas las posibles combinaciones
de los mismos. Ejemplo:
scala> for (x<- 1 to 3;y<- 4 to 6) {println("Valor x: "+x+" Valor y: "+y)}
Valor x: 1 Valor y: 4
Valor x: 1 Valor y: 5
Valor x: 1 Valor y: 6
Valor x: 2 Valor y: 4
Valor x: 2 Valor y: 5
Valor x: 2 Valor y: 6
Valor x: 3 Valor y: 4
Valor x: 3 Valor y: 5
Valor x: 3 Valor y: 6
Bucles for con colecciones
Los bucles for sirven para recorrer fácilmente los elementos de una colección.
La sintaxis de los bucles for con colecciones es:
1 for (a <- Collection) {
2 // Sentencias que se ejecutaran tantas veces
3 // como valores contenga el tipo de datos
4 // Collection dado
5 }
Algoritmo 1.15: Sintaxis bucles for con colecciones
Se puede iterar sobre los elementos de las distintas estructuras de datos definidas en la
librería Scala.collection de Scala como listas, conjuntos,etc., así como sobre los tipos de datos
Página 26
43. creados por el usuario14
La variable de control de bucle (a) se inicializará con el primer valor de la colección e
irá tomando el valor de los distintos elementos que componen la colección en las sucesivas
iteraciones del bucle.
Ejemplo:
1 def forLista = {
2 val numList = List(0,1,2,3,4,5,6,7,8,9,10);
3 for( a <- numList ){
4 println( "Valor de a: " + a );
5 }
6 }
Algoritmo 1.16: Ejemplo bucle for con colecciones.
En el ejemplo del algoritmo 1.16 se puede apreciar un bucle for recorriendo una colección
de números. Se ha creado esta colección usando el tipo de datos List de Scala. Las colecciones
en Scala se estudiarán en mayor profundidad en la Sección 3.11: Colecciones en Scala « página
102 ».
Bucles for con filtros
Los bucles for en Scala permiten emplear filtros para descartar la iteración sobre algunos
elementos de la colección que se desea iterar (rangos, listas, conjuntos,...) que no cumplan con
alguna propiedad. Para filtrar elementos se empleará una o más sentencias if.
La sintaxis de los bucles for con filtros es:
1 for (a <- Collection|Range...
2 if condicion1; if condicion2...) {
3 // Sentencias que se ejecutaran tantas veces
4 // como valores del tipo de datos
5 // Collection|Range dado satisfagan los filtros
6 }
Algoritmo 1.17: Sintaxis bucles for con filtros.
La variable de control de bucle (a) se inicializará con el primer valor de la colección/rango
que satisfaga las condiciones: condicion1 y condicion2, e irá tomando el valor de los distintos
elementos que componen la colección y que también satisfagan las condiciones impuestas como
filtros.
1 def forListaconFiltros = {
2 val numList = List(0,1,2,3,4,5,6,7,8,9,10);
3 for( a <- numList
4 if a <= 5;
5 if a != 3 ){
6 println( "Valor de a: " + a );
7 }
8 }
Algoritmo 1.18: Ejemplo bucle for con filtros
14
Estos tipos de datos creados por el usuario deberán presentar unas características especiales que serán estudia-
das con mayor detalle.
Página 27
44. En el ejemplo del algoritmo 1.18 se recorren los elementos de la lista numList que sean
menores o iguales a 5 y distintos de 3.
Bucles for con yield.
Los bucles for en Scala permiten almacenar los resultados de un bucle for en una variable o
que éstos sean el valor devuelto por una función. Para hacer esto se añadirá la palabra reservada
yield al final del cuerpo del bucle.
La sintaxis general de los bucles for con yield es:
1 for (a <- Collection|Range...
2 if condicion1; if condicion2...) {
3 // Sentencias que se ejecutaran tantas veces
4 // como valores del tipo de datos
5 // Collection dado satisfagan los filtros
6 }yield a
Algoritmo 1.19: Sintaxis bucles for con yield.
Yield generará un valor en cada iteración del bucle que será recordado por el bucle for15
.
Cuando el bucle finalice, devolverá una colección del mismo tipo de datos que la colección que
estamos iterando con los valores recordados. En los bucles for con yield se podrá también hacer
uso de filtros, haciendo de estos una herramienta mucho más potente.
En el algoritmo 1.20 podemos ver un ejemplo del uso de bucles for con yield y filtros.
1 def forListaconYield = {
2 val numList = List(0,1,2,3,4,5,6,7,8,9,10);
3 val lista= for( a <- numList
4 if a <= 5;
5 if a != 3 )yield a * 2
6 for (b<-lista){println ("Valor recordado por yield: "+b)}
7 }
Algoritmo 1.20: Ejemplo bucle for con yield
En el algoritmo 1.20 se utilizan dos bucles for. El primero de ellos generará una lista con el
doble de los números de la lista numList que satisfagan los filtros del bucle. El segundo bucle
for iterará sobre la lista resultante del primer bucle, imprimiendo los valores por pantalla.
1.6. Interacción con Java
Una de las características más importantes de Scala es que hace muy fácil la interacción
con el código escrito en Java. Todas las clases de la librería java.lang son importadas por de-
fecto, mientras que las otras necesitan ser importadas explícitamente. Scala hace muy fácil la
interactuación con código Java.
Como se verá a lo largo de los próximos capítulos, Scala es un lenguaje de programación
que ha sabido integrar algunas de las principales características presentes en los lenguajes de
programación más populares. Java no es la excepción, y comparte muchas cosas con éste. La
diferencia que se puede ver es que para cada uno de los conceptos de Java, Scala los aumenta,
15
Como si el bucle for tuviera un buffer que no se puede ver y al que en cada iteración se le añade un elemento
Página 28
45. 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.
También es posible heredar de clases Java e implementar interfaces Java directamente en
Scala.
A continuación se presenta un ejemplo que demuestra esto. Se quiere 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 utilidades interesantes, como Date y Da-
teFormat. Ya que Scala interacciona fácilmente con Java, no es necesario implementar estas
clases equivalentes en las librerías de Scala, se pueden simplemente importar las clases de los
correspondientes paquetes de Java:
1 import java.util.{Date, Locale}
2 import java.text.DateFormat._
3 object FrenchDate {
4 def main(args: Array[String]) {
5 val ahora = new Date
6 val df = getDateInstance(LONG, Locale.FRANCE)
7 println(df format ahora)
8 }
9 }
Algoritmo 1.21: Fecha actual formateada.
Las declaraciones de importación de Scala parecen muy similares a las de Java, sin embargo,
las primeras son bastante más potentes. Se pueden importar múltiples clases desde el mismo
paquete al encerrarlas entre llaves, como se muestra en la primer linea. Otra diferencia es que
se pueden importar todos los nombres de un paquete o clase, utilizando el carácter guión bajo
(_) en lugar del asterisco (*). Eso es porque el asterisco es un identificador válido en Scala (por
ejemplo se puede nombrar a un método).
Por tanto, la declaración import en la segunda línea, importa todos los miembros de la clase
DateFormat. Esto hace que el método estático getDateInstance y el campo estático LONG sean
directamente visibles.
Dentro del método main, primero se crea una instancia de la clase Date que por defecto con-
tiene la fecha actual. A continuación se define un formateador de fechas, utilizando el método
estático getDateInstance que ha sido importado previamente. Finalmente, se imprime la fecha
actual formateada de acuerdo a la instancia de DateFormat que fue “localizada”. Esta última
línea muestra un ejemplo de un método que toma un solo argumento y que ha sido utilizado
como operador infijo (véase la Subsubsección 1.2.2.2: Operadores « página 10 »). Es decir, la
expresión:
df format ahora
es solamente otra manera más corta de escribir la expresión:
df.format(ahora)
1.6.1. Ejecución sobre la JVM
Una de las características más relevantes de Java no es el lenguaje, sino su máquina virtual
(JVM). Una pulida maquinaria que el equipo de HotSpot ha ido mejorando a lo largo de los años.
Puesto que Scala es un lenguaje basado en la JVM, se integra a la perfección dentro de Java y
Página 29
46. su ecosistema (herramientas, librerías, IDE,...), por lo que no será necesario desprenderse de
todas las inversiones hechas en el pasado.
El compilador de Scala genera bytecode, siendo indistinguible a este nivel el código escrito
en Java y el escrito en Scala. Adicionalmente, puesto que se ejecuta sobre la JVM, se beneficia
del rendimiento y estabilidad de dicha plataforma. Y siendo un lenguaje de tipado estático, los
programas construidos con Scala se ejecutan tan rápido como los programas Java.
El hecho de ser un lenguaje basado en la JVM también es la consecuencia por la que al-
gunos tipos de la JVM (como vectores) acaban siendo poco elegantes en Scala o que ciertas
características, como la recursividad de cola, no están implementadas en la JVM y hay que
simularlas.
1.7. Ejercicios
Ejercicio 1. Supongamos el siguiente código en Scala:
1 val x = 10
2 val y = 20
3 def g(y: Int): Int = {
4 x+y
5 }
6 def prueba(z: Int): Int = {
7 val x = 0
8 g(x+y+z)
9 }
10 prueba(3)
¿Qué devuelve prueba(3)? ¿En qué ámbito se evalúa la expresión x+y+z ? ¿Y la expresión
x+y ? ¿Qué valores tienen esas variables en el momento de la evaluación?
Página 30
47. Capítulo 2
Programación Orientada a Objetos en
Scala
2.1. Introducción a la programación orientada a objetos en
Scala
2.1.1. Características principales de la programación orientada a objetos
La popularidad de lenguajes como Java, C# o Ruby han hecho que la POO sea un paradigma
ampliamente aceptado entre la mayoría de desarrolladores. Aunque existen numerosos lengua-
jes orientados a objetos en el ecosistema actual, únicamente podríamos encajar unos pocos si
nos ceñimos a una definición estricta de orientación a objetos. Un lenguaje orientado a objetos
“puro” debería presentar las siguientes características:
Encapsulamiento/ocultación de información.
Herencia.
Polimorfismo/Enlace dinámico.
Todos los tipos predefinidos son objetos.
Todas las operaciones son llevadas a cabo mediante el envío de mensajes a objetos.
Todos los tipos definidos por el usuario son objetos.
2.1.2. Scala como lenguaje orientado a objetos
Scala da soporte a todas las características anteriores mediante la utilización de un modelo
puro de orientación a objetos muy similar al presentado por Smalltalk (lenguaje creado por Alan
Kay sobre el año 1980)1
.
De manera adicional a todas las características puras de un lenguaje orientado a objetos
presentadas anteriormente, Scala añade algunas innovaciones en el espacio de los lenguajes
orientados a objetos:
1
http://en.wikipedia.org/wiki/Smalltalk
Página 31
48. Composición modular de los elementos mezclados (mixin). Mecanismo que permite
la composición de clases para el diseño de componentes reutilizables, evitando los pro-
blemas presentados por la herencia múltiple. Similar a los interfaces Java y las clases
abstractas. Por una parte se pueden definir múltiples “contratos”(del mismo modo que los
interfaces). Por otro lado, se podrían tener implementaciones concretas de los métodos.
Self-type. Los rasgos mezclados no dependen de ningún método y/o atributo de aque-
llas clases con las que se está entremezclando, aunque en determinadas ocasiones será
necesario hacer uso de las mismas. Esta capacidad es conocida en Scala como self-type.
Abstracción de tipos. Existen dos mecanismos principales de abstracción en los lengua-
jes de programación: la parametrización y los miembros abstractos. Scala soporta ambos
estilos de abstracción de manera uniforme para tipos y valores.
2.2. Paquetes, clases, objetos y namespaces
2.2.1. Objetos Singleton
Scala no soporta la definición de atributos estáticos en las clases, incorporando en su lugar
el concepto de objeto singleton. La definición de objetos de este tipo es muy similar a la de
las clases, salvo que se utiliza la palabra reservada object en lugar de class. Cuando un objeto
singleton comparte el mismo nombre de una clase, el primero de ellos es conocido como com-
panion object (objeto acompañante), mientras que la clase se denomina companion class (clase
acompañante) del objeto singleton. Inicialmente, sobre todo aquellos desarrolladores provenien-
tes del mundo Java, podrían ver este tipo de objetos como un contenedor en el que se podrían
definir tantos métodos estáticos como quisiéramos.
Una de las principales diferencias entre los objetos singleton y las clases es que los pri-
meros no aceptan parámetros (no podemos instanciar un objeto singleton mediante la palabra
reservada new), mientras que las clases sí lo permiten. Cada uno de los objetos singleton es
implementado mediante una instancia de una clase synthetic referenciada desde una variable
estática, por lo que presentan la misma semántica de inicialización que los estáticos de Java. Un
objeto singleton es inicializado la primera vez que es accedido por algún código.
2.2.2. Módulos, objetos, paquetes y namespaces
Si se quiere hacer referencia a un método declarado dentro de un objeto (object) se tendrá
que hacer una llamada del tipo nombreObjeto.nombreMétodo(parámetros) ya que el método
nombreMétodo habrá sido definido dentro del objeto nombreObjeto.
En realidad, en Scala todos los valores serán objetos. El principal objetivo de un objeto es
el de otorgar un namespace a sus miembros, algunas veces llamado módulo.
Un objeto puede constar de cero o más miembros. Un miembro puede ser un método decla-
rado con la palabra reservada def, o puede ser otro objeto declarado con las palabras reservadas
val o object2
Para hacer referencia a los miembros de un objeto en Scala se utilizará la notación típica de
la POO (se indicará el namespace del objeto seguido de un puto y del nombre del miembro).
Ejemplo: MyModule.abs(42)
2
Los objetos también pueden contener otros tipos de objetos, que no se mencionarán en este capítulo.
Página 32
49. Si se observa la expresión 3 * 2, lo que se está haciendo realmente es llamar al miembro
(método) * del objeto 3, es decir (3.*(2)). En general, los métodos que toman un solo argumento
pueden ser usados con una sintaxis de infijo3
. De este modo, se puede comprobar como la
llamada a MyModule abs 42 devolvería la misma salida que la llamada MyModule.abs(42).
Es posible importar miembros de un namespace haciendo uso de la palabra reservada import.
Ejemplo: import MyModule.abs
Para importar todos los miembros de un namespace se usará el guión bajo. Ejemplo: import
MyModule._4
En Scala, al igual que en Java, se puede utilizar la palabra reservada package, la cual creará
un paquete (en realidad se crea un namespace). Por tanto, se puede crear un namespace tanto
con object como con package pero si se utiliza package no se creará un objeto, por lo que,
obviamente, no se podrá pasar como tal en otras llamadas. Además en un package no podrá
haber definiciones de miembros usando las palabras reservadas val o def.
2.2.3. Clases
Del mismo modo que en todos los lenguajes orientados a objetos, Scala permite la defini-
ción de clases en las que se pueden añadir métodos y atributos. A continuación se muestra la
definición de la clase MiPrimeraClase.
1 class MiPrimeraClase{
2 val a = 1
3 }
Algoritmo 2.1: Mi primera clase
Si se quiere instanciar un objeto de la clase anterior habrá que hacer uso de la palabra
reservada new.
1 val v = new MiPrimeraClase
Cuando se defina una variable en Scala se tendrá que especificar la mutabilidad de la misma,
pudiendo escoger entre:
Utilizar la palabra reservada val para indicar que es inmutable. Una variable de este tipo
es similar al uso de final en Java. Una vez inicializada, no se podrá reasignar jamás.
De manera contraria, se podrá indicar que una variable es de tipo var, consiguiendo con
esto que su valor pueda ser modificado durante todo su ciclo de vida.
Uno de los principales mecanismos utilizados para garantizar la robustez de un objeto es
la afirmación de que su conjunto de atributos (variables de instancia) permanece constante a
lo largo de todo el ciclo de vida del mismo. El primer paso para evitar que agentes externos
tengan acceso a los campos de una clase es declarar los mismos como private. Puesto que
los campos privados sólo podrán ser accedidos desde métodos que se encuentran definidos en
la misma clase, todo el código que podría modificar el estado del mismo estará localizado en
dicha clase.5
3
En estos casos, en Scala podremos omitir el uso del punto y los paréntesis. Para más información, véase la
Subsubsección 1.2.2.2: Operadores « página 10 »
4
Véase el algoritmo 1.21 (página 29) donde se podrán ver otros ejemplos de las declaraciones de importación
en Scala
5
Por defecto, si no se especifica en el momento de la definición, los atributos y/o métodos de una clase tienen
acceso público. Es decir, public es el cualificador por defecto en Scala.
Página 33