Scala: Parte 2

Los primeros pasos en Scala
Como había comentado en mi post anterior sobre el lenguaje Scala, mi pretensión es contribuir "paso a paso" con algunas de las cosas importantes de este lenguaje, discutiendo conceptos relevantes y algo de código para ilustrar esos conceptos.
Creo que es necesario aprender a caminar con pasos firmes para después correr, y por esa razón me voy despacio explicando cada cosa en su momento.
Casi todos los libros y tutoriales de Scala aconsejan comenzar con el intérprete de Scala y "oyendo" a los que saben voy a hacer lo mismo, aunque también me gustaría "saltar del REPL a programas completos. Esa forma me ha ayudado a entender el lenguaje, aunque debo confesar que aun me falta mucho por aprender y que bueno que sea así pues evito el aburrimiento.
El REPL (Read Eval Print Loop) es un shell interactivo que compila código de Scala y retorna el resultado (o el tipo) de manera inmediata. Espero que todos los que leen esto ya tengan en su máquina a Scala y puedan interactuar con el intérprete a medida que leen esta notas.
Definamos algunas variables en el REPL:

scala>val mensaje = "Hola Java Mexico"

El intérprete me responde:

mensaje: java.lang.String = Hola Java Mexico

EL REPL es muy verboso y me indica que definí una variable y el tipo usado. "Hola Java Mexico" es un literal String y notamos que Scala utiliza la clase java.lang.String para los tipos cadena de caracteres.
Esa línea simple ilustra algunos conceptos simples pero importantes del lenguaje entre ellos los siguientes:
La sintaxis de Scala es más regular que la sintaxis de Java en el sentido que todas las definiciones comienzan con una palabra reservada. Las variables se definen con var y val y las funciones se definen con def. El uso de def también lo encontraremos prefijando a los parámetros de funciones cuando deseamos utilizar "call by name". Eso lo veremos en otro momento.
Scala es un lenguaje de tipado estático con inferencia de tipos. El código

scala>val mensaje = "Hola Java Mexico"

no especifica el tipo de mensaje, porque el lenguaje puede inferirlo del tipo de resultado de la expresión de la parte derecha de la asignación. Podríamos por supuesto ser muy explícitos y escribir:

scala>val mensaje:String = "Hola Java Mexico"

y el REPL me devuelve

mensaje: String = Hola Java Mexico

Observen que ahora el REPL especifica el tipo con su nombre parcial: no usa el nombre totalmente calificado de la clase String.
El REPL acepta una nueva redefinición de la variable porque comienza un nuevo ámbito (scope). En un programa normal esa nueva línea resultaría en un error de compilación.
Escribimos ahora:

scala>mensaje="Bye bye Java Mexico"

y el REPL nos dice:

<console>:6: error: reassignment to val
                   mensaje = "Bye bye Java Mexico"

Scala tiene dos tipos de variables, vals y vars. Una variable definida con val asocia un identificador con un valor inmutable. En ocasiones se dice que la variable definida así es inmutable. Si se desea pensar en términos de la programación funcional, es mejor decir que es el valor quien es inmutable y por tanto el identificador que usamos para referirnos a él no puede ser cambiado. No es un pecado decir que un val es similar a una variable final en Java.
Una variable definida con var es una variable en realidad y el valor que identifica puede cambiar durante la ejecución del programa. El código siguiente es válido:

scala> var mensaje:String= "Hola Java Mexico"
mensaje: String = Hola Java Mexico

scala> mensaje ="Bye bye Java Mexico"
mensaje: String = Bye bye Java Mexico

scala>

Podemos aplicar métodos de la clase String sobre un literal cadena, de la misma forma que hacemos en Java:

scala> "Hola Java Mexico".indexOf('M')
res3: Int = 10

scala>

O haciendo uso de la sintaxis de "operadores" infijos que Scala brinda:

scala> "Hola Java Mexico"  indexOf   'M'
res4: Int = 10

scala>

Conversiones implícitas
¿Qué tal el código siguiente?

scala> "Hola Java Mexico" reverse
res5: String = ocixeM avaJ aloH

scala>

