HolaMundo (en Scala)

Hace tiempo que quiero aprender Scala, pero no he tenido oportunidad de hacerlo. Hoy me decidí a aprender aunque sea un poquitititito, y me topé con una buena idea para hacer mi primer programa: Un servidorsito muy simple, que implemente el protocolo "hola mundo".

Hay que poner un servidor a escuchar conexiones TCP en un puerto; cada conexión que se recibe, debe leer un renglón de texto (se espera un nombre) y devolver un saludo a ese nombre, para después cerrar la conexión.

Tomé la idea de un ejemplo que puse como comentario en un foro aquí mismo. Así puedo comparar...

Pues bien. Primero que nada, la clase que implementa el protocolo, utilizando streams. Lo primero que me topé es con que aquí sí tengo que importar java.io, java.net, etc. Y otra cosa, que no se puede poner * para importar todas las clases, sino que hay que usar subguión.

import java.io._

class HolaProc(inputStream:InputStream, outputStream:OutputStream) extends Runnable {

  val input = new BufferedReader(new InputStreamReader(inputStream))
  val output = new PrintStream(outputStream)

  def run() = {
    val nombre = input.readLine()
    output.printf("Hola, %s!%n", nombre)
    output.flush()
  }
}

Como en Groovy, no es necesario ponerle public a todo, y no hay que estar poniendo punto y coma al final de cada sentencia. Lo curioso aquí que he notado es que no se usa implements sino extends aunque sea una interfaz; y podemos poner argumentos en la declaración de la clase, lo cual es equivalente a tener un constructor con esos parámetros.

Me gustó lo de la inferencia de tipos porque evita ser tan redundante en las declaraciones de tipos. Y me gusta que tengo opciones como var y val para declarar variables (var si la pienso modificar posteriormente, y val si la pienso dejar así, es como ponerle final, o como las variables en erlang que ya nunca se pueden modificar).

Como en Groovy, tampoco hay que estar manejando excepciones. Nada de try-catch ni declarar throws. La siguiente clase es la que ya maneja el socket:

import java.net._

class HolaSocket(socket:Socket) extends Runnable {

  def run() = {
    new HolaProc(socket.getInputStream(), socket.getOutputStream()).run()
    socket.close()
  }
}

Y por último, necesitamos la que escucha en un puerto:

import java.net._

class Servidor(port:Int) {

  var sigue = true

  def stop() {
    sigue = false
  }

  def run() = {
    val server = new ServerSocket(port)
    while (sigue) {
      val sock = server.accept()
      println("Nueva conexion")
      val proc = new HolaSocket(sock)
      new Thread(proc).start()
    }
  }
}

Al igual que en el ejemplo original, quiero hacer mis pruebas. Después de un rato de investigar, resulta que puedo usar jUnit en conjunto con ScalaTest para definir mis pruebas como ya me las conozco:

import java.io._
import org.scalatest.junit.JUnitSuite
import org.junit.Test

class TestProc extends JUnitSuite {

  @Test
  def testProc() = {
    val input = new ByteArrayInputStream(String.format("Enrique%n").getBytes())
    val output = new ByteArrayOutputStream()
    val proc = new HolaProc(input, output)
    proc.run()
    assert(output.toString().trim() == "Hola, Enrique!")
  }
}

Para no estarme peleando con el compilador y todo eso, decidí desde el inicio usar Gradle para compilar esto y poder ejecutar las pruebas sin mayor complicación. El script es bastante simple:

apply plugin:'scala'
defaultTasks 'build'

repositories {
  mavenLocal()
  mavenCentral()
}
dependencies {
  scalaTools 'org.scala-lang:scala-compiler:2.9.0-1', 'org.scala-lang:scala-library:2.9.0-1'
  compile 'org.scala-lang:scala-library:2.9.0-1'
  testCompile 'junit:junit:4.8.2', 'org.scalatest:scalatest:1.2'
}

Mis archivos de código Scala los puse en src/main/scala y la clase de pruebas unitarias la puse en src/test/scala. Ejecuto gradle y listo, todo compila (después de 2^n intentos) y la prueba pasa a la primera.

Ahora... cómo ejecuto esto? Seguramente los programas en Scala también deben tener un método main, no? Pero... resulta que Scala no tiene métodos estáticos. Lo que sí tiene, son clases que se definen como singleton, no por configuración, sino por diseño, es decir en el código indicamos que así van a ser. Y si tenemos una clase así, con un método main, ese es el que podemos ejecutar.

Aquí de paso descubrí otra cosa: al igual que en Groovy, puedo definir más de una clase en Scala. Para estas clases especiales que son singletons, hay que usar la palabra object en vez de class. Esto lo metí después de la definición de la clase Servidor, en Servidor.scala:

object Main {

  def main(args:Array[String]) = {
    val port = if (args.length > 0) args(0).toInt else 9999
    println("Corriendo servidor en puerto " + port)
    new Servidor(port).run()
  }
}

Aquí vi otro detalle: el if en Scala se comporta como el operador condicional de Java (ya saben, condicion ? unaCosa : otraCosa). Y después de fusilarme vilmente eso de toInt, me di cuenta que los paréntesis son opcionales para invocar métodos que no tienen argumentos (primero pensé que eran necesarios siempre porque no compilaba un simple println "x", les tuve que poner paréntesis para dejarlos como println("x")).

Con esto, al final solamente tuve que teclear scala -cp build/classes/main Main y el programa arrancó; un par de telnets al puerto 9999 sirvieron para comprobar que funciona correctamente:

$ telnet localhost 9999
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Scala
Hola, Scala!
Connection closed by foreign host.

Y en el servidor veo esto:

$ scala -classpath build/classes/main Main
Corriendo servidor en puerto 9999
Nueva conexion

Bueno. Sólo quería documentar mi primer encuentro con Scala. Seguramente hay varias maneras de hacer este programa más scalesco, con actores, sin tener que invocar el Thread, no sé. Con tiempo podré seguir aprendiendo al respecto; seguramente este primer programa sigue estando demasiado javesco y usé solamente el mínimo de lo que Scala tiene que ofrecer.

ACTUALIZACIÓN: El código que pongo en los ejemplos de este artículo y los que siguen, ya está disponible en GitHub.

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 Sr. Negativo

Scala y Groovy

Lo que me gusta de estos lenguajes, es que se parecen mucho (según yo) a Python.

