SlideShare une entreprise Scribd logo
1  sur  41
Télécharger pour lire hors ligne
Scala: Un lenguaje escalable para la JVM
               Miguel Pastor
Scala: Un lenguaje escalable para la JVM
Miguel Pastor




                                           2
Tabla de contenidos

1.-¿Por qué Scala?                                               1
     1.1.-¿Qué es Scala?                                         1
           1.1.1.-Orientación a objetos                          2
           1.1.2.-Lenguaje funcional                             2
           1.1.3.-Lenguaje multiparadigma                        3
           1.1.4.-Lenguaje extensible y escalable                3
           1.1.5.-Ejecución sobre la JVM                         4
     1.2.-En crisis                                              4
           1.2.1.-Ley de Moore                                   4
           1.2.2.-Programando para multinúcleo                   4
     1.3.-Objetivos                                              5
2.-Fundamentos de Scala                                          7
     2.1.-Clases y objetos                                       7
     2.2.-Reglas de inferencia de puntos y coma                  8
     2.3.-Singleton objects                                      9
     2.4.-Objetos funcionales                                    9
           2.4.1.-Números racionales                             9
           2.4.2.-Constructores                                10
           2.4.3.-Sobreescritura de métodos                    10
           2.4.4.-Precondiciones                               10
           2.4.5.-Atributos y métodos                           11
           2.4.6.-Operadores                                   12
     2.5.-Funciones y closures                                 12
           2.5.1.-Funciones first-class                        13
           2.5.2.-Closures                                     13
           2.5.3.-Tail recursion                               14
     2.6.-Currying                                             14
     2.7.-Traits                                               15
           2.7.1.-¿Cómo funcionan los traits?                  15
           2.7.2.-Ejemplo: objetos rectangulares               16
           2.7.3.-Uso de traits como stackable modifications   17
           2.7.4.-Traits: ¿si o no?                            19
     2.8.-Patrones y clases case                               20
           2.8.1.-Clases case                                  20
           2.8.2.-Patrones: estructura y tipos                 21
                 2.8.2.1.-Patrones wildcard                    21
                 2.8.2.2.-Patrones constantes                  21
                 2.8.2.3.-Patrones variables                   21


                                                                iii
2.8.2.4.-Patrones constructores                     22
               2.8.2.5.-Patrones de secuencia                      22
               2.8.2.6.-Patrones tipados                           22
     2.9.-Conclusiones                                             23
3.-Actors y concurrencia                                           24
     3.1.-Problemática                                             24
     3.2.-Modelo de actores                                        25
     3.3.-Actores en Scala                                         25
          3.3.1.-Buenas prácticas                                  26
               3.3.1.1.-Ausencia de bloqueos                       26
               3.3.1.2.-Comunicación exclusiva mediante mensajes   27
               3.3.1.3.-Mensajes inmutables                        27
               3.3.1.4.-Mensajes autocontenidos                    27
     3.4.-Un ejemplo completo                                      28
          3.4.1.-Especificación del problema                       28
          3.4.2.-Implementación del producer y coordinator         28
          3.4.3.-Interfaz iterator                                 29
4.-Conclusiones y trabajo futuro                                   31
     4.1.-Conclusiones                                             31
     4.2.-Líneas de trabajo                                        32
A.-Modelo de objetos de Scala                                      33
B.-Producers                                                       34
     B.1.-Código fuente completo                                   34
Bibliografía                                                       37




                                                                   iv
¿Por qué Scala?




                                     Capítulo 1. ¿Por qué Scala?
          Durante este capítulo se cubrirán aspectos relativos como qué es Scala, características de
          alto nivel del lenguaje o por qué deberíamos escoger Scala como nuestro siguiente lenguaje
          de programación. En la parte final del mismo analizaremos los objetivos perseguidos con el
          desarrollo de este trabajo así como la estructura del mismo.

          Debido a la creciente proliferación de una gran cantidad de diferentes lenguajes en
          las plataformas JVM, .NET, OTP entre otras muchas ha surgido el dilema entre los
          desarrolladores acerca de cuál debería ser el siguiente lenguaje de progragramación que se
          debería aprender. Entre la amplia variedad de lenguajes disponibles como Groovy, Erlang,
          Ruby o F# ¿por qué deberíamos aprender Scala ?.

          Durante este capítulo analizaremos las características de alto nivel del lenguaje
          estableciendo una comparación con aquellos lenguajes con los que estemos más
          familiarizados. Los programadores provenientes de la orientación a objetos así como
          aquellos cuyo origen es la programación funcional rápidamente se sentirán cómodos con
          Scala dado que este lenguaje soporta ambos paradigmas. Scala es uno de esos extraños
          lenguajes en los que se integran de manera satisfactoria las características de los lenguajes
          orientados a objetos y los funcionales.




1.1. ¿Qué es Scala?
          Scala es un lenguaje de propósito general diseñado para expresar los patrones de
          programación más comunes de una manera sencilla, elegante y segura. Integra de manera
          sencilla características de orientación a objetos y lenguajes funcionales, permitiendo de este
          modo que los desarrolladores puedan ser más productivos. Su creador, Martin Odersky, y su
          equipo comenzaron el desarrollo de este nuevo lenguaje en el año 2001, en el laboratorio
          de métodos de programación en EPFL1

          Scala hizo su aparación pública sobre la plataforma JVM (Java Virtual Machine) en enero de
          2004 y unos meses después haria 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 la cual se acrecenta día tras día.



1
École Polytechnique Fédérale de Lausanne



                                                                                                        1
¿Por qué Scala?




          1.1.1. Orientación a objetos

          La popularidad de lenguajes como Java, C# o Ruby han hecho que la programación
          orientada a objetos sea un paradigma ampliamente aceptado entre la mayoría de
          desarrolladores. Aunque existen numerosos lenguajes 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 en envío de mensajes a objetos.
          • Todos los tipos definidos por el usuario son 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).2

          De manera adicional a todas las caracteríscticas puras de un lenguaje orientado a objetos
          presentadas anteriormente, Scala añade algunas innovaciones en el espacio de los lenguajes
          orientados a objetos:

          • Composición modular de mixin. Mecanismo que permite la composición de clases para el
            diseño de componentes reutilizables evitando los problemas 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 mixin no dependen de ningún método y/o atributo de aquellas 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-type3
          • Abstracción de tipos. Existen dos mecanismos principales de abstracción en los lenguajes
            de programación: la parametrización y los miembros abstractos. Scala soporta ambos
            estilos de abstracción de manera uniforme para tipos y valores.



          1.1.2. Lenguaje funcional

          La programación funcional es un paradigma en el que se trata la computación como la
          evaluación de funciones matemáticas y se evitan los programas con estado y datos que
          puedan ser modificados. Se ofrece adopta una visión más matemática del mundo en el que


2
http://en.wikipedia.org/wiki/Smalltalk



                                                                                                      2
¿Por qué Scala?




          los programas están compuestos por numerosas funciones que esperan una determinada
          entrada y producen una determinada salida y, en muchas ocasiones, otras funciones.

          Otro de los aspectos de la programación funcional es la ausencia de efectos colaterales
          gracias a los cuales los programas desarrollados son mucho más sencillos de comprender y
          probar. Adicionalmente, se facilita la programación concurrente, evitando que se convierta
          en un problema gracias a la ausencia de cambio.

          Los lenguajes de programación que soportan este estilo de programación deberían ofrecer
          algunas de las siguientes características:

          • Funciones de primer nivel.
          • Closures
          • Asignación simple.
          • Evaluación tardía
          • Inferencia de tipos
          • Optimización del tail call
          • Efectos monadic

          Es importante tener claro que Scala no es un lenguaje funcional puro dado que en este
          tipo de lenguajes no se permiten las modificaciones y las variables se utilizan de manera
          matemática.4. Scala da soporte tanto a variables inmutables (tambien conocidas como
          values) como a variables que apuntan estados no permanentes



          1.1.3. Lenguaje multiparadigma

          Scala ha sido el primero en incorporar y unificar la programación funcional y la orientación
          a objetos en un lenguaje estáticamente tipado. La pregunta es por qué necesitamas 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.4. Lenguaje extensible y 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 la exigencia 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 abastracción de

4
Un ejemplo de lenguaje funcional puro sería Haskell



                                                                                                       3
¿Por qué Scala?




          componentes, su sintaxis reducida, el soporte para la orientación a objetos y funcional han
          contribuido a que el lenguaje sea más escalable.



          1.1.5. Ejecución sobre la JVM

          La características más relevante 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 con Java y su
          ecosistema (herramientas, IDEs, librerías, . . .) 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.




1.2. En crisis
          A pesar de las altas prestaciones que los procesadores están adquiriendo, los desarrolladores
          software encuentran los mecanismos para agotarla. El motivo es que, gracias al software,
          se están resolviendo problemas muy complejos, y esta tendencia continuará creciendo, al
          menos en el futuro cercano.

          La pregunta clave es si los fabricantes de procesadores serán capaces de sostener la demanda
          de potencia y velocidad exigida por los desarrolladores.



          1.2.1. Ley de Moore

          Si nos hacemos eco de la ley postulada por Moore por el año 19655, el número de transistores
          por circuito integrado se duplica cada dos años aproximadamente. Sin embargo, muchos
          fabricantes están tocando techo con esta ley6 y están apostando por los procesadores
          multinúcleo. Las buenas noticias es que la potencia de los procesadores seguirá creciendo
          de manera notable aunque las malas noticias es que los programas actuales y entornos de
          desarrollo necesitarán cambiar para hacer uso de las ventajas ofrecidas por una CPU con
          varios núcleos.



          1.2.2. Programando para multinúcleo

          ¿Cómo se puede beneficiar el software de la nueva relución iniciada por los procesadores
          multimedia?
5
Concretamente la fecha data del 19 de Abril de 1965
6
http://www.gotw.ca/publications/concurrency-ddj.htm



                                                                                                         4
¿Por qué Scala?




      Concurrencia. La concuncurrencia será, si no lo es ya, el modo en el que podremos
      escribir soluciones software que nos permitan resolver problemas complejos, distrubidos y
      empresariales, beneficiándonos de la productividad ofrecida por múltiples núcleos. Al fin y
      al cabo, ¿quién no desea software eficiente?.

      En el modelo tradicional de concurrencia basado en hilos los programas son "troceados" en
      múltiples unidades de ejecución concurrentes (threads) en el que cada de ellos opera en
      un segmento de memoria compartida. En numerosas ocasiones el modelo anterior ocasiona
      "condiciones de carrera" complicadas de detectar así como siuaciones de "deadlocks" que
      ocasionan inversiones de semanas completas intentando reproducir, aislar y subsanar el
      error. El origen de todas estos problemas no radica en el modelo de hilos sino que reside
      en segmentos de memoria compartida. La programación concurrente se ha convertido en
      un modelo demasiado complicado para los desarrolladores por lo que necesitaríamos un
      mejor modelo de programación concurrente que nos permita crear y mantener programas
      concurrentes de manera sencilla.

      Scala adopta un enfoque completamente diferente a la problemática de la concurrencia: el
      modelo basado en actores. Un actor es un modelo matemático de computación concurrente
      en el que se encapsulan datos, código y su propio hilo de control, comunicándose de manera
      asíncrona mediante técnicas de paso de mensajes inmutables. La arquitectura base de este
      modelo está basada en políticas de compartición cero y componentes ligeros.

      Haciendo un poco de historia, el modelo de actores fue propuesto por primera vez por Carl
      Hewitt en el año 1973 en el famoso artículo "A Universal Modular ACTOR Formalism for
      Artificial Intelligence " , para posteriormente ser mejorado por Gul Agha con su “ACTORS: A
      Model of Concurrent Computation in Distributed Systems”).

      El primer lenguaje que llevó a cabo la implementación de este modelo fue Erlang. Tras el éxito
      obtenido por el lenguaje en lugares tan populares como Ericsson (su lugar de nacimiento),
      Yahoo o Facebook, el modelo de actores se ha convertido en una alternativa viable para
      solucionar la problemática derivada de la concurrencia, por lo que Scala ha decidido adoptar
      este mismo enfoque.




1.3. Objetivos
      A lo largo de las secciones anteriores hemos descrito de manera superficial algunas de las
      características más relevantes del lenguaje Scala así como las motivaciones principales del
      mismo. El resto del trabajo estará dividido en las siguientes secciones:

      • Durante la primera sección, esta que nos ocupa, analizamos las características generales
        del lenguaje y los objetivos del presente documento.
      • La segunda sección abarcará los principales fundamentos del lenguaje, describiendo tanto
        los mecanismos funcionales como la orientación a objetos, ambos disponibles de manera
        nativa en el lenguaje.
      • Analizaremos, aunque no de manera excesivamente exhaustiva, el modelo de
        programación concurrente propuesto por Scala (basado en una librería de actores).
        Construiremos una pequeña serie de ejemplos que nos permita poner en marcha los


                                                                                                    5
¿Por qué Scala?




  conocimientos adquiridos, tanto aquellos relativos a la programación concurrente como
  los analizados en la primera parte del trabajo.
• Debido a limitaciones de tiempo y espacio no podremos abordar muchos temas
  interesantes realacionados con el lenguaje Scala. por lo que durante la última sección
  de este trabajo se propondrán numerosos temas de ampliación: web funcional, otras
  aproximaciones de actores en Scala, arquitectura del compilador Scala, etc




                                                                                         6
Fundamentos de Scala




          Capítulo 2. Fundamentos de Scala
      A lo largo de este capítulo ahondaremos en los aspectos fundalmentales del lenguaje,
      describiendo las características más relevantes tanto de la orientación a objectos como la
      funcional. No olvidemos que Scala, tal y como hemos descrito durante el capítulo anterior,
      permite la confluencia del paradigma funcional y la orientación a objetos.

      Aquellos lectores familiarizados con el lenguaje de programación Java encontrará muchos de
      los conceptos aquí descritos (sobre todo aquellos conceptos relativos al paradigma funcional)
      similares aunque no son exactamente idénticos.




2.1. Clases y objetos
      Del mismo modo que en todos los lenguajes orientados a objetos Scala permite la definición
      de clases en las que podremos añadir métodos y atributos:

      class MyFirstClass{
        val a = 1
      }

      Si deseamos instanciar un objeto de la clase anterior tendremos que hacer uso de la palabra
      reservada new

      val v = new MyFirstClass

      En Scala existen dos tipos de variables, vals y vars, que deberemos especificar a la hora de
      definir las mismas:

      • Se 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, podremos indicar que una variable es de clase var, consiguiendo con
        esto que su valor pueda ser modificado durante todo su ciclo de vida.

      Uno de los principales mecanismos utilizados que garantizan la robustez de un objeto es la
      afirmación 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



                                                                                                  7
Fundamentos de Scala




            en la misma clase, todo el código podría modificar el estado del mismo estará localizado en
            dicha clase.1

            El siguiente paso será incorporar funcionalidad a nuestras clases; para ello podremos definir
            métodos mediante el uso de la palabra reservada def:

            class MyFirstClass{
              var a = 1
              def add(b:Byte):Unit={
                a += b
              }
            }

            Una característica importante de los métodos en Scala es que todos los parámetros son
            inmutables, es decir, vals. Por tanto, si intentamos modificar el valor de un parámetro en el
            cuerpo de un método obtendremos un error del compilación:

            def addNotCompile(b:Byte) : Unit = {
              b = 1 // Esto no compilará puesto que el
                 // parámetro b es de tipo val
              a += b
            }

            Otro aspecto relevante que podemos apreciar en el código anterior es que no es necesario
            el uso explícito de la palabra return, Scala retornará el valor de la última expresión que
            aparece en el cuerpo del método. Adicionalmente, si el cuerpo de la función retorna una
            única expresión podemos obviar la utilización de las llaves.

            Habitualmente los métodos que presentan un tipo de retorno Unit tienen efectos colaterales,
            es decir, modifican el estado del objeto sobre el que actúan. Otra forma diferente de llevar a
            cabo la definición de este tipo de métodos consiste en eliminar el tipo de retorno y el símbolo
            igual y englobar el cuerpo de la función entre llaves, tal y como se indica a continuación:

                class MyFirstClass {
               private var sum = 0
               def add(b:Byte) { sum += b }
             }




2.2. Reglas de inferencia de puntos y coma
            La utilización de los puntos y coma como indicadores de terminación de sentencia es,
            habitualmente, opcional aunque en determinadas ocasiones la ausencia de los mismos
            puede llevarnos a resultados no esperados. Por noram general los saltos de línea son tratados
            como puntos y coma salvo que algunas de las siguientes condiciones sea cierta:

1
 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



                                                                                                                              8
Fundamentos de Scala




      • 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.




2.3. Singleton objects
      Scala no soporta la definición de atributos estáticos en las clases, incorporando en su lugar
      el concepto de singleton objects. 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 companion object mientras que la clase se denomina companion class del
      objeto singleton. Inicialmente, sobre todo aquellos desarrolladores provenientes 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 singleton objects y las clases es que los primeros
      no aceptan parámetros (no podemos instanciar un objeto singleton mediante la palabra
      reservada new) mientras que las segundos si lo permiten. Cada uno de los singleton objects
      es implementado mediante una instancia de una synthetic class 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.4. Objetos funcionales
      A lo largo de las secciones anteriores hemos adquirido una serie de conocimientos básicos
      relativos a la orientación a objetos ofrecida por Scala. Durante las siguientes páginas
      analizaremos cómo se pueden construir objetos funcionales, es decir, inmutables, mediante
      la definición de clases. El desarrollo de esta sección nos permitirá ahondar en cómo los
      aspectos funcionales y los de orientación a objetos confluyen en el lenguaje. Adicionalmente
      las siguientes secciones no servirán como base para la introducción de nuveos conceptos de
      orientación a objetos cómo parámetros de clase, sobreescritura, self references o métodos
      entre otros muchos.



      2.4.1. Números racionales

      Los números racionales son aquellos que pueden ser expresados como un cociente n/
      d. Durante las siguientes secciones construiremos una clase que nos permita modelar el
      comportamiento de este tipo de números. A continuación se presentan algunas de sus
      características principales:


                                                                                                    9