El método reverse no está definido para la clase String, por lo que algo más está interviniendo aquí, para que lo anterior no produzca un error de compilación.
Ese algo más son las conversiones implícitas en Scala: una de las soluciones que este lenguaje ofrece para "extender" la funcionalidad de una clase que no nos pertenece y que no tenemos la opción de extenderla por las vías "normales" de la herencia y la composición.
Como todos conocen, hay una diferencia fundamental entre nuestro código (clases y otras cosas que diseñamos) y el código que usamos de bibliotecas y otros diseñadores. Con nuestro código podemos hacer lo que queramos, mientras con el código de terceros no nos queda otra que usarlo tal cual.
En ocasiones necesitamos extender ese código de terceros. Varios lenguajes brindan varias técnicas para eso. Ruby ofrece el mecanismo de módulos, C# ofrece los métodos de extensión estáticos, etc.
Scala por su parte introduce la técnica de parámetros y conversiones implícitas, con las cuales se le brinda información adicional al compilador para poder usar esas conversiones ante la presencia de errores de compilación debidos fundamentalmente a compatibilidad entre tipos.
¿Qué hace el compilador cuando encuentra la línea:

scala> "Hola Java Mexico" reverse

Pues lo primero que hace es percatarse de que hay un error de compilación. El método reverse no es un método de la clase String. Inmediatamente que esto sucede, el compilador trata de buscar alguna función de conversión implícita que solucione este problema y que por supuesto esté en ámbito (scope). Esa función tendrá que lograr "convertir" el literal String, en un objeto de alguna clase que tenga el método reverse y que produzca un nuevo objeto de tipo String con el resultado esperado.
En el ámbito de todo programa en Scala se dispone de un objeto (¿un objeto o una clase?) denominado objeto Predef:
object Predef extends LowPriorityImplicits { ...
que dispone del método de conversión implícita siguiente:

implicit def augmentString(x: String): StringOps = new StringOps(x)

El compilador se encarga también de "investigar" la clase StringOps, y comprueba que esa clase ofrece el método reverse que invierte una "cadena". El compilador aplica "transparentemente" la conversión de String a StringOps, invoca el método reverse sobre el objeto de tipo StringOps y nos da como resultado la cadena invertida de tipo String.
El código del objeto incluye otras muchas definiciones utilitarias que tienen como objetivo escribir códigos más compactos. Por ejemplo escribimos:

scala> println("Hola Java Mexico")
Hola Java Mexico

scala>

¿Cómo está definida println?
Está definida en el objeto Predef así como otras funciones de impresión y lectura comunes como:

def print(x: Any) = Console.print(x)
  def println() = Console.println()
  def println(x: Any) = Console.println(x)
  def printf(text: String, xs: Any*) = Console.print(format(text, xs: _*))
  def format(text: String, xs: Any*) = augmentString(text).format(xs: _*)