'''
Ejemplo en Python
'
''
def Hola():
  print "Hola Sr. Negativo"

#main
Hola()

Eso de no ponerle ; esta muy bien (al principio no te acostumbras).

La verdad Scala me parece un poco más confuso que Groovy. Buen post como siempre.

Ahh que $%&/ que no se llaman

Ahh que $%&/ que no se llaman métodos estáticos sino de clase ( que en fin tampoco tiene , ni los necesita ). Los objects que son también llamados "companion classes" estas muy chidos.

La palabra que estás buscando para definir val es "valor". Cuando tienes un valor, el valor ya no cambia ( no es variable )

Lo que salta a la vista es que el HolaProc no tiene que ser una clase, puede ser un Clousure ( mañana que tenga mi compilador a mano hago un intento , ahorita ni me arriesgo )

En Scala hay como chorrocientas mil formas de hacer lo mismo de forma diferente y de escribir lo mismo de forma diferente, a ver si mañana encuentro logro postear alguna.

+2 por el post!

@SrNegativo, lo malo es que

@SrNegativo, lo malo es que luego olvidas ponerle el ";" en Java. Estaría bien que te aventaras la versión Jython ;)

Just for run esta sería un transcript del mismito programa en Ryz ( yaaa que chafa me ví pues si, ni modo https://gist.github.com/1068901 )

Saludos.

Imagen de ezamudio

OO

Como dije en el mismo post, esta primera forma de hacerlo está muy javesca todavía; simplemente es la misma implementación que en Java, pero usando la sintaxis de Scala. El siguiente paso es usar closures, o directamente actores, no sé porque apenas me estoy familiarizando con el tema.

Pues me dijeron que esta muy

Pues me dijeron que esta muy bien :)

Un amígo mío lo revisó ( @missingfaktor ) y estos son sus comentarios:

It's pretty idiomatic I would say (as per the Odersky book, at least). This is how you would write it in vanilla Scala.

Assuming this code is put in production, these are a few places where there is room for making it more Scala-esque (as per the latest trends in the community):


1. You might want to use Scalax IO for input-output. (possibly in conjunction with Scalaz iteratees.)
2. The class Server has a mutable variable. I can't see it used anywhere. Could be eliminated, probably?
3. `while` loop could be replaced with `Iterator.continually`.
4. You could use actors instead of threads. Though I don't know how viable this suggestion is, as I have only glanced over the code.
5. Finally, you could use ScalaTest or Specs for testing. :-)

Nada mal :)

Imagen de ezamudio

versión 2

Mientras tanto ya hice mi versión 2. Estos son los cambios:

Desaparece la clase HolaProc, se convierte en una función de primer nivel, aunque la tuve que meter en un package object porque muy muy primer nivel las funciones es lo que cacarean los Scaleros pero a la mera hora resulta que todo tiene que estar envuelto en un objeto:

package object hello {

import java.io._

def holaProc(inputStream:InputStream, outputStream:OutputStream):String = {
  val input = new BufferedReader(new InputStreamReader(inputStream))
  val output = new PrintStream(outputStream)
  val nombre = input.readLine
  output.printf("Hola, %s!%n", nombre)
  output.flush
  nombre
}

}

Con eso, cambia la clase HolaSocket, porque ahora se construye con un socket y con una función que reciba streams y devuelva un String:

class HolaSocket(socket:Socket, proc:(InputStream,OutputStream) => String) extends Runnable {

  def run() = {
    println("Nos mandan " + proc(socket.getInputStream(), socket.getOutputStream()))
    socket.close
  }

}

Y finalmente la clase Servidor, pues crea los HolaSockets pasándoles la función como parámetro, esto en su método main:

def run() = {
  val server = new ServerSocket(port)
  while (sigue) {
    val sock = server.accept
    println("Nueva conexion")
    val proc = new HolaSocket(sock, holaProc)
    new Thread(proc).start
  }
}

Ahora voy a revisar eso del Scalax IO, a ver si vale la pena para un protocolo tan simple (y aunque no valga la pena, total, es un ejercicio).

Imagen de ezamudio

UT!

Ah, obviamente tuve que cambiar la prueba unitaria, ya nomás invoco la función y verifico los valores.

val input = new ByteArrayInputStream(String.format("Enrique%n").getBytes())
val output = new ByteArrayOutputStream()
val result = holaProc(input, output)
assert (result == "Enrique")
assert(output.toString().trim() == "Hola, Enrique!")

Closures + Structural typing

La ventaja adicional de tener first class functions es poderlas declarar al vuelo y pasarlas como argumento.

Por ejemplo, el HolaSocket podría recibir una función que va a hacer algo cuando sea invocada, con la "flexibiilidad" de poder hacer cualquier otra cosa.

Para hacer más interesante el ejercicio ( y quizá complejo ) en Scala se puede utilizar un feature que se llama "structural typing" que es el simil del duck typing donde si el objeto responde al mensaje, se llama a al método/función; pero siendo Scala un lenguaje de programación con tipeo estático, la validación se hace en tiempo de compilación. Más aún Scala permite definir el método en el parametro.

Sin más preámbulos ahi el código original modificado para mostrar estas dos características:

import java.io.{PrintStream, BufferedReader, InputStreamReader}
import java.net.{Socket, ServerSocket}

// definir un tipo que reponde a "readLine" es como una interfaz
type ReadLiner = {def readLine(): String }
type PrintLiner  = {def println(m:String)}

object ServidorHola {
  def main( args: Array[String] ) {
    new ServidorHola(8081).run()
  }
}
class ServidorHola( port : Int ) {
  var sigue = true
  def stop() {
    sigue = false
  }
  def run(){
    val server = new ServerSocket( port )
    while( sigue ) {
      val socket = server.accept()
      val client = new HolaSocket( socket )
      println("Nueva conexion")
      // Hasta aquí es más o menos igual
      // pero ahora se pasa una funcion
      client.ejecuta((input: ReadLiner, output: PrintLiner)=>{
            val nombre = input.readLine()
            output.println("Hola, "+ nombre)
      })
    }
  }
}

// El thread y el runnable se movió acá:
class HolaSocket( s : Socket ) {
   def ejecuta( func : (ReadLiner,PrintLiner) => Unit) {
     val input = new BufferedReader(new InputStreamReader(s.getInputStream))
     val output = new PrintStream(s.getOutputStream)

     new Thread(
       new Runnable(){
        def run() {
          func(input,output)
          output.flush()
          s.close()
        }
     }).start()
  }
}

Lo intersante es que ni el BufferedReader implementa ReadLiner ni el PrintStream implementa PrintLiner pero el compilador sabe que ambos tienen el método requerido.

Se puede tambien definir la función/método en el parametro mismo:
Se vería así:

...
class ServidorHola( port : Int ) {
...
  def run(){
    ...
    while( sigue ) {
      ...
      client.ejecuta((input:  {def readLine(): String }, output: {def println(m:String)})=>{
            val nombre = input.readLine()
            output.println("Hola, "+ nombre)
      })
    }
  }
}

class HolaSocket( s : Socket ) {
   def ejecuta( func : ({def readLine(): String },{def println(m:String)}) => Unit) {
     val input = new BufferedReader(new InputStreamReader(s.getInputStream))
     val output = new PrintStream(s.getOutputStream)
     ...
          func(input,output)
     ...
  }
}

Lo interesante del Structural typing es que se pueden utilizar objetos diferentes sin necesidad de tener que heredar / implementar explicitamente una jerarquía o interfaz.

Por ejemplo para la prueba se podría usar algo que no tiene nada que ver con el paquete java.io:

object TestIt {
  def main(args: Array[String]) = {
    val proc = (in: ReadLiner, out: PrintLiner )=>{
                  out.println("Hola, "+ in.readLine())
                }

    val input  = new Object(){
      def readLine() = String.format("Enrique!%n")
    }
    var valor : String = ""
    val output = new Object() {
      def println(s:String) {
        valor = s
      }
      override def toString() = valor
    }
    proc(input, output)
    assert(output.toString().trim() == "Hola, Enrique!" )
  }
}

Aunque este código ya está probando una funciona X y no necesariamente la que se usa en el ejemplo del HolaServer, pero el objetivo es más bien mostrar el estructural typing.

:)

Imagen de ezamudio

Prueba

OK ya mencionaste al final que la prueba ya no sirve de nada, porque fue hecha para probar la función que se va a usar, y esta prueba que pones realmente es código que corre hasta por cuenta propia, no hace referencia al código que supuestamente debería probar. A lo mucho es como una prueba pero de PrintLiner y ReadLiner.

La pregunta aquí es: ¿cuál es la mejor manera? Esta última implementación no me parece óptima porque se define la función en el servidor, lo cual lo hace inflexible. La versión que hice del Servidor, si se compila usando otra función llamada holaProc con los mismos parámetros y tipo de retorno, puede usarse para algo distinto...

Si el objetivo es mostrar el

Si el objetivo es mostrar el structural typing y no probar realmente el código.

El ejemplo es burdo claro, pero abre la visión a modelos diferentes. Si en vez de declarar la función en el servidor la recibes como parámetro y abre nuevas posibilidades :

// Esto es JavaScript
var server = net.createServer(function (socket) {
  socket.write("Echo server\r\n");
  socket.pipe(socket);
});

Como en Node ;)

Imagen de greeneyed

Lo de no "tener" que tratar las excepciones está bien pero...

...eso no significa que haya que ignorarlas

En todas las implementaciones que he visto en la página, si se produce un problema en la función (sea run, sea func etc...) el socket nunca se cerraría por que el socket.close() no está en un finally. Un programa así en producción está predestinado a dar problemas :).

Imagen de ezamudio

de acuerdo

Como dice el título, este es un primer acercamiento, un holamundo MUY simple. De momento estoy ignorando las excepciones, sé que está mal, nunca lo haría en software "de verdad", pero esto apenas es un juguetito con el que voy aprendiendo el lenguaje y quiero enfocarme completamente al aprendizaje: la sintaxis, las estructuras, mecanismos, inferencia de tipos, etc de Scala.

De hecho el poder ignorar las excepciones por completo es parte de lo que se puede apreciar; mi proceso hasta ahora ha sido bastante empírico y muy directo; capaz que me encontraba con que Scala mágicamente le ponía un try-finally a los sockets o streams, o que haya una anotación tipo la de Lombok para lograr algo similar... en fin. Ahí vamos apenas. Esto de hecho no son ejemplos a seguir; no digo que sea la mejor manera de resolver el problema planteado, ni que el código deba ser usado como referencia, de hecho estoy seguro que se le pueden hacer bastante mejoras, por eso seguí ya en otra entrada con la segunda parte y Oscar me ha estado ayudando con sus comentarios, se ve que le ha metido más que yo, pero pues es que también quise ya poner algo de contenido de Scala en el sitio y me parece que lo primero que hay que hacer es perderle el miedo y la manera de lograr eso es simplemente escribiendo código...

Imagen de greeneyed

Entiendo

No digo que haya que hacer ejemplos que sean directamente usables en producción. Lo que quería decir es que comparar cómo se escribiría el código en Java "engorroso por que hay que envolver el uso de sockets en try/catch/finally" con el código en Scala donde no se pone ningún control de errores es engañoso y nos puede llevar a pensar que en Scala todo es mucho más "sencillo y feliz", cuando no es cierto en esa parte: sigue pudiendo haber errores y hay que tratarlos.

Si se supone que queremos aprender el lenguaje para un uso real, me parece bueno compararlo con un caso real y en ese sentido, lo que deja claro este ejemplo es que el hacer una programación robusta queda más en manos del programador por que el compilador ni siquiera te da las pistas que te da Java. En Java se podría obviar igualmente todo el tratamiento de excepciones simplemente poniendo throws Exception por todo, pero "cantaría" que estás haciendo una barbaridad y la has de hacer voluntariamente.

Simplemente digo que esas cosas tambien hay que tenerlas en cuenta para comparar correctamente cosas equivalentes y no basarse en supuestos erroneos.