Fundamentos de Scala




• Suma/resta de números racionales. Se debe obtener un común denominador de ambos
  denominadores y posteriormente sumar/restar los numeradores.
• Multiplicación de números racionales. Se multiplican los numeradores y denominadores
  de los integrantes de la operación.
• División de números racionales. Se intercambian el numerador y denominador del
  operando que aparece a la derecha y posteriormente se realiza una operación de
  multiplicación.



2.4.2. Constructores

Puesto que hemos decidido que nuestros números racionales sean inmutables
necesitaremos que los clientes de esta clase proporcionen toda la información en el
momento de creación de un objeto. Podríamos comenzar nuestro diseño del siguiente modo:

class Rational (n:Int,d:Int)

Los parámetros definidos tras el nombre de la clase son conocidos como parámetros de
clase. El compilador generará un constructor primario en cuya signatura aparecerán los dos
parámetros escritos en la definición de la clase. Cualquier código que escribamos dentro del
cuerpo de la clase que no forme parte de un atributo o de un método será incluido en el
constructor primario indicado anteriormente.



2.4.3. Sobreescritura de métodos

Si deseamos sobreescribir un método heredado de una clase padre en la jerarquía tendremos
que hacer uso de la palabra reservada override. Por ejemplo, si en la clase Rational deseamos
sobreescribir la implementación por defecto de toString podríamos actuar del siguiente
modo:

override def toString = n + "/" + d



2.4.4. Precondiciones

Una de las características de los números racionales no admiten el valor cero como
denominador aunque sin embargo, con la definición actual de nuestra clase Rational
podríamos escribir código como:

new Rational(11,0)

algo que violaría nuestra definición actual de números racionales. Dado que estamos
construyendo una clase inmutable y toda la información debe estar disponible en el
momento que se invoca al constructor este último deberá asegurarse de que el denominador
indicado no toma el valor cero (0).

La mejor aproximación para resolver este problema pasa por hacer uso de las precondiciones.
Este concepto, incluido en el lenguaje, representa un conjunto de restricciones que pueden


                                                                                           10
Fundamentos de Scala




establecerse sobre los valores pasados a métodos o constructores y que deben ser
satisfechas por el cliente que realiza la llamada del método/constructor:

class Rational(n: Int, d: Int) {
  require(d != 0)
  override def toString = n +"/"+ d
}

La restricciones se establecen mediante el uso del método require el cual espera un
argumento booleano. En caso de que la condición exigida no se cumpla el método require
disparará una excepción de tipo IllegalArgumentException.



2.4.5. Atributos y métodos

Definamos en nuestra clase un método público que reciba un número racional como
parámetro y retorne como resultado la suma de ambos operandos. Puesto que estamos
construyendo una clase inmutable el nuevo método deberá retornar la suma en un nuevo
número racional:

class Rational(n: Int, d: Int) {
 require(d != 0)
 override def toString = n +"/"+ d

 // no compila: no podemos hacer that.d o that.n
 // deben definirse como atributos
 def add(that: Rational): Rational =
  new Rational(n * that.d + that.n * d, d * that.d)
}

El código anterior muestra una primera aproximación de solución aunque incorrecta dado
que se producirá un error de compilación. Aunque los parámetros de clase n y d están el
ámbito del método add solo se puede acceder a su valor en el objeto sobre el que se realiza
la llamada.

Para resolver el problema planteado en el fragmento de código anterior tendremos que
declarar d y n como atributos de la clase Rational:

class Rational(n: Int, d: Int) {
 require(d != 0)
 val numer: Int = n // declaración de atributos
 val denom: Int = d

 override def toString = numer +"/"+ denom

 def add(that: Rational): Rational =
  new Rational(numer * that.denom +
     that.numer * denom,
     denom * that.denom)
}


                                                                                           11
Fundamentos de Scala




          Nótese que en los fragmentos de código anteriores estamos manteniendo la inmutabilidad
          de nuestro diseño. En este caso, el operador de adición add retorna un nuevo objeto racional
          que representa la suma de ambos números, en lugar de realizar la suma sobre el objeto que
          realiza la llamada.

          A continuación incorporemos a nuestra clase un método privado que nos ayude a determinar
          el máximo común divisor:

          private def gcd(a:Int,b:Int):Int =
           if(b == 0)
            a
           else gcd(b,a%b)

          El listado de código anterior nos muestra como podemos incorporar un método privado a
          nuestra clase que, en este caso, nos sirve como método auxiliar para calcular el máximo
          común divisor de dos números enteros.



          2.4.6. Operadores

          La implementación actual de nuestra clase Rational es correcta aunque podríamos definirla
          de modo que su uso resultara mucho más intuitivo. Una de las posibles mejoras que
          podríamos introducir sería la inclusión de operadores:

          def + (that: Rational): Rational =
           new Rational(
            numer * that.denom + that.numer * denom,
            denom * that.denom
            )
          def * (that: Rational): Rational =
            new Rational(numer * that.numer,
                denom * that.denom)

          De este modo podríamos escribir código como el que a continuación se indica:

          var a = new Rational(2,5)
          var b = new Rational(1,5)
          var sum = a + b
          2




2.5. Funciones y closures
          Hasta el momento hemos analizado algunas de las características más relevantes del lenguaje
          Scala, poniendo de manifiesto la incorporación de fundamentos de lenguajes funcionales así
          como de lenguajes orientados a objetos.
2
También podríamos escribir a.+(b) aunque en este caso el código resultante sería mucho menos legible



                                                                                                                        12
Fundamentos de Scala




Cuando nuestros programas crecen necesitamos hacer uso de un conjunto de abstracciones
que nos permitan dividir dicho programa en piezas más pequeñas y manejables que permitan
una mejor comprensión del mismo. Scala ofrece varios mecanismos para definir funciones
que no están presentes en Java. Además de los métodos, que no son más que funciones
miembro de un objeto, podemos hacer uso de funciones anidadas en funciones, function
literals y function values. Durante las siguientes secciones de este apartado profundizaremos
en alguno de los mecanismos anteriores no analizados en produndidad anteriormente.



2.5.1. Funciones first-class

Scala incluye una de las características principales del paradigma funcional: first class
functions. No sólamente podemos definir funciones e invocarlas sino que también podemos
definirlas como literales para, posteriormente, pasarlas como valores.

Las funciones literales son compiladas en una clase que, cuando es instanciada, se convierte
en una function value. Por lo tanto, la principal diferencia entre las funciones literales y las
funciones valor es que las primeras existen en el código fuente mientras que las segundas
existen como objetos en tiempo de ejecución.

A continuación se define un pequeño ejemplo de una función literal que suma el valor 1 al
número indicado:

(x:Int) => x + 1

Las funciones valor son objetos propiamente dichos por lo que podemos almacenarlas en
variables o invocarlas mediante la notación de paréntesis habitual.



2.5.2. Closures

Las funciones literales que hemos visto hasta este momento han hecho uso, única y
exclusivamente, de los parámetros pasados a la función. Sin embargo, podríamos definir
funciones literales en las que se hace uso de variables definidas en otro punto de nuestro
programa:

(x:Int) = x * other

La variable other es conocida como una free variable puesto que la función no le da un
significado a la misma. Al contrario, la variable x es conocida como bound variable puesto
que tiene un significado en el contexto de la función. Si intentamos utilizar esta función en un
contexto en el que no está accesible una variable other obtendremos un error de compilación
indicándonos que dicha variable no está disponible.

La función valor creada en tiempo de ejecución a partir de la función literal es conocida como
closure. El nombre se deriva del acto de "cerrar" la función literal mediante la captura en el
ámbito de la función de los valores de sus free variables. Una función valor que no presenta
free variables, creada en tiempo de ejecución a partir de su función literal no es una closure
en el sentido más estricto de la definición dado que dicha función ya se encuetra "cerrada"
en el momento de su escritura.


                                                                                              13
Fundamentos de Scala




      El fragmento de código anterior hace que nos planteemos la siguiente pregunta: ¿que ocurre
      si la variable other es modificada después de que la closure haya sido creada? La respuesta es
      sencilla: en Scala la closure tiene visión sobre el cambio ocurrido. La regla anterior también
      se cumple en sentido contrario: su una closure modifica alguno de sus valores capturados
      estos últimos son visibles fuera del ámbito de la misma.



      2.5.3. Tail recursion

      A continuación presentamos una función recursiva que aproxima un valor mediante un
      conjunto de repetidas mejoras hasta que es suficientemente bueno:

      def aproximate (guess:Double):Double =
       if(isGoodEnough(guess) guess
       else aproximate(improve(guess))

      Las funciones que presentan este tipo de tipología (se llaman a si mismas en la última
      sentencia del cuerpo de la función) son llamadas funciones tail recursive. El compilador de
      Scala detecta esta situación y reemplaza la última llamada con un salto al comienzo de la
      función tras actualizar los parámetros de la función con los nuevos valores.

      El uso de tail recursion es limitado debido a que el conjunto de instrucciones ofrecido por la
      máquina virtual (JVM) dificulta de manera notable la implementación de otros tipos de tail
      recursion. Scala únicamente optimiza llamadas recursivas a una función dentro de la misma.
      Si la recursión es indirecta, como la que se muestra en el siguiente fragmento de código, no
      se puede llevar a cabo ningún tipo de optimización:

      def isEven(x:Int): Boolean =
       if(x==0) true else isOdd(x-1)
      def isOdd(x:Int): Boolean =
       if(x==0) false else isEven(x-1)




2.6. Currying
      Scala no incluye un excesivo número de instrucciones de control de manera nativa aunque
      nos permite llevar a cabo la definición de nuestras propias construcciones de manera sencilla.
      A lo largo de esta seccion analizaremos como definir nuestras propias abstracciones de
      control con un parecido muy próximo a extensiones del lenguaje.

      El primer paso consiste en comprender una de las técnicas más comunes de los lenguajes
      funcionales: currying. Una curried function es aplicada múltiples listas de argumentos en
      lugar de una sola. El siguiente fragmento de código nos muestras una función tradicional que
      recibe dos argumentos de tipo entero y retorna la suma de ambos:

      def plainSum(x:Int, y:Int) = x + y

      A continuación se muestras una curried function similar a la descrita en el fragmento de
      código anterior:


                                                                                                  14
Fundamentos de Scala




      def curriedSum(x:Int)(y:Int) = x + y

      Cuando ejecutamos la sentencia curriedSum(9)(2) estamos obteniendo dos llamadas
      tradicionales de manera consecutiva. La primera invocación recibe el parámetro x y retorna
      una función valor para la segunda función. Esta segunda función recibe el parámetro y. El
      siguiente código muestra una función first que lleva a cabo lo que haría la primera de las
      invocaciones de la función curriedSum anterior:

      def first(x: Int) = (y: Int) => x + y

      Invocando a la función anterior con el valor 1 obtendríamos una nueva función:

      def second = first(1)

      La invocación de este segunda función con el parámetro 2 retornaría el resultado.




2.7. Traits
      Los traits son la unidad básica de reutilización de código en Scala. Un trait encapsula
      definiciones de métodos y atributos que pueden ser reutilizados mediante un proceso de
      mixin llevado a cabo en conjunción con las clases. Al contrario que en el mecanismo de
      herencia, en el que únicamente se puede tener un padre, una clase puede llevar a cabo un
      proceso de mixin con un número indefinido de traits.



      2.7.1. ¿Cómo funcionan los traits?

      La definición de un trait es similar a la de una clase tradicional salvo que se utiliza la palabra
      reservada trait en lugar de class.

      trait MyFirstTrait {
        def printMessage(){
          println("This is my first trait")
        }
      }

      Una vez definido puede ser "mezclado" junto a una clase mediante el uso de las palabras
      reservadas extend o with en XXXX analizaremos las diferencias e implicaciones de cada una
      de estas alternativas):

      class MyFirstMixin extends MyFirstTrait{
        override def toString = "This is my first mixin in Scala"
      }

      Cuando utilizamos la palabra reservada extends para realizar el proceso de mixin estaremos
      heredando de manera implícita las superclases del trait. Los métodos heredados de un trait
      se utilizan del mismo modo que se utilizan los métodos heredados de una clase. De manera
      adicional, un trait también define un tipo.


                                                                                                     15
Fundamentos de Scala




En el caso de que deseemos realizar un proceso de mixin en el que una clase ya indica
un padre de manera explicita mediante el uso extends tendremos que utilizar la palabra
reservada with. Si deseamos incluir en el proceso de mixin múltiples traits no trendremos
más que incluir más cláusulas with.

Llegados a este punto podríamos pensar que los traits son como interfaces Java con métodos
concretos pero realmente pueden hacer muchas más cosas. Por ejemplo, los traits pueden
definir atributos y mantener un estado. Realmente, en un trait podemos hacer lo mismo que
en una definición de clase con una sintaxis similar aunque existen dos excepciones:

• Un trait no puede tener parámetros de clase (los parámetros pasados al constructor
  primario de la clase).
• Mientras que en las clases las llamadas a métodos de clases padre (super.xxx) son
  enlazadas de manera estática en el caso de los traits dichas llamadas son enlazadas
  dinámicamente. Si en una clase escribimos super.method() sabremos en todo momento
  que implementación del método será invocada. Sin embargo, el mismo código escrito en
  un trait provoca un desconocimiento de la implementación del método que será invocado
  en tiempo de ejecución. Dicha implementación será determinada cada una de las veces
  que un trait y una clase realizan un proceso de mixin. Este curioso comportamiento de
  super es la clave que permite a los traits trabajar como stackable modifications3 que
  veremos a continuación.



2.7.2. Ejemplo: objetos rectangulares

Las librerías gráficas habitualmente presentan numerosas clases que representan objetos
rectangulares: ventanas, selección de una región de la pantalla, imágenes, etc. Son muchos
los métodos que nos gustaría tener en el API por lo que necesitaríamos una gran cantidad de
desarrolladores que poblaran la librería con métodos para todos los objetos rectangulares.
En Scala, los desarrolladores de la librería podrían hacer uso de traits para incorporar los
métodos necesarios en aquellas clases que se deseen.

Una primera aproximación en la que no se hace uso de traits se muestra a continuación:

class Point(val x: Int, val y: Int)

class Rectangle(val topLeft: Point, val bottomRight: Point) {

 def left = topLeft.x

 def right = bottomRight.x

 def width = right - left

 // más métodos . . .
}

Incorporemos un nuevo componente gráfico:

abstract class Component {


                                                                                           16
Fundamentos de Scala




 def topLeft: Point
 def bottomRight: Point
 def left = topLeft.x
 def right = bottomRight.x
 def width = right - left
 // más métodos . . .
}

Modifiquemos ahora la aproximación anterior e incorporemos el uso de traits. Incorporemos
un trait que incorpore la funcionalidad común vista en los dos fragmentos de código anterior:

trait Rectangular {
 def topLeft: Point
 def bottomRight: Point
 def left = topLeft.x
 def right = bottomRight.x
 def width = right - left

 // más métodos
}

La clase Component podría realizar un proceso de mix con el trait anterior para incorporar
toda la funcionalidad proporcionada por este último:

abstract class Component extends Rectangular{
  // nuevos métodos para este tipo de widgets
}

Del mismo modo que la clase Component , la clase Rectangle podría realizar el proceso de
mixin con el trait Rectangular:

class Rectangle(val topLeft: Point, val bottomRight: Point)
          extends Rectangular {
  // métodos propios de la clase rectangle
}



2.7.3. Uso de traits como stackable modifications

Hasta el momento hemos visto uno de los principales usos de los traits: el enriquecimiento
de interfaces. Durante la sección que nos ocupa analizaremos otro de los usos más populares
de los traits: facilitar stackable modifications en las clases. Los traits nos permitirán modificar
los métodos de una clase y, adicionalmente, nos permitirá apilarlas entre si.

Apilemos modificaciones sobre una cola de números enteros. Dicha cola tendrá dos
operaciones: put, que añadirá números a la cola, y get que los sacará de la misma.
Generalmente las colas siguen el comportamiento "primero en entrar, primero en salir" por
lo que el método get tendría que retornar los elementos en el mismo orden en el que fueron
introducidos.



                                                                                                17
Fundamentos de Scala




Dada una clase que implementa el comportamiento descrito en el párrafo anterior
podríamos definir un trait que llevara a cabo modificaciones como:

• Multiplicar por dos todos cualquier elemento que se añada en la cola.
• Incrementar en una unidad cada uno de los elementos que se añaden en la cola.
• Filtrado de elementos negativos. Evita que cualquier número menor que cero sea añadido
  a la cola.

Los tres traits anteriores representan modificaciones dado que no definen una cola por
si mismos sino que llevan a cabo modificaciones sobre la cola subyacente con la que
realizan el proceso de mixin. Los traits también son apilables: podríamos escoger cualquier
subconjunto de los tres anteriores e incorporarlos a una clase de manera que conseguiríamos
una nueva clase con la funcionalidad deseada. El siguiente fragmento de código representa
una implementación reducida del comportamiento de una cola descrito en el inicio de esta
sección:

import scala.collection.mutable.ArrayBuffer

abstract class IntQueue {
  def get(): Int
  def put(x: Int)
}


class BasicIntQueue extends IntQueue {
  private val buf = new ArrayBuffer[Int]
  def get() = buf.remove(0)
  def put(x: Int) { buf += x }
}

Realicemos ahora un conjunto de modificaciones sobre la clase anterior; para ello, vamos a
hacer uso de los traits. El siguiente fragmento de código muestra un trait que duplica el valor
de un elemento que se desea añadir a la cola:

trait Duplication extends IntQueue{
  abstract override def put(x:Int) { super.put(2*x) }
}

Nótese el uso de las palabras reservadas abstract override. Esta combinación de
modificadores sólo puede ser utilizada en los traits y no en las clases, e indica que el trait
debe ser integrado (mixed) con una clase que presenta una implementación concreta del
método en cuestión.