  def readLine(): String = Console.readLine()
  def readLine(text: String, args: Any*) = Console.readLine(text, args)
  def readBoolean() = Console.readBoolean()
  def readByte() = Console.readByte()
  def readShort() = Console.readShort()
  def readChar() = Console.readChar()
  def readInt() = Console.readInt()
  def readLong() = Console.readLong()
  def readFloat() = Console.readFloat()
  def readDouble() = Console.readDouble()
  def readf(format: String) = Console.readf(format)
  def readf1(format: String) = Console.readf1(format)
  def readf2(format: String) = Console.readf2(format)
  def readf3(format: String) = Console.readf3(format)

Definamos algunas funciones
La definición de una función en Scala comienza con la palabra reservada def, seguida por el nombre de la función, la lista o listas de parámetros (¿más de una lista?), el tipo de resultado, el signo igual y el bloque que define la función. Un ejemplo viene a continuación:

scala> def gcdLoop(x: Long, y: Long): Long = {
     |     var a = x
     |     var b = y
     |     while (a != 0) {
     |       val temp = a
     |       a = b % a
     |       b = temp
     |     }
     |     b
     |   }
gcdLoop: (x: Long,y: Long)Long

scala>

Usamos esa función:

scala> gcdLoop(24, 12)
res6: Long = 12

scala> gcdLoop(82,26)
res7: Long = 2

scala> gcdLoop(82,40)
res8: Long = 2

scala>

Scala no puede inferir los tipos de sus parámetros, por lo que es obligada la anotación de tipos de los parámetros.
Las funciones en Scala no requieren en la mayoría de los casos la sentencia return. El resultado devuelto es el valor de la última expresión.
Scala utiliza el signo igual para enfatizar el concepto de función: el cuerpo de la función calcula algo y devuelve un resultado. Cuando se omite el signo igual nos referimos a un procedimiento destinado mayormente a producir efectos laterales o a realizar operaciones de I/O.
Scala puede inferir el tipo de resultado de una función: en este caso puede no escribirse el tipo de resultado de la función gcdLoop(Long, Long). Puede inferirse del código.
Por ejemplo, ¿cuál es el tipo de retorno de la función siguiente?:

scala> def IntegerOrString(in:Boolean)=if (in) 5 else "5"

Si el argumento es verdadero, el tipo de retorno es Int (se infiere del literal entero 5). Si el argumento es falso el tipo de retorno es String (se infiere del literal cadena "5").
El tipo de retorno entonces debe ser un tipo que sea base de los tipos valores y de los tipos referencias: el tipo Any.

scala> def IntegerOrString(in:Boolean)=if (in) 5 else "5"
IntegerOrString: (in: Boolean) Any

scala>

Los dos ejemplos anteriores tienen cosas interesantes con respecto a la distinción entre expresiones y sentencias (statements). Noten que para el caso de la sentencia (statement)while tuvimos la necesidad de crear una variable temporal mutable para devolver el resultado, mientras que la estructura de control if es una expresión y se evalúa a un valor, sin la necesidad de hacer uso de variables mutables temporales.
La programación funcional tiene entre sus objetivos evitar los efectos laterales, la modificación del estado y hacer uso "exclusivamente" de expresiones que regresan un valor en el momento de ejecutarse. Que if es una expresión lo podemos demostrar en el REPL:

scala> (if(true) 6 else "5").+("9")
res11: java.lang.String = 69
scala>

¿Por qué el siguiente código no compila?

scala> (if(true) 6 else "5").+(9)
<console>:6: error: type mismatch;
 found   : Int(9)
 required: String
       (if(true) 6 else "5").+(9)
                               ^

scala>

Hoy es domingo y toca. Nos vemos en la próxima

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de bferro

Algo olvidé

Algo olvidé comentar. Lo dejo para la próxima

Según lo entiendo de la

Según lo entiendo de la lectura, no compila porque no va a haber forma de poder sumar un enero y Any, que es el tipo de retorno de la expresión: (if(true) 6 else "5" ) y el anterior sí funcionó porque Any+String da String

¿Correcto?

La conversión explicita es un mecanismo mucho muy poderoso. Mientras otros lenguajes de programación permiten agregar un método a un tipo de dato, Scala convierte un tipo de dato en otro.

Esto sumado a los operadores infijos que señala bferro podria darnos con que construir una literal falsa de fecha:

scala> implicit def literalDeFecha( dia : Int ) = new { //<-- convierte un entero en un nuevo objeto
     |     import java.util.Date
     |     def enero( año : Int ) = new Date( año, 0, dia )  // con la funcion "enero"
     |     def febrero( año : Int ) = new Date( año, 1 , dia ) // y la function "febrero"
     |    // etc.  que reciben un año y regresan una fecha...
     | }

literalDeFecha: (Int)java.lang.Object{def enero(Int): java.util.Date; def febrero(Int): java.util.Date}

Ejemplo:

scala> val diaDelaBandera = 24 febrero 11
diaDelaBandera: java.util.Date = Fri Feb 24 00:00:00 CST 1911

Ese ejemplo lo tomé de aquí: http://stackoverflow.com/questions/2861501/can-someone-explain-me-implic...

Lo anterior también se pudo haber escrito con este código un poquito menos mágico:

// Definir una clase a la "antigüita"
scala> class DateLiteral( dia: Int ) {
     |    // con los métodos: enero, febrero etc.  
     |    def enero ( año : Int ) = new java.util.Date( año, 0, dia )
     | }
defined class DateLiteral

// Definir el método de conversión, que transorma un "int" en un DateLiteral
scala> implicit def int2DateLiteral( dia : Int ) = new DateLiteral( dia )
int2DateLiteral: (Int)DateLiteral

Probar

scala> 6.enero( 10 )  // método "enero" no encontrado en "int" buscar la conversion e invocarlo :
res0: java.util.Date = Thu Jan 06 00:00:00 CST 1910

Siempre me había preguntado como estaría definido ese método "println" y ahora lo sé :)

Imagen de bferro

Buenos los ejemplos

Buenos los ejemplos de OscarRyz.

Efectivamente el tipo de la expresión (if(true) 6 else "5") es Any, la clase raíz de Scala y de la cual heredan las clases AnyVal para los tipos valores y AnyRef para los tipos referencias. Scala infiere ese tipo para la expresión atendiendo que la parte then es un Int que hereda de AnyVal y la parte else es un String que hereda de AnyRef (en Scala para la JVM, AnyRef equivale a java.lang.Object).
En la clase Any está definido el método toString(), que en el caso del ejemplo que compila se aplica al resultado de la expresión if para concatenerlo con el operando "9" del método (operador) +.

Podemos indicarle al compilador que el resultado es un Int con un "casting" usando el método asInstanceOf[T] definido en la clase Any.

scala> (if(true) 6 else "5").asInstanceOf[Int]
res13: Int = 6

scala>

El tipo de resultado ahora es Int (si el casting es incorrecto sucederá un error de runtime).
y entonces:

scala> (if(true) 6 else "5").asInstanceOf[Int] +9
res14: Int = 15

scala>

Al usar el método asInstanceOf[T]de la clase Any, hay que recordar siempre la semántica de borrado de los tipos genéricos. De la documentación de Scala:

5.asInstanceOf[String]

produce un error de run time de ClassCastException, pero:

List(1).asInstanceOf[List[String]]

no produce error.

Imagen de benek

Predef

Que interesante lo de Predef, no sabía de su existencia.

Ahora que lo mencionas (y como lo mencionas) sí causa cierta confusión conceptual el hecho de que un singleton sea nombrado 'object' siendo una clase todavía...

Muy buena continuación!!

Muy muy buena entrada

Muy muy buena entrada @bferro. De verdad que cada comentario y entrada de personas cómo usted dejan algo para los que estamos en pañales en esto de la programación en general ;), da gusto ver cómo hay gente que no pretende tener el conocimiento acumulado e inútil, en lugar de eso lo ponen en práctica y lo comparten.

De nuevo, muy buena entrada.

Imagen de bferro

Un ejemplo de conversión implícita "esclarecedor"

Oderky en su libro "Programming in Scala", comienza el capítulo de conversiones implícitas con un ejemplo muy adecuado para los que conocen Java y que seguramente alguna vez han trabajado con algún componente de Swing al que se le registra un manejador de eventos (listener). Reproduzco aquí ese ejemplo para contribuir al entendimiento de las conversiones implícitas.
Todos los componentes de Swing son despachadores de eventos. Las aplicaciones con Swing son dirigidas por eventos como sabemos.
Tomemos el caso de un botón que genera eventos de tipo ActionEvent y que deseamos procesar esos eventos con un manejador de eventos de tipo ActionListener. Escribimos en Scala ( a lo Java) el código siguiente:

val button = new JButton
button.addActionListener(
  new ActionListener {
    def actionPerformed(event: ActionEvent) {
      println("pressed!")
    }
  }  
)

Como bien comenta Odersky, esta porción de código tiene una buena porción de código "boilerplate" que puede ser inferido del contexto.
El mero hecho de que el manejador de eventos que suscribimos en el botón es un manejador de tipo ActionListener, implica que el método de callback tiene que a fuerzas llamarse actionPerformed y que el argumento pasado al método addActionListener tiene que ser de tipo ActionListener.
Es facíl notar que lo que se requiere pasar es una función ( en este caso un procedimiento) que tome como argumento un valor de tipo ActionEvent. Si Java manipulara a las funciones como valores de primera clase, seguramente habría usado esa técnica.
En Scala las funciones son valores de primera clase y podríamos usarla, pero tenemos el problema de que el método addActionListener espera como argumento un objeto de tipo ActionListener.
¿Qué podemos hacer entonces?
Pues hacer uso de las conversiones implícitas para convertir un tipo función en un tipo ActionListener y resuelto el problema.

implicit def function2ActionListener(f: ActionEvent => Unit) =
  new ActionListener {
    def actionPerformed(event: ActionEvent) = f(event)
  }

Y ahora, escribimos la función y la pasamos al método addActionListener. El compilador se encarga de la conversión con la función implícita disponible en el ámbito (scope):

button.addActionListener( (_: ActionEvent) => println("pressed!") )

Estamos usando un placeholder para indicar el argumento recibido. En alguna de las partes de esta serie explicaré bien el uso de esas "cosas"

No se trata solamente de poder escribir un código más sencillo; se trata de expresar la semántica con más claridad

BaySick

Encontré otro ejemplo de uso de implicits para hacer un internal DSL de BASIC en Scala, llamado BaySick

Ejemplo

object SquareRoot extends Baysick {
  def main(args:Array[String]) = {
    10 PRINT "Enter a number"
    20 INPUT 'n
    30 PRINT "Square root of " % "'
n is " % SQRT('n)
    40 END

    RUN
  }
}

https://github.com/fogus/baysick