A continuación se muestra un ejemplo de uso del trait anterior:

scala> class MyQueue extends BasicIntQueue with Doubling
defined class MyQueue

scala> val queue = new MyQueue
queue: MyQueue = MyQueue@91f017


                                                                                             18
Fundamentos de Scala




scala> queue.put(10)

scala> queue.get()
res12: Int = 20

Para analizar el mecanismo de apilado de modificaciones implementemos en primer lugar
los dos traits restantes que hemos descrito al inicio de esta sección:

trait Increment extends IntQueue{
 abstract override def put(x:Int) { super.put(x + 1) }
 }

trait Filter extends IntQueue{
  abstract override def put(x:Int) { if ( x >= 0 ) super.put(x) }
}

Una vez tenemos disponibles las modificaciones podríamos generar una nueva cola del modo
que más nos interese:

scala> val queue = (new BasicIntQueue
     with Increment with Filter)
queue: BasicIntQueue with Increment with Filter...

scala> queue.put(-1); queue.put(0); queue.put(1)

scala> queue.get()
res15: Int = 1

scala> queue.get()
res16: Int = 2

El orden de los mixins es importante . De manera resumida, cuando invocamos a un método
de una clase con mixins el método del trait definido más a la derecha es el primero en
ser invocado. Si dicho método invoca a super este invocará al trait que se encuentra más
a la izquierda y así sucesivamente. En el ejemplo anterior, el método put del trait Filter
será invocado en primer lugar, por lo que aquellos números menores que cero no serán
incorporados a la cola. El método put del trait Filter sumará el valor uno a cada uno de los
números (mayores o iguales que cero).



2.7.4. Traits: ¿si o no?

A continuación se presentan una serie de criterios más o menos objetivos que pretenden
ayudar al lector a determinar cuando debería usar estas construcciones proporcionadas por
el lenguaje Scala:

• Si el comportamiento no pretende ser reutilizado entonces encapsularlo en una clase.
• Si el comportamiento pretende ser reutilizado en múltiples clases no relacionadas
  entonces construir un trait.


                                                                                           19
Fundamentos de Scala




           • Si se desee que una clase Java herede de nuestra funcionalidad entonces deberemos
             utilizar una clase abstracta.
           • Si la eficiencia es importante deberíamos inclinarnos hacia el uso de las clases. La mayoría
             de los entornos de ejecución Java hacen una llamada a un método virtual de una
             clase mucho más rápido que la invocación de un método de un interfaz. Los traits son
             compilados a interfaces y podríamos penalizar el rendimiento.
           • Si tras todas las opciones anteriores no tenemos claro qué aproximación deseamos
             utilizar deberíamos comenzar por el uso de traits. Lo podremos cambiar en el futuro y,
             generalmente, mantedremos más opciones abiertas.




2.8. Patrones y clases case
           Aquellos lectores que hayan progamado en algún lenguaje perteneciente al paradigma
           funcional reconocerán el uso de la concordancia de patrones. Las clases case son un concepto
           relativamente novedoso y nos permiten incorporar el mecanismo de matching de patrones
           sobre objetos sin la necesidad de código repetitivo. De manera general, no tendremos más
           que prefijar la definición de una clase con la palabra reservada case para indicar que la clase
           definida pueda ser utilizada en la definición de patrones.

           A lo largo de esta sección analizaremos los dos conceptos anteriores en conjunción con un
           conjunto de ejemplos con el objetivo de ilustrar y amenizar la lectura de esta sección.



           2.8.1. Clases case

           El uso del modificador case provoca que el compilador de Scala incorpore una serie de
           facilidades a la clase indicada.

           En primer lugar incorpora un factory-method con el nombre de la clase. Gracias a esto
           podríamos escribir código como Foo("x") para construir un objeto Foo en lugar de new
           Foo("x"). Una de las principales ventajas de este tipo de métodos es la ausencia de
           operadores new cuando los anidamos:

           val op = BinaryOperation("+", Number(1), v)

           Otra funcionalidad sintáctica incorporada por el compilador es que todos los argumentos en
           la lista de parámetros incorporan de manera implicita el prefijo val por lo que éstos últimos
           serán atributos de clase. Por último, pero no por ello no menos importante, el compilador
           añade implementaciones "instintivas" de los métodos toString, hashCode e equals.

           Todas estas facilidades incorporadas acarrean un pequeño coste: las clases y objetos
           generados son un poco más grandes4 y tenemos que incorporar la palabra case en las
           definiciones de nuestras clases. La principal ventaja de este tipo de clases es que soportan
           la concordancia de patrones.
4
 Son más grandes porque se generan métodos adicionales y se incorporan atributos implícitos para cada uno de los parámetros
del constructor



                                                                                                                        20
Fundamentos de Scala




2.8.2. Patrones: estructura y tipos

La estructura general de un patrón en Scala presenta la siguiente estructura:

selector match { alternatives }

Incorporan un conjunto de alternativas en las que cada una de ellas comienza por la palabra
reservada case. Cada una de estas alternativas incorpora un patrón y una o más expresiones
que serán evaluadas en caso de que se produzca la concordancia del patrón. Se utiliza el
símbolo de flecha (=>) para separar el patrón de las expresiones.

Como hemos visto al comienzo de esta sección la sintaxis de los patrones es sumamente
sencilla por lo que vamos a profundizar en los diferentes tipos de patrones que podemos
construir.


2.8.2.1. Patrones wildcard

El patrón (_) concuerda con cualquier objeto por lo que podríamos utilizarlo como una
alternativa catch-all tal y como se muestra en el siguiente ejemplo:

expression match {
 case BinaryOperation(op,leftSide,rightSide) =>
   println(expression + " is a BinaryOperation")
 case _ =>
 }


2.8.2.2. Patrones constantes

Un patrón constante concuerda única y exclusivamente consigo mismo. El siguiente
fragmento de código muestra algunos ejemplos de patrones constantes:

def describe(x: Any) = x match {
  case 5 => "five"
  case true => "truth"
  case "hello" => "hi!"
  case Nil => "this is an empty list"
  case _ => "anything else"
}


2.8.2.3. Patrones variables

Un patrón variable concuerda con cualquier objeto, del mismo modo que los patrones
wildcard. A diferencia de los patrones wildcard, Scala enlaza la variable al objeto, por lo que
posteriormente podremos utilizar dicha variable para actuar sobre el objeto:

expr match {


                                                                                             21
Fundamentos de Scala




 case 0 => "zero value"
 case somethingElse => "not zero: "+ somethingElse + " value"
}


2.8.2.4. Patrones constructores

Son en este tipo de construcciones donde los patrones se convierten en una herramienta
muy poderosa. Básicamente están formados por un nombre y un número indefinido de
patrones. Asumiendo que el nombre designa una clase de tipo case este tipo de patrones
comprobarán primero si el objeto pertenece a dicha clase, para, posteriormente comprobar
si los parámetros del constructor concuerdan con el conjunto de patrones extra indicados.

La definición anterior puede no resultar demasiado explicativa por lo que a continuación se
incluye un pequeño ejemplo en el que se comprueba que el objeto de primer nivel es de tipo
BinaryOperation y que su tercer argumento es de tipo Number y su atributo de clase vale 0:

expr match {
  case BinaryOperation("+", e, Number(0))
    => println("a deep match")
  case _ =>
}


2.8.2.5. Patrones de secuencia

Podemos establecer patrones de concordancia sobre listas o arrays del mismo modo que lo
hacemos para las clases. Deberá utilizarse la misma sintáxis aunque ahora podremos indicar
cualquier número de elementos en el patrón.

El siguiente fragmento de código muestra un patrón que comprueba una lista de tres
elementos cuyo primer valor toma 0:

expr match {
  case List(0, _, _) => println("found it")
  case _ =>
}


2.8.2.6. Patrones tipados

Podemos utilizar este tipo de construcciones como reemplazo de las comprobaciones y
conversiones de tipos:

def genericSize(x: Any) = x match {
  case s: String => s.length
  case m: Map[_, _] => m.size
  case _ => -1
}


                                                                                          22
Fundamentos de Scala




     El método genericSize retorna la longitud de un objeto cualquiera. El patrón "s:String" es un
     patrón tipado: cualquier instancia no nula de tipo String concordará con dicho patrón. La
     variable de patrón s hará referencia a dicha cadena.




2.9. Conclusiones
     A lo largo de este capítulo hemos analizado varias de las características principales del
     lenguaje de programación Scala, haciendo especial hincapié en como el lenguaje incorpora
     funcionalidades provenientes de los paradigmas funcional y orientado a objetos.

     Las secciones anteriores nos permitirán comenzar a escribir nuestros primeros programas en
     Scala aunque nos faltaría un largo camino para convertirnos en unos expertos en la materia.
     Para el lector más interesado a continuación se indican algunos conceptos en los que se
     podría profundizar:

     • Interoperabilidad entre Scala y Java.
     • Parametrización de tipos.
     • Extractors.
     • Trabajar con XML.
     • Inferencia de tipos
     • ...

     En la siguiente parte del trabajo analizaremos el modelo de actores propuesto por Scala y
     cómo esta aproximación nos permitirá construir aplicaciones concurrentes de manera más
     sencilla.




                                                                                                23
Actors y concurrencia




                   Capítulo 3. Actors y concurrencia
          En muchas ocasiones cuando estamos escribiendo nuestros programas necesitamos que
          muchas de sus partes se ejecuten de manera independiente, es decir, de manera
          concurrente. Aunque el soporte introducido por Java es suficiente, a medida que la
          complejidad y tamaño de los programas se incrementan, conseguir que nuestro código
          concurrente funcione de manera correcta se convierte en una tarea complicada. Los actores
          incluyen un modelo de concurrencia más sencillo con el que podremos evitar la problemática
          habitual ocasionada por el modelo de concurrencia nativo de Java.

          Durante este capítulo presentaremos los fundamentos del modelo de actores y como
          podremos utilizar la librería de actores facilitada Scala.




3.1. Problemática
          La plataforma Java proporciona de manera nativa un modelo de hilos basado en bloqueos
          y memoria compartida. Cada uno de los objetos lleva asociado un monitor que puede
          ser utilizado para realizar el control de acceso de múltiples hilos sobre los datos. Para
          utilizar este modelo debemos decidir que datos queremos compartir entre múltiples hilos
          y establecer como synchronized aquellas secciones de código que acceden a segmentos
          de datos compartidos. En tiempo de ejecución, Java utiliza un mecanismo de bloqueo con
          el que se garantiza que en un instante de tiempo T un único hilo accede a una sección
          sincronizada S. Los desarrolladores han encontrado numerosos problemas para construir
          aplicaciones robustas con esta aproximación, especialmente cuando los problemas crecen
          en tamaño y complejidad. En cada punto del programa el desarrollador debe razonar sobre
          los datos modificados y/o accedidos que pueden ser accedidos por otros hilos y establecer
          los bloqueos necesarios.

          Añadiendo un nuevo inconveniente a los descritos en el párrafo anterior, los procesos de
          prueba no son fiables cuando tratamos con códigos multihilo. Dado que los hilos son no
          deterministas, podríamos probar nuestra aplicaciones miles de veces de manera satisfactoria
          y obtener un error la primera vez que se ejecuta el mismo código en la máquina de nuestro
          cliente.

          La aproximación seguida por Scala1 para resolver el problema de la concurrencia ofrece
          un modelo alternativo de no compartición y paso de mensajes. En la siguiente sección se
          ofrecerá una definición resumida del modelo de actores.

1
Realmente Scala ofrece el modelo de actores mediante una librería del lenguaje en lugar de manera nativa



                                                                                                                       24
Actors y concurrencia




3.2. Modelo de actores
      El modelo de actores ofrece una solución diferente al problema de la concurrencia. En lugar
      de procesos interactuando mediante memoria compartida, el modelo de actores ofrece una
      solución basada en buzones y paso de mensajes asíncronos. En estos buzones los mensajes
      pueden ser almacenados y recuperados para su procesamiento por otros actores. En lugar
      de compartir variables en memoria, el uso de estos buzones nos permite aislar cada uno de
      los procesos.

      Los actores son entidades independientes que no comparten ningún tipo de memoria para
      llevar a cabo el proceso de comunicación. De hecho, los actores únicamente se pueden
      comunicar a través de los buzones descritos en el párrafo anterior. En esta aproximación
      de concurrencia no existen bloqueos ni secciones sincronizadas por lo que los problemas
      derivados de las mismas (deadlocks, pérdida de actualizaciones de datos, . . .) no existen en
      este modelo. Los actores están pensados para trabajar de manera concurrente, no de modo
      secuencial.

      El modelo de actores no es una novedad: Erlang basa su modelo de concurrencia en actores
      en lugar de hilos. De hecho, la popularidad alcanzada por Erlang en determinados ámbitos
      empresariales han hecho que la popularidad del modelo de actores haya crecido de manera
      notable y lo ha convertido en una opción viable para otros lenguajes.




3.3. Actores en Scala
      Para implementar un actor en Scala no tenemos más que extender scala.actors.Actor e
      implementar el método act. El siguiente fragmento de código ilustra un actor sumamente
      simple que no realiza nada con su buzón:

      import scala.actors._

      object FooActor extends Actor{
        def act(){
          for(i <- 1 to 11){
            println("Executing actor!")
            Thread.sleep(1000)
          }
        }
      }

      Si deseamos ejecutar un actor no tenemos más que invocar a su método start()

      scala> FooActor.start()
      res0: scala.actors.Actor = FooActor$@681070

      Otro mecanismo diferente que nos permitiría instanciar un actor sería hacer uso del método
      de utilidad actor disponible en scala.actors.Actor:

      scala> import scala.actors.Actor._


                                                                                                   25
Actors y concurrencia




scala> val otherActor = actor {
  for (i <- 1 to 11)
   println("This is other actor.")
   Thread.sleep(1000)
}

Hasta el momento hemos visto como podemos crear un actor y ejecutarlo de manera
independiente pero, ¿cómo conseguimos que dos actores trabajen de manera conjunta? Tal
y como hemos descrito en la sección anterior, los actores se comunican mediante el paso de
mensajes. Para enviar un mensaje haremos uso del operador !.

Definamos ahora un actor que haga uso de su buzón, esperando por un mensaje e
imprimiendo aquello que ha recibido:

val echoActor = actor {
  while (true) {
    receive {
      case msg =>
       println ("Received message " + msg)
    }
  }
}

Cuando un actor envía un mensaje no se bloquea y cuando lo recibe no es interrumpido. El
mensaje enviado queda a la espera en el buzón del receptor hasta que este último ejecute
la instrucción receive. El siguiente fragmento de código ilustra el comportamiento descrito:

scala> echoActor ! "My First Message"
Received message My First Message

scala> echoActor ! 11
Received message 11



3.3.1. Buenas prácticas

Llegados a este punto conocemos los fundamentos básicos para escribir nuestros propios
actores. El punto fuerte de los métodos vistos hasta este momento es que ofrecen un
modelo de programación concurrente basado en actores por lo que, en la medida que
podamos escribir siguiendo este estilo nuestro código será más sencillo de depurar y tendrá
menos deadlocks y condiciones de carrera. Las siguientes secciones describen,de manera
breve, algunas directrices que nos permitirán adopotar un estilo de programación basado
en actores.


3.3.1.1. Ausencia de bloqueos

Un actor no debería bloquearse mientras se encuentra procesando un mensaje. El problema
radica en que mientras un actor se bloquea, otro actor podría realizar una petición sobre


                                                                                            26
Actors y concurrencia




el primero. Si el actor se bloquea en la primera petición no se dará cuenta de una segunda
solicitud. En el peor de los casos, se podría producir un deadlock en el que varios actores
están esperando por otros actores que a su vez están bloqueados.

En lugar de bloquearse, el actor debería esperar la llegada de un mensaje indicando que la
acción está lista para ser ejecutada. Esta nueva disposición, por norma general, implicará la
participación de otros actores.


3.3.1.2. Comunicación exclusiva mediante mensajes

La clave de los actores es el modelo de no compartición, ofreciendo un espacio seguro (el
método act de cada actor) en el que podríamos razonar de manera secuencial. Expresándolo
de manera diferente, los actores nos permiten escribir programas multihilo como un
conjunto independiente de programas monohilo. La simplificación anterior se cumple
siempre y cuando el único mecanismo de comunicación entre actores sea el paso de
mensajes.


3.3.1.3. Mensajes inmutables

Puesto que el modelo de actores provee un entorno monohilo dentro de cada método act no
debemos preocuparnos si los objetos que utilizamos dentro de la implementación de dicho
método son thread-safe. Este es el motivo por el que el modelo de actores es llamado shared-
nothing, los datos están confinados en un único hilo en lugar de ser compartidos por varios.

La excepción a esta regla reside en la información de los mensajes intercambiados entre
actores dado que es compartida por varios de ellos. Por tanto, tendremos que preocuparnos
de que los mensajes intercambiados entre actores sean thread-safe.


3.3.1.4. Mensajes autocontenidos

Cuando retornamos un valor al finalizar la ejecución de un método el fragmento de
código que realiza la llamada se encuentra en una posición idónea para recordar lo que
estaba haciendo anteriormente a la ejecución del método, recoger el resultado y actuar en
consecuencia.

Sin embargo, en el modelo de actores las cosas se vuelven un poco más complicadas. Cuando
un actor realiza una petición a otro actor el primero de ellos no es consciente del tiempo que
tardará la respuesta, instantes en los que dicho actor no debería bloquearse, sino que debería
continuar ejecutando otro trabajo hasta que la respuesta a su petición le sea enviada. ¿Puede
el actor recordar qué estaba haciendo en el momento en el que envió la petición inicial?

Podríamos adoptar dos soluciones para intetar resolver el problema planteado en el párrafo
anterior:




                                                                                              27
Actors y concurrencia




     • Un mecanismo para simplificar la lógica de los actores sería incluir información redundante
       en los mensajes. Si la petición es un objeto inmutable, el coste de incluir una referencia a
       la solicitud en el valor de retorno no sería costoso.
     • Otro mecanismo adicional que nos permitiría incrementar la redundancia en los mensajes
       sería la utilización de una clase diferente para cada uno de las clases de mensajes que
       dispongamos




3.4. Un ejemplo completo
     Como ejemplo completo de aplicación del modelo de actores descrito a lo largo de las
     secciones anteriores y con el objetivo de asentar y poner en marcha los conocimientos
     adquiridos vamos a construir, paso a paso, una pequeña aplicación de ejemplo.



     3.4.1. Especificación del problema

     Durante la construcción de este ejemplo desarrollaremos una abstraccción de producers la
     cual ofrecerá una interfaz de iterador estándar para la recuperación de una secuencia de
     valores.

     La definición de un producer específico se realiza mediante la implementación del método
     abstracto produceValues. Los valores individuales son generados utilizados el método
     produce. Por ejemplo, un producer que genera en preorden los valores contenidos en un
     árbol podría ser definido como:

     class TraversePreorder(n:Tree)
         extends Producer[int]{
      def produceValues = traverse(n)

      def traverse(n:Tree){
        if( n!= null){
          produce(n.elem)
          traverse(n.left)
          traverse(n.right)
        }
      }
     }



     3.4.2. Implementación del producer y coordinator

     La abstracción producer se implementa en base a dos actores: un actor productor y un
     actor coordinador. El siguiente fragmento de código ilustra cómo podríamos llevar a cabo la
     definición del actor productor:

     abstract class Producer[T] {


                                                                                                   28
Actors y concurrencia




 protected def produceValues: unit

 protected def produce(x: T) {
     coordinator ! Some(x)
     receive { case Next => }
 }

 private val producer: Actor = actor {
     receive {
       case Next =>
         produceValues
         coordinator ! None
     }
 }
 ...
}

¿Cuál es el mecanismo de funcionamiento del actor productor anterior? Cuando un actor
productor recibe el mensaje Next éste ejecuta el método (abstracto) produceValues, que
desencadena la ejecución del método produce. Ésta ultima ejecución provoca el envío de una
serie de valores (recubiertos por el message Some) al coordinador.

El coordinador es el responsable de sincronizar las peticiones de los clientes y los valores
provenientes del productor. Una posible implementación del actor coordinador se ilustra en
el siguiente fragmento de código:

private val coordinator:Actor = actor {
  loop {
    react {
      case Next =>
       producer ! Next
       reply{
         receive {case x: Option[_] => x}
       }
      case Stop => exit('stop)
    }
  }
}



3.4.3. Interfaz iterator

Nuestro objetivo es que los producers se puedan utilizar del mismo modo en que utilizamos
los iteradores tradicionales. Nótese como los métodos hasNext y next envían sendos
mensajes al actor coordinador para llevar a cabo su tarea:

def iterator = new Iterator[T] {

 private var current: Any = Undefined


                                                                                            29
Actors y concurrencia




 private def lookAhead = {
   if(current == Undefined)
    current = coordinator !? Next
   current
 }

 def hasNext: boolean = lookAhead match {
   case Some(x) => true
   case None => { coordinator ! Stop; false}
 }

 def next:T = lookAhead match{
   case Some(x) => current = Undefined; x.asInstanceOf[T]
 }
}

Centremos nuestra atención en el método lookAhead. Cuando el atributo current tenga un
valor indefinido significa que tendremos que recuperar el siguiente valor. Para llevar a cabo
dicha tarea se hace uso del operador de envío de mensajes síncronos !? lo cual implica que
se queda a la espera de la respuesta del coordinador. Los mensajes enviados mediante el
operador !? debe ser respondidos mediante el uso de reply.

En el apéndice Código fuente producers se puede ver código completo del ejemplo descrito
durante esta sección.




                                                                                             30
Conclusiones y trabajo futuro




         Capítulo 4. Conclusiones y trabajo
                                     futuro
     Como punto final de este trabajo haremos un breve resumen de los conceptos analizados
     durante todo el trabajo y plantearemos futuras líneas de trabajo que podrían resultar
     atractivas para aquellas personas interesadas en esta temática.




4.1. Conclusiones
     Durante el desarrollo de este trabajo hemos vistos algunas de las principales características
     del lenguaje Scala, haciendo espacial hincapié en cómo se integran las capacidades del
     paradigma de orientación a objetos y el funcional. Asimismo hemos analizado el modelo de
     concurrencia basado en actores y cómo Scala ofrece dicho modelo de computación mediante
     una librería que complementa al lenguaje.

     Como ya hemos indicado al inicio del trabajo Scala es el primer lenguaje de propósito general
     que integra conceptos del paradigma funcional y el orientado a objetos. Muchos de los
     lectores se estarán preguntando cuál es el ámbito de aplicación del lenguaje. La respuesta
     corta y concisa podría ser: en todos aquellos lugares donde utilizamos Java. Entrando un poco
     más en detalle en las posibles escenarios de aplicación a continuación se indican algunos de
     los posibles usos:

     • Lenguaje de parte servidora.
     • Escritura de scripts
     • Desarrollo de aplicaciones robustas, escalables y fiables.
     • Desarrollo de aplicaciones web.
     • Construcción de lenguajes de dominio específico (DSL)
     • ...




                                                                                                    31
Conclusiones y trabajo futuro




4.2. Líneas de trabajo
      Por motivos de espacio y, principalmente, de tiempo se han quedado numerosos temas en
      el tintero que podrían resultar interesantes. A continuación se listan y describen, de manera
      sumamente breve, algunas sugerencias consideradas interesantes:

      • Análisis de la plataforma Akka [http://akka.io] . Plataforma basada en Scala que ofrece
        un modelo de actores junto con Sofware Transactional Memory con el objetivo de
        proporcionar los fundamentos correctos para la construcción de aplicaciones escalables
        y concurrentes.

        Una comparación entre el modelo de actores ofrecido por Scala y el modelo de actores
        ofrecido por Akka podría resultar atractiva.
      • Web funcional. Análisis de cómo las características de los lenguajes funcionales se pueden
        utilizar para construir aplicaciones web. Análisis de frameworks web funcionales como Lift
        [http://www.liftweb.net/] o Play [http://www.playframework.org/]
      • Análisis de interoperabilidad de Scala y Java. Mecanismos y modos de interacción entre
        ambos lenguajes. Problemática habitual.
      • Scala en .NET.
      • Colecciones. Análisis del nuevo API de colecciones diseñado a partir de Scala 2.8. Análisis
        de colecciones concurrentes.
      • GUI en Scala. Análisis de cómo podemos utilizar Scala para la construcción de aplicaciones
        de escritorio.
      • Monads en Scala.
      • Arquitectura del compilador de Scala.
      • Lenguajes de dominio específicos (DSLs). Construcción de lenguajes de dominio específicos
        basados en el lenguaje Scala. Combinator parsing.




                                                                                                     32
Apéndice A. Modelo de objetos de
                               Scala
A continuación, a modo de resumen, se incluye un diagrama en el que se refleja la jerarquía
de clases presentes en Scala:




                                                                                        33
Apéndice B. Producers
     El siguiente código fuente contiene la definición del interfaz producer analizado en Ejemplo
     completo de actores




B.1. Código fuente completo
     package com.blogspot.miguelinlas3.phd.paragprog.actors

     import scala.actors.Actor
     import scala.actors.Actor._

     abstract class Producer[T] {

       /** Mensaje indicando que el siguiente valor debe de computarse. */
       private val Next = new Object

       /** Estado indefinido del iterator. */
       private val Undefined = new Object

       /** Mensaje para detener al coordinador. */
       private val Stop = new Object

       protected def produce(x: T) {
         coordinator ! Some(x)
         receive { case Next => }
       }

       protected def produceValues: Unit

       def iterator = new Iterator[T] {
         private var current: Any = Undefined
         private def lookAhead = {
           if (current == Undefined) current = coordinator !? Next
           current
         }

          def hasNext: Boolean = lookAhead match {
            case Some(x) => true



                                                                                              34
case None => { coordinator ! Stop; false }
        }

        def next: T = lookAhead match {
          case Some(x) => current = Undefined; x.asInstanceOf[T]
        }
    }

    private val coordinator: Actor = actor {
      loop {
        react {
          case Next =>
            producer ! Next
            reply {
              receive { case x: Option[_] => x }
            }
          case Stop =>
            exit('stop)
        }
      }
    }

    private val producer: Actor = actor {
      receive {
        case Next =>
          produceValues
          coordinator ! None
      }
    }
}

object producers extends Application {

    class Tree(val    left:   Tree, val elem: Int, val right: Tree)
    def node(left:    Tree,   elem: Int, right: Tree): Tree =
    new Tree(left,    elem,   right)
    def node(elem:    Int):   Tree = node(null, elem, null)

  def tree =
 node(node(node(3), 4, node(6)), 8, node(node(9), 10, node(11)))

    class PreOrder(n: Tree) extends Producer[Int] {
      def produceValues = traverse(n)
      def traverse(n: Tree) {
        if (n != null) {
          produce(n.elem)
          traverse(n.left)
          traverse(n.right)
        }



                                                                      35
}
    }

    class PostOrder(n: Tree) extends Producer[Int] {
      def produceValues = traverse(n)
      def traverse(n: Tree) {
        if (n != null) {
          traverse(n.left)
          traverse(n.right)
          produce(n.elem)
        }
      }
    }

    class InOrder(n: Tree) extends Producer[Int] {
      def produceValues = traverse(n)
      def traverse(n: Tree) {
        if (n != null) {
          traverse(n.left)
          produce(n.elem)
          traverse(n.right)
        }
      }
    }

    actor {
      print("PreOrder:")
      for (x <- new PreOrder(tree).iterator) print(" "+x)
      print("nPostOrder:")
      for (x <- new PostOrder(tree).iterator) print(" "+x)
      print("nInOrder:")
      for (x <- new InOrder(tree).iterator) print(" "+x)
      print("n")
    }
}




                                                             36
Bibliografía
Scala in Action. Nilanjan Raychaudhuri. Early Access started on March 2010.

Scala in Depth. Joshua D. Suereth. Early Access started on September 2010.

Programming in Scala, Second Edition. Martin Odersky. Lex Spoon. Bill Venners. December 13, 2010.

Programming Scala: Tackle Multi-Core Complexity on the Java Virtual Machine. Venkat Subramaniam.
       Jul 2009.

ACTORS: A Model of Concurrent Computation in Distributed Systems. Agha, Gul Abdulnabi. 1985-06-01.

Martin Odersky interview: the future of Scala. http://www.infoq.com/interviews/martin-odersky-
       scala-future.

Akka framework. http://akka.io/.

Play framework. http://www.playframework.org/.

Lift framework. http://www.liftweb.net/.

Haskell language. http://www.haskell.org/haskellwiki/Haskell.

Scalaz: pure functional data structures. http://code.google.com/p/scalaz/.




                                                                                                37

Contenu connexe

Tendances

Tendances (20)

Fundamentos de programación Java
Fundamentos de programación JavaFundamentos de programación Java
Fundamentos de programación Java
 
Lenguaje de Programación Ruby
Lenguaje de Programación RubyLenguaje de Programación Ruby
Lenguaje de Programación Ruby
 
Intro Java
Intro JavaIntro Java
Intro Java
 
Tipos de datos
Tipos de datosTipos de datos
Tipos de datos
 
cuestionario de Programación
cuestionario de Programación cuestionario de Programación
cuestionario de Programación
 
Clasesyobjetos
ClasesyobjetosClasesyobjetos
Clasesyobjetos
 
Test
TestTest
Test
 
Java vs. Ruby
Java vs. RubyJava vs. Ruby
Java vs. Ruby
 
iNTRODUCCION AL LENGUAJE JAVA
iNTRODUCCION AL LENGUAJE  JAVAiNTRODUCCION AL LENGUAJE  JAVA
iNTRODUCCION AL LENGUAJE JAVA
 
2.1 caracteristicas de lenguajes de programación
2.1 caracteristicas de lenguajes de programación2.1 caracteristicas de lenguajes de programación
2.1 caracteristicas de lenguajes de programación
 
Scala
ScalaScala
Scala
 
CUESTIONARIO JAVA
CUESTIONARIO JAVACUESTIONARIO JAVA
CUESTIONARIO JAVA
 
Programación con java en Eclipse
Programación con java en EclipseProgramación con java en Eclipse
Programación con java en Eclipse
 
Programacion orientada a objetos 2
Programacion orientada a objetos 2Programacion orientada a objetos 2
Programacion orientada a objetos 2
 
Curso Java Inicial 2 - Introducción y Sintaxis
Curso Java Inicial  2 - Introducción y SintaxisCurso Java Inicial  2 - Introducción y Sintaxis
Curso Java Inicial 2 - Introducción y Sintaxis
 
Programacion java basica
Programacion java basicaProgramacion java basica
Programacion java basica
 
Estructura basica de un programa
Estructura basica de un programaEstructura basica de un programa
Estructura basica de un programa
 
Aplicaciones java
Aplicaciones javaAplicaciones java
Aplicaciones java
 
Sintaxis Java
Sintaxis JavaSintaxis Java
Sintaxis Java
 
Java basico
Java basicoJava basico
Java basico
 

Similaire à Scala Overview

El lenguaje de programación c++, apuntesprev
El lenguaje de programación c++, apuntesprevEl lenguaje de programación c++, apuntesprev
El lenguaje de programación c++, apuntesprevjtk1
 
Introducción a Scala
Introducción a ScalaIntroducción a Scala
Introducción a Scalajose diaz
 
Curso Java Avanzado 0 Conceptos Basicos
Curso Java Avanzado   0 Conceptos BasicosCurso Java Avanzado   0 Conceptos Basicos
Curso Java Avanzado 0 Conceptos BasicosEmilio Aviles Avila
 
Temario de nuevas tecnologias de bd
Temario de nuevas tecnologias de bdTemario de nuevas tecnologias de bd
Temario de nuevas tecnologias de bdMa Teresa Dominguez
 
Base de datos laboratorio
Base de datos laboratorioBase de datos laboratorio
Base de datos laboratoriofreddy Fred
 
Desarrollando KDevelop Ruby
Desarrollando KDevelop RubyDesarrollando KDevelop Ruby
Desarrollando KDevelop Rubykdeespana
 
Introducción a la algoritmia
Introducción a la algoritmiaIntroducción a la algoritmia
Introducción a la algoritmiapierre R.
 
Ruby on Rails - ETyC 2011
Ruby on Rails - ETyC 2011Ruby on Rails - ETyC 2011
Ruby on Rails - ETyC 2011Rafael Franco
 
[UNED2016] Practica 1 - Biblioteca mockito
[UNED2016] Practica 1 - Biblioteca mockito[UNED2016] Practica 1 - Biblioteca mockito
[UNED2016] Practica 1 - Biblioteca mockitoJose Barba Martinez
 
Fundamentos de Java
Fundamentos de Java Fundamentos de Java
Fundamentos de Java jmosorio777
 
Iniciando microservicios reales con JakartaEE/MicroProfile y arquetipos de Maven
Iniciando microservicios reales con JakartaEE/MicroProfile y arquetipos de MavenIniciando microservicios reales con JakartaEE/MicroProfile y arquetipos de Maven
Iniciando microservicios reales con JakartaEE/MicroProfile y arquetipos de MavenVíctor Leonel Orozco López
 
Introducción a JAVA
Introducción a JAVAIntroducción a JAVA
Introducción a JAVAjohitafresh
 
Hardware y software de un pc y un host en general
Hardware y software de un pc y un host  en generalHardware y software de un pc y un host  en general
Hardware y software de un pc y un host en generalGustavoAdolfoDiaz3
 

Similaire à Scala Overview (20)

El lenguaje de programación c++, apuntesprev
El lenguaje de programación c++, apuntesprevEl lenguaje de programación c++, apuntesprev
El lenguaje de programación c++, apuntesprev
 
Introducción a Scala
Introducción a ScalaIntroducción a Scala
Introducción a Scala
 
Curso Java Avanzado 0 Conceptos Basicos
Curso Java Avanzado   0 Conceptos BasicosCurso Java Avanzado   0 Conceptos Basicos
Curso Java Avanzado 0 Conceptos Basicos
 
Temario de nuevas tecnologias de bd
Temario de nuevas tecnologias de bdTemario de nuevas tecnologias de bd
Temario de nuevas tecnologias de bd
 
Base de datos laboratorio
Base de datos laboratorioBase de datos laboratorio
Base de datos laboratorio
 
Desarrollando KDevelop Ruby
Desarrollando KDevelop RubyDesarrollando KDevelop Ruby
Desarrollando KDevelop Ruby
 
Introducción a la algoritmia
Introducción a la algoritmiaIntroducción a la algoritmia
Introducción a la algoritmia
 
Metacoretex
MetacoretexMetacoretex
Metacoretex
 
Memoria PFC
Memoria PFCMemoria PFC
Memoria PFC
 
Ruby on Rails - ETyC 2011
Ruby on Rails - ETyC 2011Ruby on Rails - ETyC 2011
Ruby on Rails - ETyC 2011
 
[UNED2016] Practica 1 - Biblioteca mockito
[UNED2016] Practica 1 - Biblioteca mockito[UNED2016] Practica 1 - Biblioteca mockito
[UNED2016] Practica 1 - Biblioteca mockito
 
JRuby Al Rescate J2EE
JRuby Al Rescate J2EEJRuby Al Rescate J2EE
JRuby Al Rescate J2EE
 
Fundamentos de Java
Fundamentos de Java Fundamentos de Java
Fundamentos de Java
 
Redes Ii 2008 Ii
Redes Ii 2008 IiRedes Ii 2008 Ii
Redes Ii 2008 Ii
 
Iniciando microservicios reales con JakartaEE/MicroProfile y arquetipos de Maven
Iniciando microservicios reales con JakartaEE/MicroProfile y arquetipos de MavenIniciando microservicios reales con JakartaEE/MicroProfile y arquetipos de Maven
Iniciando microservicios reales con JakartaEE/MicroProfile y arquetipos de Maven
 
JRuby al Rescate de J2EE
JRuby al Rescate de J2EEJRuby al Rescate de J2EE
JRuby al Rescate de J2EE
 
Introducción a JAVA
Introducción a JAVAIntroducción a JAVA
Introducción a JAVA
 
Hardware y software de un pc y un host en general
Hardware y software de un pc y un host  en generalHardware y software de un pc y un host  en general
Hardware y software de un pc y un host en general
 
Curso UML DBACCESS
Curso UML DBACCESSCurso UML DBACCESS
Curso UML DBACCESS
 
Introducción a la Algoritmia
Introducción a la AlgoritmiaIntroducción a la Algoritmia
Introducción a la Algoritmia
 

Plus de Miguel Pastor

Liferay & Big Data Dev Con 2014
Liferay & Big Data Dev Con 2014Liferay & Big Data Dev Con 2014
Liferay & Big Data Dev Con 2014Miguel Pastor
 
Microservices: The OSGi way A different vision on microservices
Microservices: The OSGi way A different vision on microservicesMicroservices: The OSGi way A different vision on microservices
Microservices: The OSGi way A different vision on microservicesMiguel Pastor
 
Liferay and Big Data
Liferay and Big DataLiferay and Big Data
Liferay and Big DataMiguel Pastor
 
Reactive applications and Akka intro used in the Madrid Scala Meetup
Reactive applications and Akka intro used in the Madrid Scala MeetupReactive applications and Akka intro used in the Madrid Scala Meetup
Reactive applications and Akka intro used in the Madrid Scala MeetupMiguel Pastor
 
Reactive applications using Akka
Reactive applications using AkkaReactive applications using Akka
Reactive applications using AkkaMiguel Pastor
 
Liferay Devcon 2013: Our way towards modularity
Liferay Devcon 2013: Our way towards modularityLiferay Devcon 2013: Our way towards modularity
Liferay Devcon 2013: Our way towards modularityMiguel Pastor
 
Liferay Module Framework
Liferay Module FrameworkLiferay Module Framework
Liferay Module FrameworkMiguel Pastor
 
Hadoop, Cloud y Spring
Hadoop, Cloud y Spring Hadoop, Cloud y Spring
Hadoop, Cloud y Spring Miguel Pastor
 
Platform as a Service overview
Platform as a Service overviewPlatform as a Service overview
Platform as a Service overviewMiguel Pastor
 
Aspect Oriented Programming introduction
Aspect Oriented Programming introductionAspect Oriented Programming introduction
Aspect Oriented Programming introductionMiguel Pastor
 
Software measure-slides
Software measure-slidesSoftware measure-slides
Software measure-slidesMiguel Pastor
 
Groovy and Grails intro
Groovy and Grails introGroovy and Grails intro
Groovy and Grails introMiguel Pastor
 

Plus de Miguel Pastor (17)

Liferay & Big Data Dev Con 2014
Liferay & Big Data Dev Con 2014Liferay & Big Data Dev Con 2014
Liferay & Big Data Dev Con 2014
 
Microservices: The OSGi way A different vision on microservices
Microservices: The OSGi way A different vision on microservicesMicroservices: The OSGi way A different vision on microservices
Microservices: The OSGi way A different vision on microservices
 
Liferay and Big Data
Liferay and Big DataLiferay and Big Data
Liferay and Big Data
 
Reactive applications and Akka intro used in the Madrid Scala Meetup
Reactive applications and Akka intro used in the Madrid Scala MeetupReactive applications and Akka intro used in the Madrid Scala Meetup
Reactive applications and Akka intro used in the Madrid Scala Meetup
 
Reactive applications using Akka
Reactive applications using AkkaReactive applications using Akka
Reactive applications using Akka
 
Liferay Devcon 2013: Our way towards modularity
Liferay Devcon 2013: Our way towards modularityLiferay Devcon 2013: Our way towards modularity
Liferay Devcon 2013: Our way towards modularity
 
Liferay Module Framework
Liferay Module FrameworkLiferay Module Framework
Liferay Module Framework
 
Liferay and Cloud
Liferay and CloudLiferay and Cloud
Liferay and Cloud
 
Jvm fundamentals
Jvm fundamentalsJvm fundamentals
Jvm fundamentals
 
Hadoop, Cloud y Spring
Hadoop, Cloud y Spring Hadoop, Cloud y Spring
Hadoop, Cloud y Spring
 
Platform as a Service overview
Platform as a Service overviewPlatform as a Service overview
Platform as a Service overview
 
HadoopDB
HadoopDBHadoopDB
HadoopDB
 
Aspect Oriented Programming introduction
Aspect Oriented Programming introductionAspect Oriented Programming introduction
Aspect Oriented Programming introduction
 
Software measure-slides
Software measure-slidesSoftware measure-slides
Software measure-slides
 
Arquitecturas MMOG
Arquitecturas MMOGArquitecturas MMOG
Arquitecturas MMOG
 
Software Failures
Software FailuresSoftware Failures
Software Failures
 
Groovy and Grails intro
Groovy and Grails introGroovy and Grails intro
Groovy and Grails intro
 

Dernier

CLASE DE TECNOLOGIA E INFORMATICA PRIMARIA
CLASE  DE TECNOLOGIA E INFORMATICA PRIMARIACLASE  DE TECNOLOGIA E INFORMATICA PRIMARIA
CLASE DE TECNOLOGIA E INFORMATICA PRIMARIAWilbisVega
 
Desarrollo Web Moderno con Svelte 2024.pdf
Desarrollo Web Moderno con Svelte 2024.pdfDesarrollo Web Moderno con Svelte 2024.pdf
Desarrollo Web Moderno con Svelte 2024.pdfJulian Lamprea
 
International Women's Day Sucre 2024 (IWD)
International Women's Day Sucre 2024 (IWD)International Women's Day Sucre 2024 (IWD)
International Women's Day Sucre 2024 (IWD)GDGSucre
 
Trabajo Mas Completo De Excel en clase tecnología
Trabajo Mas Completo De Excel en clase tecnologíaTrabajo Mas Completo De Excel en clase tecnología
Trabajo Mas Completo De Excel en clase tecnologíassuserf18419
 
EPA-pdf resultado da prova presencial Uninove
EPA-pdf resultado da prova presencial UninoveEPA-pdf resultado da prova presencial Uninove
EPA-pdf resultado da prova presencial UninoveFagnerLisboa3
 
9egb-lengua y Literatura.pdf_texto del estudiante
9egb-lengua y Literatura.pdf_texto del estudiante9egb-lengua y Literatura.pdf_texto del estudiante
9egb-lengua y Literatura.pdf_texto del estudianteAndreaHuertas24
 
guía de registro de slideshare por Brayan Joseph
guía de registro de slideshare por Brayan Josephguía de registro de slideshare por Brayan Joseph
guía de registro de slideshare por Brayan JosephBRAYANJOSEPHPEREZGOM
 
Proyecto integrador. Las TIC en la sociedad S4.pptx
Proyecto integrador. Las TIC en la sociedad S4.pptxProyecto integrador. Las TIC en la sociedad S4.pptx
Proyecto integrador. Las TIC en la sociedad S4.pptx241521559
 
Presentación guía sencilla en Microsoft Excel.pptx
Presentación guía sencilla en Microsoft Excel.pptxPresentación guía sencilla en Microsoft Excel.pptx
Presentación guía sencilla en Microsoft Excel.pptxLolaBunny11
 
pruebas unitarias unitarias en java con JUNIT
pruebas unitarias unitarias en java con JUNITpruebas unitarias unitarias en java con JUNIT
pruebas unitarias unitarias en java con JUNITMaricarmen Sánchez Ruiz
 
POWER POINT YUCRAElabore una PRESENTACIÓN CORTA sobre el video película: La C...
POWER POINT YUCRAElabore una PRESENTACIÓN CORTA sobre el video película: La C...POWER POINT YUCRAElabore una PRESENTACIÓN CORTA sobre el video película: La C...
POWER POINT YUCRAElabore una PRESENTACIÓN CORTA sobre el video película: La C...silviayucra2
 
Redes direccionamiento y subredes ipv4 2024 .pdf
Redes direccionamiento y subredes ipv4 2024 .pdfRedes direccionamiento y subredes ipv4 2024 .pdf
Redes direccionamiento y subredes ipv4 2024 .pdfsoporteupcology
 
Global Azure Lima 2024 - Integración de Datos con Microsoft Fabric
Global Azure Lima 2024 - Integración de Datos con Microsoft FabricGlobal Azure Lima 2024 - Integración de Datos con Microsoft Fabric
Global Azure Lima 2024 - Integración de Datos con Microsoft FabricKeyla Dolores Méndez
 

Dernier (13)

CLASE DE TECNOLOGIA E INFORMATICA PRIMARIA
CLASE  DE TECNOLOGIA E INFORMATICA PRIMARIACLASE  DE TECNOLOGIA E INFORMATICA PRIMARIA
CLASE DE TECNOLOGIA E INFORMATICA PRIMARIA
 
Desarrollo Web Moderno con Svelte 2024.pdf
Desarrollo Web Moderno con Svelte 2024.pdfDesarrollo Web Moderno con Svelte 2024.pdf
Desarrollo Web Moderno con Svelte 2024.pdf
 
International Women's Day Sucre 2024 (IWD)
International Women's Day Sucre 2024 (IWD)International Women's Day Sucre 2024 (IWD)
International Women's Day Sucre 2024 (IWD)
 
Trabajo Mas Completo De Excel en clase tecnología
Trabajo Mas Completo De Excel en clase tecnologíaTrabajo Mas Completo De Excel en clase tecnología
Trabajo Mas Completo De Excel en clase tecnología
 
EPA-pdf resultado da prova presencial Uninove
EPA-pdf resultado da prova presencial UninoveEPA-pdf resultado da prova presencial Uninove
EPA-pdf resultado da prova presencial Uninove
 
9egb-lengua y Literatura.pdf_texto del estudiante
9egb-lengua y Literatura.pdf_texto del estudiante9egb-lengua y Literatura.pdf_texto del estudiante
9egb-lengua y Literatura.pdf_texto del estudiante
 
guía de registro de slideshare por Brayan Joseph
guía de registro de slideshare por Brayan Josephguía de registro de slideshare por Brayan Joseph
guía de registro de slideshare por Brayan Joseph
 
Proyecto integrador. Las TIC en la sociedad S4.pptx
Proyecto integrador. Las TIC en la sociedad S4.pptxProyecto integrador. Las TIC en la sociedad S4.pptx
Proyecto integrador. Las TIC en la sociedad S4.pptx
 
Presentación guía sencilla en Microsoft Excel.pptx
Presentación guía sencilla en Microsoft Excel.pptxPresentación guía sencilla en Microsoft Excel.pptx
Presentación guía sencilla en Microsoft Excel.pptx
 
pruebas unitarias unitarias en java con JUNIT
pruebas unitarias unitarias en java con JUNITpruebas unitarias unitarias en java con JUNIT
pruebas unitarias unitarias en java con JUNIT
 
POWER POINT YUCRAElabore una PRESENTACIÓN CORTA sobre el video película: La C...
POWER POINT YUCRAElabore una PRESENTACIÓN CORTA sobre el video película: La C...POWER POINT YUCRAElabore una PRESENTACIÓN CORTA sobre el video película: La C...
POWER POINT YUCRAElabore una PRESENTACIÓN CORTA sobre el video película: La C...
 
Redes direccionamiento y subredes ipv4 2024 .pdf
Redes direccionamiento y subredes ipv4 2024 .pdfRedes direccionamiento y subredes ipv4 2024 .pdf
Redes direccionamiento y subredes ipv4 2024 .pdf
 
Global Azure Lima 2024 - Integración de Datos con Microsoft Fabric
Global Azure Lima 2024 - Integración de Datos con Microsoft FabricGlobal Azure Lima 2024 - Integración de Datos con Microsoft Fabric
Global Azure Lima 2024 - Integración de Datos con Microsoft Fabric
 

Scala Overview

  • 1. Scala: Un lenguaje escalable para la JVM Miguel Pastor
  • 2. Scala: Un lenguaje escalable para la JVM Miguel Pastor 2
  • 3. Tabla de contenidos 1.-¿Por qué Scala? 1 1.1.-¿Qué es Scala? 1 1.1.1.-Orientación a objetos 2 1.1.2.-Lenguaje funcional 2 1.1.3.-Lenguaje multiparadigma 3 1.1.4.-Lenguaje extensible y escalable 3 1.1.5.-Ejecución sobre la JVM 4 1.2.-En crisis 4 1.2.1.-Ley de Moore 4 1.2.2.-Programando para multinúcleo 4 1.3.-Objetivos 5 2.-Fundamentos de Scala 7 2.1.-Clases y objetos 7 2.2.-Reglas de inferencia de puntos y coma 8 2.3.-Singleton objects 9 2.4.-Objetos funcionales 9 2.4.1.-Números racionales 9 2.4.2.-Constructores 10 2.4.3.-Sobreescritura de métodos 10 2.4.4.-Precondiciones 10 2.4.5.-Atributos y métodos 11 2.4.6.-Operadores 12 2.5.-Funciones y closures 12 2.5.1.-Funciones first-class 13 2.5.2.-Closures 13 2.5.3.-Tail recursion 14 2.6.-Currying 14 2.7.-Traits 15 2.7.1.-¿Cómo funcionan los traits? 15 2.7.2.-Ejemplo: objetos rectangulares 16 2.7.3.-Uso de traits como stackable modifications 17 2.7.4.-Traits: ¿si o no? 19 2.8.-Patrones y clases case 20 2.8.1.-Clases case 20 2.8.2.-Patrones: estructura y tipos 21 2.8.2.1.-Patrones wildcard 21 2.8.2.2.-Patrones constantes 21 2.8.2.3.-Patrones variables 21 iii
  • 4. 2.8.2.4.-Patrones constructores 22 2.8.2.5.-Patrones de secuencia 22 2.8.2.6.-Patrones tipados 22 2.9.-Conclusiones 23 3.-Actors y concurrencia 24 3.1.-Problemática 24 3.2.-Modelo de actores 25 3.3.-Actores en Scala 25 3.3.1.-Buenas prácticas 26 3.3.1.1.-Ausencia de bloqueos 26 3.3.1.2.-Comunicación exclusiva mediante mensajes 27 3.3.1.3.-Mensajes inmutables 27 3.3.1.4.-Mensajes autocontenidos 27 3.4.-Un ejemplo completo 28 3.4.1.-Especificación del problema 28 3.4.2.-Implementación del producer y coordinator 28 3.4.3.-Interfaz iterator 29 4.-Conclusiones y trabajo futuro 31 4.1.-Conclusiones 31 4.2.-Líneas de trabajo 32 A.-Modelo de objetos de Scala 33 B.-Producers 34 B.1.-Código fuente completo 34 Bibliografía 37 iv
  • 5. ¿Por qué Scala? Capítulo 1. ¿Por qué Scala? Durante este capítulo se cubrirán aspectos relativos como qué es Scala, características de alto nivel del lenguaje o por qué deberíamos escoger Scala como nuestro siguiente lenguaje de programación. En la parte final del mismo analizaremos los objetivos perseguidos con el desarrollo de este trabajo así como la estructura del mismo. Debido a la creciente proliferación de una gran cantidad de diferentes lenguajes en las plataformas JVM, .NET, OTP entre otras muchas ha surgido el dilema entre los desarrolladores acerca de cuál debería ser el siguiente lenguaje de progragramación que se debería aprender. Entre la amplia variedad de lenguajes disponibles como Groovy, Erlang, Ruby o F# ¿por qué deberíamos aprender Scala ?. Durante este capítulo analizaremos las características de alto nivel del lenguaje estableciendo una comparación con aquellos lenguajes con los que estemos más familiarizados. Los programadores provenientes de la orientación a objetos así como aquellos cuyo origen es la programación funcional rápidamente se sentirán cómodos con Scala dado que este lenguaje soporta ambos paradigmas. Scala es uno de esos extraños lenguajes en los que se integran de manera satisfactoria las características de los lenguajes orientados a objetos y los funcionales. 1.1. ¿Qué es Scala? Scala es un lenguaje de propósito general diseñado para expresar los patrones de programación más comunes de una manera sencilla, elegante y segura. Integra de manera sencilla características de orientación a objetos y lenguajes funcionales, permitiendo de este modo que los desarrolladores puedan ser más productivos. Su creador, Martin Odersky, y su equipo comenzaron el desarrollo de este nuevo lenguaje en el año 2001, en el laboratorio de métodos de programación en EPFL1 Scala hizo su aparación pública sobre la plataforma JVM (Java Virtual Machine) en enero de 2004 y unos meses después haria 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 la cual se acrecenta día tras día. 1 École Polytechnique Fédérale de Lausanne 1
  • 6. ¿Por qué Scala? 1.1.1. Orientación a objetos La popularidad de lenguajes como Java, C# o Ruby han hecho que la programación orientada a objetos sea un paradigma ampliamente aceptado entre la mayoría de desarrolladores. Aunque existen numerosos lenguajes 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 en envío de mensajes a objetos. • Todos los tipos definidos por el usuario son 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).2 De manera adicional a todas las caracteríscticas puras de un lenguaje orientado a objetos presentadas anteriormente, Scala añade algunas innovaciones en el espacio de los lenguajes orientados a objetos: • Composición modular de mixin. Mecanismo que permite la composición de clases para el diseño de componentes reutilizables evitando los problemas 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 mixin no dependen de ningún método y/o atributo de aquellas 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-type3 • Abstracción de tipos. Existen dos mecanismos principales de abstracción en los lenguajes de programación: la parametrización y los miembros abstractos. Scala soporta ambos estilos de abstracción de manera uniforme para tipos y valores. 1.1.2. Lenguaje funcional La programación funcional es un paradigma en el que se trata la computación como la evaluación de funciones matemáticas y se evitan los programas con estado y datos que puedan ser modificados. Se ofrece adopta una visión más matemática del mundo en el que 2 http://en.wikipedia.org/wiki/Smalltalk 2
  • 7. ¿Por qué Scala? los programas están compuestos por numerosas funciones que esperan una determinada entrada y producen una determinada salida y, en muchas ocasiones, otras funciones. Otro de los aspectos de la programación funcional es la ausencia de efectos colaterales gracias a los cuales los programas desarrollados son mucho más sencillos de comprender y probar. Adicionalmente, se facilita la programación concurrente, evitando que se convierta en un problema gracias a la ausencia de cambio. Los lenguajes de programación que soportan este estilo de programación deberían ofrecer algunas de las siguientes características: • Funciones de primer nivel. • Closures • Asignación simple. • Evaluación tardía • Inferencia de tipos • Optimización del tail call • Efectos monadic Es importante tener claro que Scala no es un lenguaje funcional puro dado que en este tipo de lenguajes no se permiten las modificaciones y las variables se utilizan de manera matemática.4. Scala da soporte tanto a variables inmutables (tambien conocidas como values) como a variables que apuntan estados no permanentes 1.1.3. Lenguaje multiparadigma Scala ha sido el primero en incorporar y unificar la programación funcional y la orientación a objetos en un lenguaje estáticamente tipado. La pregunta es por qué necesitamas 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.4. Lenguaje extensible y 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 la exigencia 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 abastracción de 4 Un ejemplo de lenguaje funcional puro sería Haskell 3
  • 8. ¿Por qué Scala? componentes, su sintaxis reducida, el soporte para la orientación a objetos y funcional han contribuido a que el lenguaje sea más escalable. 1.1.5. Ejecución sobre la JVM La características más relevante 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 con Java y su ecosistema (herramientas, IDEs, librerías, . . .) 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. 1.2. En crisis A pesar de las altas prestaciones que los procesadores están adquiriendo, los desarrolladores software encuentran los mecanismos para agotarla. El motivo es que, gracias al software, se están resolviendo problemas muy complejos, y esta tendencia continuará creciendo, al menos en el futuro cercano. La pregunta clave es si los fabricantes de procesadores serán capaces de sostener la demanda de potencia y velocidad exigida por los desarrolladores. 1.2.1. Ley de Moore Si nos hacemos eco de la ley postulada por Moore por el año 19655, el número de transistores por circuito integrado se duplica cada dos años aproximadamente. Sin embargo, muchos fabricantes están tocando techo con esta ley6 y están apostando por los procesadores multinúcleo. Las buenas noticias es que la potencia de los procesadores seguirá creciendo de manera notable aunque las malas noticias es que los programas actuales y entornos de desarrollo necesitarán cambiar para hacer uso de las ventajas ofrecidas por una CPU con varios núcleos. 1.2.2. Programando para multinúcleo ¿Cómo se puede beneficiar el software de la nueva relución iniciada por los procesadores multimedia? 5 Concretamente la fecha data del 19 de Abril de 1965 6 http://www.gotw.ca/publications/concurrency-ddj.htm 4
  • 9. ¿Por qué Scala? Concurrencia. La concuncurrencia será, si no lo es ya, el modo en el que podremos escribir soluciones software que nos permitan resolver problemas complejos, distrubidos y empresariales, beneficiándonos de la productividad ofrecida por múltiples núcleos. Al fin y al cabo, ¿quién no desea software eficiente?. En el modelo tradicional de concurrencia basado en hilos los programas son "troceados" en múltiples unidades de ejecución concurrentes (threads) en el que cada de ellos opera en un segmento de memoria compartida. En numerosas ocasiones el modelo anterior ocasiona "condiciones de carrera" complicadas de detectar así como siuaciones de "deadlocks" que ocasionan inversiones de semanas completas intentando reproducir, aislar y subsanar el error. El origen de todas estos problemas no radica en el modelo de hilos sino que reside en segmentos de memoria compartida. La programación concurrente se ha convertido en un modelo demasiado complicado para los desarrolladores por lo que necesitaríamos un mejor modelo de programación concurrente que nos permita crear y mantener programas concurrentes de manera sencilla. Scala adopta un enfoque completamente diferente a la problemática de la concurrencia: el modelo basado en actores. Un actor es un modelo matemático de computación concurrente en el que se encapsulan datos, código y su propio hilo de control, comunicándose de manera asíncrona mediante técnicas de paso de mensajes inmutables. La arquitectura base de este modelo está basada en políticas de compartición cero y componentes ligeros. Haciendo un poco de historia, el modelo de actores fue propuesto por primera vez por Carl Hewitt en el año 1973 en el famoso artículo "A Universal Modular ACTOR Formalism for Artificial Intelligence " , para posteriormente ser mejorado por Gul Agha con su “ACTORS: A Model of Concurrent Computation in Distributed Systems”). El primer lenguaje que llevó a cabo la implementación de este modelo fue Erlang. Tras el éxito obtenido por el lenguaje en lugares tan populares como Ericsson (su lugar de nacimiento), Yahoo o Facebook, el modelo de actores se ha convertido en una alternativa viable para solucionar la problemática derivada de la concurrencia, por lo que Scala ha decidido adoptar este mismo enfoque. 1.3. Objetivos A lo largo de las secciones anteriores hemos descrito de manera superficial algunas de las características más relevantes del lenguaje Scala así como las motivaciones principales del mismo. El resto del trabajo estará dividido en las siguientes secciones: • Durante la primera sección, esta que nos ocupa, analizamos las características generales del lenguaje y los objetivos del presente documento. • La segunda sección abarcará los principales fundamentos del lenguaje, describiendo tanto los mecanismos funcionales como la orientación a objetos, ambos disponibles de manera nativa en el lenguaje. • Analizaremos, aunque no de manera excesivamente exhaustiva, el modelo de programación concurrente propuesto por Scala (basado en una librería de actores). Construiremos una pequeña serie de ejemplos que nos permita poner en marcha los 5
  • 10. ¿Por qué Scala? conocimientos adquiridos, tanto aquellos relativos a la programación concurrente como los analizados en la primera parte del trabajo. • Debido a limitaciones de tiempo y espacio no podremos abordar muchos temas interesantes realacionados con el lenguaje Scala. por lo que durante la última sección de este trabajo se propondrán numerosos temas de ampliación: web funcional, otras aproximaciones de actores en Scala, arquitectura del compilador Scala, etc 6
  • 11. Fundamentos de Scala Capítulo 2. Fundamentos de Scala A lo largo de este capítulo ahondaremos en los aspectos fundalmentales del lenguaje, describiendo las características más relevantes tanto de la orientación a objectos como la funcional. No olvidemos que Scala, tal y como hemos descrito durante el capítulo anterior, permite la confluencia del paradigma funcional y la orientación a objetos. Aquellos lectores familiarizados con el lenguaje de programación Java encontrará muchos de los conceptos aquí descritos (sobre todo aquellos conceptos relativos al paradigma funcional) similares aunque no son exactamente idénticos. 2.1. Clases y objetos Del mismo modo que en todos los lenguajes orientados a objetos Scala permite la definición de clases en las que podremos añadir métodos y atributos: class MyFirstClass{ val a = 1 } Si deseamos instanciar un objeto de la clase anterior tendremos que hacer uso de la palabra reservada new val v = new MyFirstClass En Scala existen dos tipos de variables, vals y vars, que deberemos especificar a la hora de definir las mismas: • Se 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, podremos indicar que una variable es de clase var, consiguiendo con esto que su valor pueda ser modificado durante todo su ciclo de vida. Uno de los principales mecanismos utilizados que garantizan la robustez de un objeto es la afirmación 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 7
  • 12. Fundamentos de Scala en la misma clase, todo el código podría modificar el estado del mismo estará localizado en dicha clase.1 El siguiente paso será incorporar funcionalidad a nuestras clases; para ello podremos definir métodos mediante el uso de la palabra reservada def: class MyFirstClass{ var a = 1 def add(b:Byte):Unit={ a += b } } Una característica importante de los métodos en Scala es que todos los parámetros son inmutables, es decir, vals. Por tanto, si intentamos modificar el valor de un parámetro en el cuerpo de un método obtendremos un error del compilación: def addNotCompile(b:Byte) : Unit = { b = 1 // Esto no compilará puesto que el // parámetro b es de tipo val a += b } Otro aspecto relevante que podemos apreciar en el código anterior es que no es necesario el uso explícito de la palabra return, Scala retornará el valor de la última expresión que aparece en el cuerpo del método. Adicionalmente, si el cuerpo de la función retorna una única expresión podemos obviar la utilización de las llaves. Habitualmente los métodos que presentan un tipo de retorno Unit tienen efectos colaterales, es decir, modifican el estado del objeto sobre el que actúan. Otra forma diferente de llevar a cabo la definición de este tipo de métodos consiste en eliminar el tipo de retorno y el símbolo igual y englobar el cuerpo de la función entre llaves, tal y como se indica a continuación: class MyFirstClass { private var sum = 0 def add(b:Byte) { sum += b } } 2.2. Reglas de inferencia de puntos y coma La utilización de los puntos y coma como indicadores de terminación de sentencia es, habitualmente, opcional aunque en determinadas ocasiones la ausencia de los mismos puede llevarnos a resultados no esperados. Por noram general los saltos de línea son tratados como puntos y coma salvo que algunas de las siguientes condiciones sea cierta: 1 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 8
  • 13. Fundamentos de Scala • 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. 2.3. Singleton objects Scala no soporta la definición de atributos estáticos en las clases, incorporando en su lugar el concepto de singleton objects. 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 companion object mientras que la clase se denomina companion class del objeto singleton. Inicialmente, sobre todo aquellos desarrolladores provenientes 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 singleton objects y las clases es que los primeros no aceptan parámetros (no podemos instanciar un objeto singleton mediante la palabra reservada new) mientras que las segundos si lo permiten. Cada uno de los singleton objects es implementado mediante una instancia de una synthetic class 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.4. Objetos funcionales A lo largo de las secciones anteriores hemos adquirido una serie de conocimientos básicos relativos a la orientación a objetos ofrecida por Scala. Durante las siguientes páginas analizaremos cómo se pueden construir objetos funcionales, es decir, inmutables, mediante la definición de clases. El desarrollo de esta sección nos permitirá ahondar en cómo los aspectos funcionales y los de orientación a objetos confluyen en el lenguaje. Adicionalmente las siguientes secciones no servirán como base para la introducción de nuveos conceptos de orientación a objetos cómo parámetros de clase, sobreescritura, self references o métodos entre otros muchos. 2.4.1. Números racionales Los números racionales son aquellos que pueden ser expresados como un cociente n/ d. Durante las siguientes secciones construiremos una clase que nos permita modelar el comportamiento de este tipo de números. A continuación se presentan algunas de sus características principales: 9
  • 14. Fundamentos de Scala • Suma/resta de números racionales. Se debe obtener un común denominador de ambos denominadores y posteriormente sumar/restar los numeradores. • Multiplicación de números racionales. Se multiplican los numeradores y denominadores de los integrantes de la operación. • División de números racionales. Se intercambian el numerador y denominador del operando que aparece a la derecha y posteriormente se realiza una operación de multiplicación. 2.4.2. Constructores Puesto que hemos decidido que nuestros números racionales sean inmutables necesitaremos que los clientes de esta clase proporcionen toda la información en el momento de creación de un objeto. Podríamos comenzar nuestro diseño del siguiente modo: class Rational (n:Int,d:Int) Los parámetros definidos tras el nombre de la clase son conocidos como parámetros de clase. El compilador generará un constructor primario en cuya signatura aparecerán los dos parámetros escritos en la definición de la clase. Cualquier código que escribamos dentro del cuerpo de la clase que no forme parte de un atributo o de un método será incluido en el constructor primario indicado anteriormente. 2.4.3. Sobreescritura de métodos Si deseamos sobreescribir un método heredado de una clase padre en la jerarquía tendremos que hacer uso de la palabra reservada override. Por ejemplo, si en la clase Rational deseamos sobreescribir la implementación por defecto de toString podríamos actuar del siguiente modo: override def toString = n + "/" + d 2.4.4. Precondiciones Una de las características de los números racionales no admiten el valor cero como denominador aunque sin embargo, con la definición actual de nuestra clase Rational podríamos escribir código como: new Rational(11,0) algo que violaría nuestra definición actual de números racionales. Dado que estamos construyendo una clase inmutable y toda la información debe estar disponible en el momento que se invoca al constructor este último deberá asegurarse de que el denominador indicado no toma el valor cero (0). La mejor aproximación para resolver este problema pasa por hacer uso de las precondiciones. Este concepto, incluido en el lenguaje, representa un conjunto de restricciones que pueden 10
  • 15. Fundamentos de Scala establecerse sobre los valores pasados a métodos o constructores y que deben ser satisfechas por el cliente que realiza la llamada del método/constructor: class Rational(n: Int, d: Int) { require(d != 0) override def toString = n +"/"+ d } La restricciones se establecen mediante el uso del método require el cual espera un argumento booleano. En caso de que la condición exigida no se cumpla el método require disparará una excepción de tipo IllegalArgumentException. 2.4.5. Atributos y métodos Definamos en nuestra clase un método público que reciba un número racional como parámetro y retorne como resultado la suma de ambos operandos. Puesto que estamos construyendo una clase inmutable el nuevo método deberá retornar la suma en un nuevo número racional: class Rational(n: Int, d: Int) { require(d != 0) override def toString = n +"/"+ d // no compila: no podemos hacer that.d o that.n // deben definirse como atributos def add(that: Rational): Rational = new Rational(n * that.d + that.n * d, d * that.d) } El código anterior muestra una primera aproximación de solución aunque incorrecta dado que se producirá un error de compilación. Aunque los parámetros de clase n y d están el ámbito del método add solo se puede acceder a su valor en el objeto sobre el que se realiza la llamada. Para resolver el problema planteado en el fragmento de código anterior tendremos que declarar d y n como atributos de la clase Rational: class Rational(n: Int, d: Int) { require(d != 0) val numer: Int = n // declaración de atributos val denom: Int = d override def toString = numer +"/"+ denom def add(that: Rational): Rational = new Rational(numer * that.denom + that.numer * denom, denom * that.denom) } 11
  • 16. Fundamentos de Scala Nótese que en los fragmentos de código anteriores estamos manteniendo la inmutabilidad de nuestro diseño. En este caso, el operador de adición add retorna un nuevo objeto racional que representa la suma de ambos números, en lugar de realizar la suma sobre el objeto que realiza la llamada. A continuación incorporemos a nuestra clase un método privado que nos ayude a determinar el máximo común divisor: private def gcd(a:Int,b:Int):Int = if(b == 0) a else gcd(b,a%b) El listado de código anterior nos muestra como podemos incorporar un método privado a nuestra clase que, en este caso, nos sirve como método auxiliar para calcular el máximo común divisor de dos números enteros. 2.4.6. Operadores La implementación actual de nuestra clase Rational es correcta aunque podríamos definirla de modo que su uso resultara mucho más intuitivo. Una de las posibles mejoras que podríamos introducir sería la inclusión de operadores: def + (that: Rational): Rational = new Rational( numer * that.denom + that.numer * denom, denom * that.denom ) def * (that: Rational): Rational = new Rational(numer * that.numer, denom * that.denom) De este modo podríamos escribir código como el que a continuación se indica: var a = new Rational(2,5) var b = new Rational(1,5) var sum = a + b 2 2.5. Funciones y closures Hasta el momento hemos analizado algunas de las características más relevantes del lenguaje Scala, poniendo de manifiesto la incorporación de fundamentos de lenguajes funcionales así como de lenguajes orientados a objetos. 2 También podríamos escribir a.+(b) aunque en este caso el código resultante sería mucho menos legible 12
  • 17. Fundamentos de Scala Cuando nuestros programas crecen necesitamos hacer uso de un conjunto de abstracciones que nos permitan dividir dicho programa en piezas más pequeñas y manejables que permitan una mejor comprensión del mismo. Scala ofrece varios mecanismos para definir funciones que no están presentes en Java. Además de los métodos, que no son más que funciones miembro de un objeto, podemos hacer uso de funciones anidadas en funciones, function literals y function values. Durante las siguientes secciones de este apartado profundizaremos en alguno de los mecanismos anteriores no analizados en produndidad anteriormente. 2.5.1. Funciones first-class Scala incluye una de las características principales del paradigma funcional: first class functions. No sólamente podemos definir funciones e invocarlas sino que también podemos definirlas como literales para, posteriormente, pasarlas como valores. Las funciones literales son compiladas en una clase que, cuando es instanciada, se convierte en una function value. Por lo tanto, la principal diferencia entre las funciones literales y las funciones valor es que las primeras existen en el código fuente mientras que las segundas existen como objetos en tiempo de ejecución. A continuación se define un pequeño ejemplo de una función literal que suma el valor 1 al número indicado: (x:Int) => x + 1 Las funciones valor son objetos propiamente dichos por lo que podemos almacenarlas en variables o invocarlas mediante la notación de paréntesis habitual. 2.5.2. Closures Las funciones literales que hemos visto hasta este momento han hecho uso, única y exclusivamente, de los parámetros pasados a la función. Sin embargo, podríamos definir funciones literales en las que se hace uso de variables definidas en otro punto de nuestro programa: (x:Int) = x * other La variable other es conocida como una free variable puesto que la función no le da un significado a la misma. Al contrario, la variable x es conocida como bound variable puesto que tiene un significado en el contexto de la función. Si intentamos utilizar esta función en un contexto en el que no está accesible una variable other obtendremos un error de compilación indicándonos que dicha variable no está disponible. La función valor creada en tiempo de ejecución a partir de la función literal es conocida como closure. El nombre se deriva del acto de "cerrar" la función literal mediante la captura en el ámbito de la función de los valores de sus free variables. Una función valor que no presenta free variables, creada en tiempo de ejecución a partir de su función literal no es una closure en el sentido más estricto de la definición dado que dicha función ya se encuetra "cerrada" en el momento de su escritura. 13
  • 18. Fundamentos de Scala El fragmento de código anterior hace que nos planteemos la siguiente pregunta: ¿que ocurre si la variable other es modificada después de que la closure haya sido creada? La respuesta es sencilla: en Scala la closure tiene visión sobre el cambio ocurrido. La regla anterior también se cumple en sentido contrario: su una closure modifica alguno de sus valores capturados estos últimos son visibles fuera del ámbito de la misma. 2.5.3. Tail recursion A continuación presentamos una función recursiva que aproxima un valor mediante un conjunto de repetidas mejoras hasta que es suficientemente bueno: def aproximate (guess:Double):Double = if(isGoodEnough(guess) guess else aproximate(improve(guess)) Las funciones que presentan este tipo de tipología (se llaman a si mismas en la última sentencia del cuerpo de la función) son llamadas funciones tail recursive. El compilador de Scala detecta esta situación y reemplaza la última llamada con un salto al comienzo de la función tras actualizar los parámetros de la función con los nuevos valores. El uso de tail recursion es limitado debido a que el conjunto de instrucciones ofrecido por la máquina virtual (JVM) dificulta de manera notable la implementación de otros tipos de tail recursion. Scala únicamente optimiza llamadas recursivas a una función dentro de la misma. Si la recursión es indirecta, como la que se muestra en el siguiente fragmento de código, no se puede llevar a cabo ningún tipo de optimización: def isEven(x:Int): Boolean = if(x==0) true else isOdd(x-1) def isOdd(x:Int): Boolean = if(x==0) false else isEven(x-1) 2.6. Currying Scala no incluye un excesivo número de instrucciones de control de manera nativa aunque nos permite llevar a cabo la definición de nuestras propias construcciones de manera sencilla. A lo largo de esta seccion analizaremos como definir nuestras propias abstracciones de control con un parecido muy próximo a extensiones del lenguaje. El primer paso consiste en comprender una de las técnicas más comunes de los lenguajes funcionales: currying. Una curried function es aplicada múltiples listas de argumentos en lugar de una sola. El siguiente fragmento de código nos muestras una función tradicional que recibe dos argumentos de tipo entero y retorna la suma de ambos: def plainSum(x:Int, y:Int) = x + y A continuación se muestras una curried function similar a la descrita en el fragmento de código anterior: 14
  • 19. Fundamentos de Scala def curriedSum(x:Int)(y:Int) = x + y Cuando ejecutamos la sentencia curriedSum(9)(2) estamos obteniendo dos llamadas tradicionales de manera consecutiva. La primera invocación recibe el parámetro x y retorna una función valor para la segunda función. Esta segunda función recibe el parámetro y. El siguiente código muestra una función first que lleva a cabo lo que haría la primera de las invocaciones de la función curriedSum anterior: def first(x: Int) = (y: Int) => x + y Invocando a la función anterior con el valor 1 obtendríamos una nueva función: def second = first(1) La invocación de este segunda función con el parámetro 2 retornaría el resultado. 2.7. Traits Los traits son la unidad básica de reutilización de código en Scala. Un trait encapsula definiciones de métodos y atributos que pueden ser reutilizados mediante un proceso de mixin llevado a cabo en conjunción con las clases. Al contrario que en el mecanismo de herencia, en el que únicamente se puede tener un padre, una clase puede llevar a cabo un proceso de mixin con un número indefinido de traits. 2.7.1. ¿Cómo funcionan los traits? La definición de un trait es similar a la de una clase tradicional salvo que se utiliza la palabra reservada trait en lugar de class. trait MyFirstTrait { def printMessage(){ println("This is my first trait") } } Una vez definido puede ser "mezclado" junto a una clase mediante el uso de las palabras reservadas extend o with en XXXX analizaremos las diferencias e implicaciones de cada una de estas alternativas): class MyFirstMixin extends MyFirstTrait{ override def toString = "This is my first mixin in Scala" } Cuando utilizamos la palabra reservada extends para realizar el proceso de mixin estaremos heredando de manera implícita las superclases del trait. Los métodos heredados de un trait se utilizan del mismo modo que se utilizan los métodos heredados de una clase. De manera adicional, un trait también define un tipo. 15
  • 20. Fundamentos de Scala En el caso de que deseemos realizar un proceso de mixin en el que una clase ya indica un padre de manera explicita mediante el uso extends tendremos que utilizar la palabra reservada with. Si deseamos incluir en el proceso de mixin múltiples traits no trendremos más que incluir más cláusulas with. Llegados a este punto podríamos pensar que los traits son como interfaces Java con métodos concretos pero realmente pueden hacer muchas más cosas. Por ejemplo, los traits pueden definir atributos y mantener un estado. Realmente, en un trait podemos hacer lo mismo que en una definición de clase con una sintaxis similar aunque existen dos excepciones: • Un trait no puede tener parámetros de clase (los parámetros pasados al constructor primario de la clase). • Mientras que en las clases las llamadas a métodos de clases padre (super.xxx) son enlazadas de manera estática en el caso de los traits dichas llamadas son enlazadas dinámicamente. Si en una clase escribimos super.method() sabremos en todo momento que implementación del método será invocada. Sin embargo, el mismo código escrito en un trait provoca un desconocimiento de la implementación del método que será invocado en tiempo de ejecución. Dicha implementación será determinada cada una de las veces que un trait y una clase realizan un proceso de mixin. Este curioso comportamiento de super es la clave que permite a los traits trabajar como stackable modifications3 que veremos a continuación. 2.7.2. Ejemplo: objetos rectangulares Las librerías gráficas habitualmente presentan numerosas clases que representan objetos rectangulares: ventanas, selección de una región de la pantalla, imágenes, etc. Son muchos los métodos que nos gustaría tener en el API por lo que necesitaríamos una gran cantidad de desarrolladores que poblaran la librería con métodos para todos los objetos rectangulares. En Scala, los desarrolladores de la librería podrían hacer uso de traits para incorporar los métodos necesarios en aquellas clases que se deseen. Una primera aproximación en la que no se hace uso de traits se muestra a continuación: class Point(val x: Int, val y: Int) class Rectangle(val topLeft: Point, val bottomRight: Point) { def left = topLeft.x def right = bottomRight.x def width = right - left // más métodos . . . } Incorporemos un nuevo componente gráfico: abstract class Component { 16
  • 21. Fundamentos de Scala def topLeft: Point def bottomRight: Point def left = topLeft.x def right = bottomRight.x def width = right - left // más métodos . . . } Modifiquemos ahora la aproximación anterior e incorporemos el uso de traits. Incorporemos un trait que incorpore la funcionalidad común vista en los dos fragmentos de código anterior: trait Rectangular { def topLeft: Point def bottomRight: Point def left = topLeft.x def right = bottomRight.x def width = right - left // más métodos } La clase Component podría realizar un proceso de mix con el trait anterior para incorporar toda la funcionalidad proporcionada por este último: abstract class Component extends Rectangular{ // nuevos métodos para este tipo de widgets } Del mismo modo que la clase Component , la clase Rectangle podría realizar el proceso de mixin con el trait Rectangular: class Rectangle(val topLeft: Point, val bottomRight: Point) extends Rectangular { // métodos propios de la clase rectangle } 2.7.3. Uso de traits como stackable modifications Hasta el momento hemos visto uno de los principales usos de los traits: el enriquecimiento de interfaces. Durante la sección que nos ocupa analizaremos otro de los usos más populares de los traits: facilitar stackable modifications en las clases. Los traits nos permitirán modificar los métodos de una clase y, adicionalmente, nos permitirá apilarlas entre si. Apilemos modificaciones sobre una cola de números enteros. Dicha cola tendrá dos operaciones: put, que añadirá números a la cola, y get que los sacará de la misma. Generalmente las colas siguen el comportamiento "primero en entrar, primero en salir" por lo que el método get tendría que retornar los elementos en el mismo orden en el que fueron introducidos. 17
  • 22. Fundamentos de Scala Dada una clase que implementa el comportamiento descrito en el párrafo anterior podríamos definir un trait que llevara a cabo modificaciones como: • Multiplicar por dos todos cualquier elemento que se añada en la cola. • Incrementar en una unidad cada uno de los elementos que se añaden en la cola. • Filtrado de elementos negativos. Evita que cualquier número menor que cero sea añadido a la cola. Los tres traits anteriores representan modificaciones dado que no definen una cola por si mismos sino que llevan a cabo modificaciones sobre la cola subyacente con la que realizan el proceso de mixin. Los traits también son apilables: podríamos escoger cualquier subconjunto de los tres anteriores e incorporarlos a una clase de manera que conseguiríamos una nueva clase con la funcionalidad deseada. El siguiente fragmento de código representa una implementación reducida del comportamiento de una cola descrito en el inicio de esta sección: import scala.collection.mutable.ArrayBuffer abstract class IntQueue { def get(): Int def put(x: Int) } class BasicIntQueue extends IntQueue { private val buf = new ArrayBuffer[Int] def get() = buf.remove(0) def put(x: Int) { buf += x } } Realicemos ahora un conjunto de modificaciones sobre la clase anterior; para ello, vamos a hacer uso de los traits. El siguiente fragmento de código muestra un trait que duplica el valor de un elemento que se desea añadir a la cola: trait Duplication extends IntQueue{ abstract override def put(x:Int) { super.put(2*x) } } Nótese el uso de las palabras reservadas abstract override. Esta combinación de modificadores sólo puede ser utilizada en los traits y no en las clases, e indica que el trait debe ser integrado (mixed) con una clase que presenta una implementación concreta del método en cuestión. A continuación se muestra un ejemplo de uso del trait anterior: scala> class MyQueue extends BasicIntQueue with Doubling defined class MyQueue scala> val queue = new MyQueue queue: MyQueue = MyQueue@91f017 18
  • 23. Fundamentos de Scala scala> queue.put(10) scala> queue.get() res12: Int = 20 Para analizar el mecanismo de apilado de modificaciones implementemos en primer lugar los dos traits restantes que hemos descrito al inicio de esta sección: trait Increment extends IntQueue{ abstract override def put(x:Int) { super.put(x + 1) } } trait Filter extends IntQueue{ abstract override def put(x:Int) { if ( x >= 0 ) super.put(x) } } Una vez tenemos disponibles las modificaciones podríamos generar una nueva cola del modo que más nos interese: scala> val queue = (new BasicIntQueue with Increment with Filter) queue: BasicIntQueue with Increment with Filter... scala> queue.put(-1); queue.put(0); queue.put(1) scala> queue.get() res15: Int = 1 scala> queue.get() res16: Int = 2 El orden de los mixins es importante . De manera resumida, cuando invocamos a un método de una clase con mixins el método del trait definido más a la derecha es el primero en ser invocado. Si dicho método invoca a super este invocará al trait que se encuentra más a la izquierda y así sucesivamente. En el ejemplo anterior, el método put del trait Filter será invocado en primer lugar, por lo que aquellos números menores que cero no serán incorporados a la cola. El método put del trait Filter sumará el valor uno a cada uno de los números (mayores o iguales que cero). 2.7.4. Traits: ¿si o no? A continuación se presentan una serie de criterios más o menos objetivos que pretenden ayudar al lector a determinar cuando debería usar estas construcciones proporcionadas por el lenguaje Scala: • Si el comportamiento no pretende ser reutilizado entonces encapsularlo en una clase. • Si el comportamiento pretende ser reutilizado en múltiples clases no relacionadas entonces construir un trait. 19
  • 24. Fundamentos de Scala • Si se desee que una clase Java herede de nuestra funcionalidad entonces deberemos utilizar una clase abstracta. • Si la eficiencia es importante deberíamos inclinarnos hacia el uso de las clases. La mayoría de los entornos de ejecución Java hacen una llamada a un método virtual de una clase mucho más rápido que la invocación de un método de un interfaz. Los traits son compilados a interfaces y podríamos penalizar el rendimiento. • Si tras todas las opciones anteriores no tenemos claro qué aproximación deseamos utilizar deberíamos comenzar por el uso de traits. Lo podremos cambiar en el futuro y, generalmente, mantedremos más opciones abiertas. 2.8. Patrones y clases case Aquellos lectores que hayan progamado en algún lenguaje perteneciente al paradigma funcional reconocerán el uso de la concordancia de patrones. Las clases case son un concepto relativamente novedoso y nos permiten incorporar el mecanismo de matching de patrones sobre objetos sin la necesidad de código repetitivo. De manera general, no tendremos más que prefijar la definición de una clase con la palabra reservada case para indicar que la clase definida pueda ser utilizada en la definición de patrones. A lo largo de esta sección analizaremos los dos conceptos anteriores en conjunción con un conjunto de ejemplos con el objetivo de ilustrar y amenizar la lectura de esta sección. 2.8.1. Clases case El uso del modificador case provoca que el compilador de Scala incorpore una serie de facilidades a la clase indicada. En primer lugar incorpora un factory-method con el nombre de la clase. Gracias a esto podríamos escribir código como Foo("x") para construir un objeto Foo en lugar de new Foo("x"). Una de las principales ventajas de este tipo de métodos es la ausencia de operadores new cuando los anidamos: val op = BinaryOperation("+", Number(1), v) Otra funcionalidad sintáctica incorporada por el compilador es que todos los argumentos en la lista de parámetros incorporan de manera implicita el prefijo val por lo que éstos últimos serán atributos de clase. Por último, pero no por ello no menos importante, el compilador añade implementaciones "instintivas" de los métodos toString, hashCode e equals. Todas estas facilidades incorporadas acarrean un pequeño coste: las clases y objetos generados son un poco más grandes4 y tenemos que incorporar la palabra case en las definiciones de nuestras clases. La principal ventaja de este tipo de clases es que soportan la concordancia de patrones. 4 Son más grandes porque se generan métodos adicionales y se incorporan atributos implícitos para cada uno de los parámetros del constructor 20
  • 25. Fundamentos de Scala 2.8.2. Patrones: estructura y tipos La estructura general de un patrón en Scala presenta la siguiente estructura: selector match { alternatives } Incorporan un conjunto de alternativas en las que cada una de ellas comienza por la palabra reservada case. Cada una de estas alternativas incorpora un patrón y una o más expresiones que serán evaluadas en caso de que se produzca la concordancia del patrón. Se utiliza el símbolo de flecha (=>) para separar el patrón de las expresiones. Como hemos visto al comienzo de esta sección la sintaxis de los patrones es sumamente sencilla por lo que vamos a profundizar en los diferentes tipos de patrones que podemos construir. 2.8.2.1. Patrones wildcard El patrón (_) concuerda con cualquier objeto por lo que podríamos utilizarlo como una alternativa catch-all tal y como se muestra en el siguiente ejemplo: expression match { case BinaryOperation(op,leftSide,rightSide) => println(expression + " is a BinaryOperation") case _ => } 2.8.2.2. Patrones constantes Un patrón constante concuerda única y exclusivamente consigo mismo. El siguiente fragmento de código muestra algunos ejemplos de patrones constantes: def describe(x: Any) = x match { case 5 => "five" case true => "truth" case "hello" => "hi!" case Nil => "this is an empty list" case _ => "anything else" } 2.8.2.3. Patrones variables Un patrón variable concuerda con cualquier objeto, del mismo modo que los patrones wildcard. A diferencia de los patrones wildcard, Scala enlaza la variable al objeto, por lo que posteriormente podremos utilizar dicha variable para actuar sobre el objeto: expr match { 21
  • 26. Fundamentos de Scala case 0 => "zero value" case somethingElse => "not zero: "+ somethingElse + " value" } 2.8.2.4. Patrones constructores Son en este tipo de construcciones donde los patrones se convierten en una herramienta muy poderosa. Básicamente están formados por un nombre y un número indefinido de patrones. Asumiendo que el nombre designa una clase de tipo case este tipo de patrones comprobarán primero si el objeto pertenece a dicha clase, para, posteriormente comprobar si los parámetros del constructor concuerdan con el conjunto de patrones extra indicados. La definición anterior puede no resultar demasiado explicativa por lo que a continuación se incluye un pequeño ejemplo en el que se comprueba que el objeto de primer nivel es de tipo BinaryOperation y que su tercer argumento es de tipo Number y su atributo de clase vale 0: expr match { case BinaryOperation("+", e, Number(0)) => println("a deep match") case _ => } 2.8.2.5. Patrones de secuencia Podemos establecer patrones de concordancia sobre listas o arrays del mismo modo que lo hacemos para las clases. Deberá utilizarse la misma sintáxis aunque ahora podremos indicar cualquier número de elementos en el patrón. El siguiente fragmento de código muestra un patrón que comprueba una lista de tres elementos cuyo primer valor toma 0: expr match { case List(0, _, _) => println("found it") case _ => } 2.8.2.6. Patrones tipados Podemos utilizar este tipo de construcciones como reemplazo de las comprobaciones y conversiones de tipos: def genericSize(x: Any) = x match { case s: String => s.length case m: Map[_, _] => m.size case _ => -1 } 22
  • 27. Fundamentos de Scala El método genericSize retorna la longitud de un objeto cualquiera. El patrón "s:String" es un patrón tipado: cualquier instancia no nula de tipo String concordará con dicho patrón. La variable de patrón s hará referencia a dicha cadena. 2.9. Conclusiones A lo largo de este capítulo hemos analizado varias de las características principales del lenguaje de programación Scala, haciendo especial hincapié en como el lenguaje incorpora funcionalidades provenientes de los paradigmas funcional y orientado a objetos. Las secciones anteriores nos permitirán comenzar a escribir nuestros primeros programas en Scala aunque nos faltaría un largo camino para convertirnos en unos expertos en la materia. Para el lector más interesado a continuación se indican algunos conceptos en los que se podría profundizar: • Interoperabilidad entre Scala y Java. • Parametrización de tipos. • Extractors. • Trabajar con XML. • Inferencia de tipos • ... En la siguiente parte del trabajo analizaremos el modelo de actores propuesto por Scala y cómo esta aproximación nos permitirá construir aplicaciones concurrentes de manera más sencilla. 23
  • 28. Actors y concurrencia Capítulo 3. Actors y concurrencia En muchas ocasiones cuando estamos escribiendo nuestros programas necesitamos que muchas de sus partes se ejecuten de manera independiente, es decir, de manera concurrente. Aunque el soporte introducido por Java es suficiente, a medida que la complejidad y tamaño de los programas se incrementan, conseguir que nuestro código concurrente funcione de manera correcta se convierte en una tarea complicada. Los actores incluyen un modelo de concurrencia más sencillo con el que podremos evitar la problemática habitual ocasionada por el modelo de concurrencia nativo de Java. Durante este capítulo presentaremos los fundamentos del modelo de actores y como podremos utilizar la librería de actores facilitada Scala. 3.1. Problemática La plataforma Java proporciona de manera nativa un modelo de hilos basado en bloqueos y memoria compartida. Cada uno de los objetos lleva asociado un monitor que puede ser utilizado para realizar el control de acceso de múltiples hilos sobre los datos. Para utilizar este modelo debemos decidir que datos queremos compartir entre múltiples hilos y establecer como synchronized aquellas secciones de código que acceden a segmentos de datos compartidos. En tiempo de ejecución, Java utiliza un mecanismo de bloqueo con el que se garantiza que en un instante de tiempo T un único hilo accede a una sección sincronizada S. Los desarrolladores han encontrado numerosos problemas para construir aplicaciones robustas con esta aproximación, especialmente cuando los problemas crecen en tamaño y complejidad. En cada punto del programa el desarrollador debe razonar sobre los datos modificados y/o accedidos que pueden ser accedidos por otros hilos y establecer los bloqueos necesarios. Añadiendo un nuevo inconveniente a los descritos en el párrafo anterior, los procesos de prueba no son fiables cuando tratamos con códigos multihilo. Dado que los hilos son no deterministas, podríamos probar nuestra aplicaciones miles de veces de manera satisfactoria y obtener un error la primera vez que se ejecuta el mismo código en la máquina de nuestro cliente. La aproximación seguida por Scala1 para resolver el problema de la concurrencia ofrece un modelo alternativo de no compartición y paso de mensajes. En la siguiente sección se ofrecerá una definición resumida del modelo de actores. 1 Realmente Scala ofrece el modelo de actores mediante una librería del lenguaje en lugar de manera nativa 24
  • 29. Actors y concurrencia 3.2. Modelo de actores El modelo de actores ofrece una solución diferente al problema de la concurrencia. En lugar de procesos interactuando mediante memoria compartida, el modelo de actores ofrece una solución basada en buzones y paso de mensajes asíncronos. En estos buzones los mensajes pueden ser almacenados y recuperados para su procesamiento por otros actores. En lugar de compartir variables en memoria, el uso de estos buzones nos permite aislar cada uno de los procesos. Los actores son entidades independientes que no comparten ningún tipo de memoria para llevar a cabo el proceso de comunicación. De hecho, los actores únicamente se pueden comunicar a través de los buzones descritos en el párrafo anterior. En esta aproximación de concurrencia no existen bloqueos ni secciones sincronizadas por lo que los problemas derivados de las mismas (deadlocks, pérdida de actualizaciones de datos, . . .) no existen en este modelo. Los actores están pensados para trabajar de manera concurrente, no de modo secuencial. El modelo de actores no es una novedad: Erlang basa su modelo de concurrencia en actores en lugar de hilos. De hecho, la popularidad alcanzada por Erlang en determinados ámbitos empresariales han hecho que la popularidad del modelo de actores haya crecido de manera notable y lo ha convertido en una opción viable para otros lenguajes. 3.3. Actores en Scala Para implementar un actor en Scala no tenemos más que extender scala.actors.Actor e implementar el método act. El siguiente fragmento de código ilustra un actor sumamente simple que no realiza nada con su buzón: import scala.actors._ object FooActor extends Actor{ def act(){ for(i <- 1 to 11){ println("Executing actor!") Thread.sleep(1000) } } } Si deseamos ejecutar un actor no tenemos más que invocar a su método start() scala> FooActor.start() res0: scala.actors.Actor = FooActor$@681070 Otro mecanismo diferente que nos permitiría instanciar un actor sería hacer uso del método de utilidad actor disponible en scala.actors.Actor: scala> import scala.actors.Actor._ 25
  • 30. Actors y concurrencia scala> val otherActor = actor { for (i <- 1 to 11) println("This is other actor.") Thread.sleep(1000) } Hasta el momento hemos visto como podemos crear un actor y ejecutarlo de manera independiente pero, ¿cómo conseguimos que dos actores trabajen de manera conjunta? Tal y como hemos descrito en la sección anterior, los actores se comunican mediante el paso de mensajes. Para enviar un mensaje haremos uso del operador !. Definamos ahora un actor que haga uso de su buzón, esperando por un mensaje e imprimiendo aquello que ha recibido: val echoActor = actor { while (true) { receive { case msg => println ("Received message " + msg) } } } Cuando un actor envía un mensaje no se bloquea y cuando lo recibe no es interrumpido. El mensaje enviado queda a la espera en el buzón del receptor hasta que este último ejecute la instrucción receive. El siguiente fragmento de código ilustra el comportamiento descrito: scala> echoActor ! "My First Message" Received message My First Message scala> echoActor ! 11 Received message 11 3.3.1. Buenas prácticas Llegados a este punto conocemos los fundamentos básicos para escribir nuestros propios actores. El punto fuerte de los métodos vistos hasta este momento es que ofrecen un modelo de programación concurrente basado en actores por lo que, en la medida que podamos escribir siguiendo este estilo nuestro código será más sencillo de depurar y tendrá menos deadlocks y condiciones de carrera. Las siguientes secciones describen,de manera breve, algunas directrices que nos permitirán adopotar un estilo de programación basado en actores. 3.3.1.1. Ausencia de bloqueos Un actor no debería bloquearse mientras se encuentra procesando un mensaje. El problema radica en que mientras un actor se bloquea, otro actor podría realizar una petición sobre 26
  • 31. Actors y concurrencia el primero. Si el actor se bloquea en la primera petición no se dará cuenta de una segunda solicitud. En el peor de los casos, se podría producir un deadlock en el que varios actores están esperando por otros actores que a su vez están bloqueados. En lugar de bloquearse, el actor debería esperar la llegada de un mensaje indicando que la acción está lista para ser ejecutada. Esta nueva disposición, por norma general, implicará la participación de otros actores. 3.3.1.2. Comunicación exclusiva mediante mensajes La clave de los actores es el modelo de no compartición, ofreciendo un espacio seguro (el método act de cada actor) en el que podríamos razonar de manera secuencial. Expresándolo de manera diferente, los actores nos permiten escribir programas multihilo como un conjunto independiente de programas monohilo. La simplificación anterior se cumple siempre y cuando el único mecanismo de comunicación entre actores sea el paso de mensajes. 3.3.1.3. Mensajes inmutables Puesto que el modelo de actores provee un entorno monohilo dentro de cada método act no debemos preocuparnos si los objetos que utilizamos dentro de la implementación de dicho método son thread-safe. Este es el motivo por el que el modelo de actores es llamado shared- nothing, los datos están confinados en un único hilo en lugar de ser compartidos por varios. La excepción a esta regla reside en la información de los mensajes intercambiados entre actores dado que es compartida por varios de ellos. Por tanto, tendremos que preocuparnos de que los mensajes intercambiados entre actores sean thread-safe. 3.3.1.4. Mensajes autocontenidos Cuando retornamos un valor al finalizar la ejecución de un método el fragmento de código que realiza la llamada se encuentra en una posición idónea para recordar lo que estaba haciendo anteriormente a la ejecución del método, recoger el resultado y actuar en consecuencia. Sin embargo, en el modelo de actores las cosas se vuelven un poco más complicadas. Cuando un actor realiza una petición a otro actor el primero de ellos no es consciente del tiempo que tardará la respuesta, instantes en los que dicho actor no debería bloquearse, sino que debería continuar ejecutando otro trabajo hasta que la respuesta a su petición le sea enviada. ¿Puede el actor recordar qué estaba haciendo en el momento en el que envió la petición inicial? Podríamos adoptar dos soluciones para intetar resolver el problema planteado en el párrafo anterior: 27
  • 32. Actors y concurrencia • Un mecanismo para simplificar la lógica de los actores sería incluir información redundante en los mensajes. Si la petición es un objeto inmutable, el coste de incluir una referencia a la solicitud en el valor de retorno no sería costoso. • Otro mecanismo adicional que nos permitiría incrementar la redundancia en los mensajes sería la utilización de una clase diferente para cada uno de las clases de mensajes que dispongamos 3.4. Un ejemplo completo Como ejemplo completo de aplicación del modelo de actores descrito a lo largo de las secciones anteriores y con el objetivo de asentar y poner en marcha los conocimientos adquiridos vamos a construir, paso a paso, una pequeña aplicación de ejemplo. 3.4.1. Especificación del problema Durante la construcción de este ejemplo desarrollaremos una abstraccción de producers la cual ofrecerá una interfaz de iterador estándar para la recuperación de una secuencia de valores. La definición de un producer específico se realiza mediante la implementación del método abstracto produceValues. Los valores individuales son generados utilizados el método produce. Por ejemplo, un producer que genera en preorden los valores contenidos en un árbol podría ser definido como: class TraversePreorder(n:Tree) extends Producer[int]{ def produceValues = traverse(n) def traverse(n:Tree){ if( n!= null){ produce(n.elem) traverse(n.left) traverse(n.right) } } } 3.4.2. Implementación del producer y coordinator La abstracción producer se implementa en base a dos actores: un actor productor y un actor coordinador. El siguiente fragmento de código ilustra cómo podríamos llevar a cabo la definición del actor productor: abstract class Producer[T] { 28
  • 33. Actors y concurrencia protected def produceValues: unit protected def produce(x: T) { coordinator ! Some(x) receive { case Next => } } private val producer: Actor = actor { receive { case Next => produceValues coordinator ! None } } ... } ¿Cuál es el mecanismo de funcionamiento del actor productor anterior? Cuando un actor productor recibe el mensaje Next éste ejecuta el método (abstracto) produceValues, que desencadena la ejecución del método produce. Ésta ultima ejecución provoca el envío de una serie de valores (recubiertos por el message Some) al coordinador. El coordinador es el responsable de sincronizar las peticiones de los clientes y los valores provenientes del productor. Una posible implementación del actor coordinador se ilustra en el siguiente fragmento de código: private val coordinator:Actor = actor { loop { react { case Next => producer ! Next reply{ receive {case x: Option[_] => x} } case Stop => exit('stop) } } } 3.4.3. Interfaz iterator Nuestro objetivo es que los producers se puedan utilizar del mismo modo en que utilizamos los iteradores tradicionales. Nótese como los métodos hasNext y next envían sendos mensajes al actor coordinador para llevar a cabo su tarea: def iterator = new Iterator[T] { private var current: Any = Undefined 29
  • 34. Actors y concurrencia private def lookAhead = { if(current == Undefined) current = coordinator !? Next current } def hasNext: boolean = lookAhead match { case Some(x) => true case None => { coordinator ! Stop; false} } def next:T = lookAhead match{ case Some(x) => current = Undefined; x.asInstanceOf[T] } } Centremos nuestra atención en el método lookAhead. Cuando el atributo current tenga un valor indefinido significa que tendremos que recuperar el siguiente valor. Para llevar a cabo dicha tarea se hace uso del operador de envío de mensajes síncronos !? lo cual implica que se queda a la espera de la respuesta del coordinador. Los mensajes enviados mediante el operador !? debe ser respondidos mediante el uso de reply. En el apéndice Código fuente producers se puede ver código completo del ejemplo descrito durante esta sección. 30
  • 35. Conclusiones y trabajo futuro Capítulo 4. Conclusiones y trabajo futuro Como punto final de este trabajo haremos un breve resumen de los conceptos analizados durante todo el trabajo y plantearemos futuras líneas de trabajo que podrían resultar atractivas para aquellas personas interesadas en esta temática. 4.1. Conclusiones Durante el desarrollo de este trabajo hemos vistos algunas de las principales características del lenguaje Scala, haciendo espacial hincapié en cómo se integran las capacidades del paradigma de orientación a objetos y el funcional. Asimismo hemos analizado el modelo de concurrencia basado en actores y cómo Scala ofrece dicho modelo de computación mediante una librería que complementa al lenguaje. Como ya hemos indicado al inicio del trabajo Scala es el primer lenguaje de propósito general que integra conceptos del paradigma funcional y el orientado a objetos. Muchos de los lectores se estarán preguntando cuál es el ámbito de aplicación del lenguaje. La respuesta corta y concisa podría ser: en todos aquellos lugares donde utilizamos Java. Entrando un poco más en detalle en las posibles escenarios de aplicación a continuación se indican algunos de los posibles usos: • Lenguaje de parte servidora. • Escritura de scripts • Desarrollo de aplicaciones robustas, escalables y fiables. • Desarrollo de aplicaciones web. • Construcción de lenguajes de dominio específico (DSL) • ... 31
  • 36. Conclusiones y trabajo futuro 4.2. Líneas de trabajo Por motivos de espacio y, principalmente, de tiempo se han quedado numerosos temas en el tintero que podrían resultar interesantes. A continuación se listan y describen, de manera sumamente breve, algunas sugerencias consideradas interesantes: • Análisis de la plataforma Akka [http://akka.io] . Plataforma basada en Scala que ofrece un modelo de actores junto con Sofware Transactional Memory con el objetivo de proporcionar los fundamentos correctos para la construcción de aplicaciones escalables y concurrentes. Una comparación entre el modelo de actores ofrecido por Scala y el modelo de actores ofrecido por Akka podría resultar atractiva. • Web funcional. Análisis de cómo las características de los lenguajes funcionales se pueden utilizar para construir aplicaciones web. Análisis de frameworks web funcionales como Lift [http://www.liftweb.net/] o Play [http://www.playframework.org/] • Análisis de interoperabilidad de Scala y Java. Mecanismos y modos de interacción entre ambos lenguajes. Problemática habitual. • Scala en .NET. • Colecciones. Análisis del nuevo API de colecciones diseñado a partir de Scala 2.8. Análisis de colecciones concurrentes. • GUI en Scala. Análisis de cómo podemos utilizar Scala para la construcción de aplicaciones de escritorio. • Monads en Scala. • Arquitectura del compilador de Scala. • Lenguajes de dominio específicos (DSLs). Construcción de lenguajes de dominio específicos basados en el lenguaje Scala. Combinator parsing. 32
  • 37. Apéndice A. Modelo de objetos de Scala A continuación, a modo de resumen, se incluye un diagrama en el que se refleja la jerarquía de clases presentes en Scala: 33
  • 38. Apéndice B. Producers El siguiente código fuente contiene la definición del interfaz producer analizado en Ejemplo completo de actores B.1. Código fuente completo package com.blogspot.miguelinlas3.phd.paragprog.actors import scala.actors.Actor import scala.actors.Actor._ abstract class Producer[T] { /** Mensaje indicando que el siguiente valor debe de computarse. */ private val Next = new Object /** Estado indefinido del iterator. */ private val Undefined = new Object /** Mensaje para detener al coordinador. */ private val Stop = new Object protected def produce(x: T) { coordinator ! Some(x) receive { case Next => } } protected def produceValues: Unit def iterator = new Iterator[T] { private var current: Any = Undefined private def lookAhead = { if (current == Undefined) current = coordinator !? Next current } def hasNext: Boolean = lookAhead match { case Some(x) => true 34
  • 39. case None => { coordinator ! Stop; false } } def next: T = lookAhead match { case Some(x) => current = Undefined; x.asInstanceOf[T] } } private val coordinator: Actor = actor { loop { react { case Next => producer ! Next reply { receive { case x: Option[_] => x } } case Stop => exit('stop) } } } private val producer: Actor = actor { receive { case Next => produceValues coordinator ! None } } } object producers extends Application { class Tree(val left: Tree, val elem: Int, val right: Tree) def node(left: Tree, elem: Int, right: Tree): Tree = new Tree(left, elem, right) def node(elem: Int): Tree = node(null, elem, null) def tree = node(node(node(3), 4, node(6)), 8, node(node(9), 10, node(11))) class PreOrder(n: Tree) extends Producer[Int] { def produceValues = traverse(n) def traverse(n: Tree) { if (n != null) { produce(n.elem) traverse(n.left) traverse(n.right) } 35
  • 40. } } class PostOrder(n: Tree) extends Producer[Int] { def produceValues = traverse(n) def traverse(n: Tree) { if (n != null) { traverse(n.left) traverse(n.right) produce(n.elem) } } } class InOrder(n: Tree) extends Producer[Int] { def produceValues = traverse(n) def traverse(n: Tree) { if (n != null) { traverse(n.left) produce(n.elem) traverse(n.right) } } } actor { print("PreOrder:") for (x <- new PreOrder(tree).iterator) print(" "+x) print("nPostOrder:") for (x <- new PostOrder(tree).iterator) print(" "+x) print("nInOrder:") for (x <- new InOrder(tree).iterator) print(" "+x) print("n") } } 36
  • 41. Bibliografía Scala in Action. Nilanjan Raychaudhuri. Early Access started on March 2010. Scala in Depth. Joshua D. Suereth. Early Access started on September 2010. Programming in Scala, Second Edition. Martin Odersky. Lex Spoon. Bill Venners. December 13, 2010. Programming Scala: Tackle Multi-Core Complexity on the Java Virtual Machine. Venkat Subramaniam. Jul 2009. ACTORS: A Model of Concurrent Computation in Distributed Systems. Agha, Gul Abdulnabi. 1985-06-01. Martin Odersky interview: the future of Scala. http://www.infoq.com/interviews/martin-odersky- scala-future. Akka framework. http://akka.io/. Play framework. http://www.playframework.org/. Lift framework. http://www.liftweb.net/. Haskell language. http://www.haskell.org/haskellwiki/Haskell. Scalaz: pure functional data structures. http://code.google.com/p/scalaz/. 37