style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">

HolaMundo en Scala III: Mensajes síncronos, patrones

Sigo estudiando algo de Scala, de manera muy empírica aún, leyendo varias referencias y fuentes de información de todo tipo, y sigo aprendiendo bastante.

He seguido orientándome más a la parte de multiprocesamiento, que a la parte de programación funcional. Sigo jugando con el ejemplito del servidor holamundo; ahora le puse manejo de excepciones, condiciones de salida y he estado viendo algo del pattern matching que parece ser una parte importante de Scala, para varias cosas distintas (desde usarse en vez de un simple cast que haríamos en Java, hasta manejo de excepciones, y por supuesto los mensajes entre actores).

Condiciones de salida

Primero que nada, ya le agregué una condición de salida a la clase Servidor, para que dependiendo de algún mensaje recibido por un socket, se pueda terminar con el ciclo que estaba previamente definido como infinito. Para ello agregué un nuevo método con la intención de que lo invoque un actor desde otro proceso, y el debido manejo de una excepción que esta secuencia de salida genera. Con esto ya podremos apreciar algo del manejo de excepciones, que es ligeramente distinto a Java. Queda así al final:

  //Definimos el server ahora como variable
  var server:ServerSocket = null
  //Y tenemos una variable nueva para el ciclo
  var running=true

  //Este es el método nuevo
  def shutdown() = {
    running=false
    server.close()
  }

  //El método run, que ya teníamos
  def run(port:Int) = {
    try {
      server = new ServerSocket(port)
      //Tenemos que iniciar estos a patin
      Lector.start()
      Escritor.start()
      while (running) {
        val sock = server.accept()
        log.info("Nueva conexion")
        Lector ! sock
      }
    } catch {
      //Hay un solo bloque de catch, y se definen las excepciones
      //como casos para que Scala compare la arrojada con las que
      //se definen aqui
      case _:java.net.BindException =>
        //Aqui no usamos la excepcion, por eso la puedo definir con _
        log.error("Ya está ocupado el puerto {} por otro proceso", port)
      case ex:java.net.SocketException =>
        //Aqui si la uso, para comparar el mensaje
        if ("Socket closed" == ex.getMessage) {
          log.warn("Han cerrado el server socket")
        } else {
          log.error("Error de socket", ex)
        }
      //Para implementar el antipatron pokemon podria hacer esto
      //case e => log.error("ALGO MALO", e)
      //No le puse clase pero por ser un bloque de catch van a caer puras excepciones/throwables aqui
    }
  }

Así es: el catch se define como un solo bloque, en el cual se definen los casos que queremos manejar, según la clase de la excepción. En caso de querer manejar varias excepciones que pertenezcan a la misma jerarquía, debemos primero manejar los casos más específicos (las subclases) y luego los más generales (las superclases). Si por ejemplo quisiera manejar IOException, hay que ponerla después de SocketException (que a su vez está después de BindException porque de hecho BindException es subclase de SocketException).

Ese fue un ejemplo del pattern matching que es muy utilizado en Scala. De hecho desde que metí el esquema de actores, ya teníamos algo de pattern matching pero no se aplicaba mucho porque los mensajes eran muy simples, pero ahora podemos aprovechar esto para sofisticarlo un poco.

Saludo modificado

Quiero además modificar la manera en que se maneja el saludo. Anteriormente, tenía un actor Lector que leía del socket, y pasaba ese texto al Escritor para que devolviera un saludo (el socket le llegaba al escritor como parte del mensaje). Otra manera de hacerloo, es que un actor maneje el socket, mientras que otro genere el saludo; pero entonces, el que maneja el socket debe pedir al otro actor el saludo generado, por lo que debe esperar una respuesta. Esto se podría hacer con 2 mensajes asíncronos: uno del primer actor al segundo actor, y otro de vuelta del segundo actor al primero, pero esto puede complicar de manera innecesaria el flujo, además de que no queda muy bien expresado, ya que de hecho el actor que maneja el socket no puede continuar hasta no tener un saludo que devolver, por lo que tiene más sentido manejar un mensaje síncrono.

Ya habíamos visto la notación del signo de admiración para enviar un mensaje asíncrono a un actor. Hay otra notación para enviar un mensaje síncrono. A fin de cuentas, un mensaje síncrono es casi lo mismo que invocar un método de un objeto, en el mismo hilo de ejecución; cuando hago algo como esto:

val resultado = objeto.metodo()

Eso a fin de cuentas es un mensaje síncrono; el mensaje es la invocación de metodo(), el destino es objeto y la ejecución del mismo se hace toda en un solo hilo; lo que devuelva el método se asigna a resultado. La diferencia con los mensajes síncronos es que cada actor puede estar realizando varias tareas en distintos hilos, y para poder manejar un mensaje síncrono pues debe haber sincronización entre hilos: el hilo desde donde se envía el mensaje se debe quedar esperando a obtener un resultado, pero la ejecución del mensaje probablemente se hará en un hilo separado (o se podría hacer en el mismo; algo bueno de esto es que es un detalle de implementación y no debemos preocuparnos por ello; la cosa es que se ejecutará como proceso separado).

Entonces, en vez de tener actores Lector y Escritor, ahora tengo los actores Conexion (que maneja el socket) y Saludador (que simplemente genera el saludo). El actor Saludador es más simple; no envía ningún mensaje, solamente los recibe. Pero aquí es donde vamos a manejar algo de pattern matching en los mensajes; para ello, vamos incluso a definir una expresión regular para identificar cuando alguien no envía un nombre sino una despedida, en cuyo caso debemos terminar el programa.

//Redefinimos la clase Saluda, quitando el Socket porque ya no
//se necesita pasar al Saludador
case class Saluda(nombre:String, fecha:java.util.Date)

object Saludador extends Actor {

  //Esto es de SLF4J no importa mucho aquí, pero es útil
  //fijarnos en que se invoca getClass() porque no es una variable
  //estática, los singletons son objetos que sólo tienen una instancia.
  private val log = LoggerFactory.getLogger(getClass())
  //Aquí definimos la regex, invocando el método r de String
  //(OK, realmente es de RichString de Scala)
  private val Bye = "(bye|adios|au revoir).*".r

  def act() = {
    loop {
      react {
        //Primero caso más específico:
        //Si mandan una cadena que comience con la despedida
        //en español, inglés o francés, terminamos
        case Saluda(Bye(despedida),_) =>
          //El método reply es para devolver respuesta a quien
          haya invocado este mensaje (si fue invocado de manera síncrona)
          reply(None)
          log.info("ADIOS ({})!!!", despedida)
          //El metodo exit es para salir del loop en el que estamos
          //ya no vamos a procesar más mensajes
          exit()

        //El caso más general: cualquier otra cosa es un nombre
        //y simplemente devolvemos un saludo
        case Saluda(nom,cuando) =>
          log.info("Formateamos saludo recibido {} a las {}", cuando, new java.util.Date)
          //El metodo reply recibe un parámetro de tipo Any (como decir Object en Java)
          reply(String.format("Hola, %s!", nom))
      }
    }
  }
}

Así que como podemos ver, los mensajes se reciben como ya habíamos visto; solamente hay que tomar en cuenta que debemos invocar reply para devolver un valor a quien haya invocado ese mensaje de manera síncrona (si lo invocaron de manera asíncrona el reply será ignorado).

Lo interesante es ver cómo también para los mensajes se pueden definir patrones que se irán cotejando en tiempo de ejecución, e incluso se pueden usar expresiones regulares cuando estamos manejando cadenas; en el caso particular de que el actor reciba algo que parezca una despedida, devolverá una respuesta vacía y dejará de procesar más mensajes.

En el caso de que llegue una despedida, no vamos a hacer nada con la fecha que llega, por lo que podemos usar el comodín _ simplemente para ignorar ese parámetro.

Cuando se tienen bloques con varios case, como en los mensajes o en los catch, etc, es importante ver que no pasa como en un switch de Java: no es necesario poner break o return al final de cada caso, porque no se pasa de uno a otro; en Scala cuando se cumple un caso solamente se ejecuta ese y se dejan de evaluar los restantes. Por eso es muy importante definir bien los casos; si se pone uno más específico después de uno más general, nunca se va a evaluar ese último porque la evaluación general será la que cumpla la condición primero.

Ahora veamos cómo se invocan estos mensajes síncronos desde la Conexion:

object Conexion extends Actor {

  private val log = LoggerFactory.getLogger(getClass())

  def act() {
    loop {
      react {
        //Seguimos teniendo el caso de recibir un socket porque esto lo invoca el Servidor
        case s:Socket =>
          val now = new java.util.Date
          log info "Recibimos conexion"
          val out = new PrintStream(s.getOutputStream)
          //Ahora ya manejamos algunas excepciones
          try {
            //Le ponemos timeout al socket; deben enviarnos texto en menos de 5 segundos
            s.setSoTimeout(5000)
            //Leemos texto del socket
            val reader = new BufferedReader(new InputStreamReader(s.getInputStream))
            val nombre = reader.readLine()
            //AQUI es la invocación síncrona de mensaje con !?
            //El tipo de retorno es Any
            val saludo = Saludador !? Saluda(nombre, now)
            //Esto es lo que se hace en Scala en vez de un simple cast
            saludo match {
              //Si nos mandan una cadena, imprimimos saludo en el socket
              case saludo:String =>
                //sintaxis alterna para out.println(saludo)
                //esto se permite para poder usar los métodos con
                //nombres "raros" como operadores de notación infija
                // 2+2 es más claro que 2.+(2)
                out println saludo
              //Si nos mandan respuesta vacía, mandamos shutdown al Servidor y dejamos de procesar mensajes
              case None =>
                Servidor.shutdown()
                exit()
            }
          } catch {
            //Especificamente, si hay timeout, enviamos instrucciones en el socket
            case ex:java.net.SocketTimeoutException =>
              out println "Debes enviarme una linea de texto para devolverte un saludo"
            //Si pasa algun otro error pues imprimimos error en el log
            case ex:IOException =>
              log.error("Problemas en el socket", ex)
                  } finally {
            //También tenemos un bloque finally por supuesto
            log info "cerrando socket"
            s.close()
                  }
      }
    }
  }
}

La palabra reservada match

La palabra reservada match de Scala es bastante más poderosa que un simple switch de Java (sí, incluso que el de Java 7 que ya puede manejar cadenas huuuuuuy), porque cada caso de un match puede ser una expresión completa, como ya vimos en el caso de la expresión regular que se evalúa para el primer parámetro del objeto de saludo.

Al principio puede parecer muy engorroso tener que hacer match en vez de un simple cast, pero en realidad nos lleva a poder hacer mejor código, ya que en Java es común tener problemas como este:

Object o = pool.obtenerObjeto();
//Según yo, estoy segurisisisisisimo de que me devuelven una conexión
Connection conn = (Connection)o;

Lo anterior compila sin ningún problema, pero si resulta que por alguna razón el pool nos devuelve un objeto de otra clase que no se pueda asignar a una variable de tipo Connection, se arrojará una bonita ClassCastException que prácticamente nadie cacha nunca porque nadie la espera porque siempre que un programador hace un cast es porque está 999% seguro de la clase de objeto que debería estar manejando.

En cualquier lenguaje se pueden hacer porquerías; en Scala en vez del cast podemos hacer un match de un solo caso y poner ahí el código que en Java iría después del cast (o sea, lo que se ejecuta si NO hubo ClassCastException). Si en la ejecución el pool devuelve algo que no es una conexión, el código simplemente no se ejecuta porque no se entra a ese bloque (seguramente no es la mejor opción, a fin de cuetnas es fallar de manera silenciosa).

Otra opción es poner un segundo caso que arroje una excepción para cualquier otra cosa que no sea una conexión y entonces ya tenemos un comportamiento similar al de Java. Pero lo importante es que tenemos la opción (en Java también, pero es tan fácil hacer un cast que todo mundo lo hace sin revisar primero con instanceof).

Object o = pool.obtenerObjeto()
o match {
  case conn:Connection => //el código a ejecutar si realmente es Connection
  case _ => throw new IllegalStateException("El pool devolvio otra cosa que no es conexion")
}

Qué pasa cuando usamos algo como un ApplicationContext de Spring? A veces terminamos comparando contra varias clases (es String o int?)

Object o = applicationContext.getBean("bean");
if (o instanceof Runnable) {
  ((Runnable)o).run();
} else if (o instanceof Bla) {
  ((Bla)o).bla();
} else {
  log.error("WTF no sabemos qué hacer con esto {} {}",
    o, o.getClass().getName());
}

En Scala, el match está hecho para manejar varios casos y nos permite expresar lo mismo de mejor manera:

val o = applicationContext.getBean("bean")
o match {
  case r:Runnable => r.run()
  case b:Bla => b.bla()
  //Object sigue siendo la clase raíz de Java
  case wtf:Object =>
    log.error("Qué debo hacer con un {} tipo {}?",
      wtf, wtf.getClass.getName)
  //Any es la clase raíz de Scala y es lo que se usa aqui si no se define tipo
  case wtf =>
    log.error("Qué debo hacer con un {}?", wtf)
}

Hay que recordar que realmente no se trata de escribir menos código (aunque es deseable), sino de escribir mejor código.

ACTUALIZACIÓN: El código completo para todos estos ejemplos 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.

Orale, orale voy a tener que

Orale, orale voy a tener que registrar lema de Ryz eh "Ryz: no se trata de escribir menos código, sino de expresar mejor tus intenciones"

Sobre el pattern matching aunque al final lo mencionas bien, sí es más que un simple cast con instanceof integrado y tiene varias otras cosas más, por ejemplo estos patrones se pueden anidar y anidar y así en vez de tener que revisar una estructura compleja con una gran cadena de if/else y e instancesof se puede revisar si un objeto tiene dentro otro y ese otro otro y demás:

Por ejemplo modificado de: http://www.artima.com/scalazine/articles/pattern_matching.html

object match {
  case Direccion(Nombre(propio,aPaterno,aMaterno), calle, ciudad, estado, cp) => println( aPaterno + ", " +  cp)
  case _ => println("No es una dirección") // the default case
}

Revisa si el objeto es una dirección que tenga un destinatario: nombre con, aPaterno, aMaterno, calle, ciudad, estado y código postal, si es así usa el apellidoPatterno y el código postal .

El equivalente en Java empezaría con:

if( object instanceof Direccion ) {
    if(  ( ( Direccion ) object ).getNombre()  ) {
         ....
   }
}

Obviamente estas abstracciones tienen un costo en el momento del aprendizaje, al principio no son intuitivos ni nada, pero eso pasa siempre con todo lo nuevo y se quita con la práctica, aunque en mi opinión en algunos lenguajes necesitan más horas de vuelo que otros.

+1 por la tercera parte :)

Y se ve "raro"

Por ejemplo cuando haces:

object match {
  case Direccion(Nombre(propio,aPaterno,aMaterno), calle, ciudad, estado, cp)

el orden lo toma de un cnstructor o algo asi?

Imagen de ezamudio

case class

Pues existen las case classes, que puedes crear precisamente para usarlas en cláusulas case. Los ejemplos de la segunda y tercera parte del holamundo (o sea mi artículo anterior y este mismo) usan una case class para el mensaje de saludo; no hace otra cosa que encapsular una cadena y una fecha, por lo que bien pude haber usado una tupla, pero para efectos del ejemplo preferí definir mi propia clase.

Las puedes definir así:

case class Nombre(nombre:String, apaterno:String, amaterno:String)
case class Direccion(nombre:Nombre, calle:String, ciudad:String, estado:String, cp:String)

Y luego lo del pattern matching pues también ya viste en el ejemplo que puse en este artículo, que puedes hacer cosas como pone Oscar, pero no solamente eso, sino por ejemplo:

case Direccion(null, calle, ciudad, edo, cp) => //tal vez dar un error porque no viene nombre
case Direccion(Nombre(propio, null, amaterno), w, x, y, z) => //dar error porque no viene apellido paterno
case Direccion(Nombre(propio, apaterno, amaterno), calle, ciudad, "DF", cp) => //Tratar las direcciones del DF como caso especial
case Direccion(Nombre(propio,apaterno,amaterno),calle,ciudad,edo,cp) => //El caso general
case _ => //Cuando te mandan cualquier otra cosa que ya no cayó en los demás casos. Tipo el "default" en un switch
Imagen de bferro

match no es un operador

Seguramente voy a "fusilarme" algunos de los ejemplos que ezamudio está escribiendo en su "Hola Mundo" para ilustrar algunos de los conceptos de Scala que estoy comentando en mis posts.
También me gustaría puntualizar algunas cosas como ésta que escribo ahora.
La palabra "match" no es un operador; es una palabra reservada del lenguaje. Es bueno entender esto con claridad, atendiendo a que Scala permite que casi todos los símbolos que regularmente usamos en otras palabras como operadores, formen parte de la clase sintáctica de identificadores léxicos. Las palabras reservadas no forman parte de esa clase sintáctica.
La palabra "match" es una de las estructuras de control incorporadas (built in) de Scala. Las otras estructuras de control son: if, while, for, try, match, y las llamadas a funciones.
Scala permite una sintaxis especial para las llamadas a funciones con un sólo parámetro para que parezcan estructuras de control. Esa es la razón para incluir las llamadas a funciones dentro de las estructuras de control y poder escribir entonces código como el siguiente (un pedazo tomado del Hola Mundo de Enrique):

  def act() {
    loop {
      react {
        //Seguimos teniendo el caso de recibir un socket porque esto lo invoca el Servidor
        case s:Socket =>
          val now = new java.util.Date

Se observa que tanto loop como react están siendo usadas con la sintaxis de una estructura de control. Realmente son llamadas a los siguientes métodos definidos en el trait Actor:

def loop(body: =>Unit):Unit
def react (handler: PartialFunction[Any,Unit]): Nothing

La sintaxis del parámetro de la función loop puede parecer extraña. Scala maneja diferentes formas para el paso de argumentos: llamada por valor, cuando el valor del parámetro se evalúa antes de pasarlo a la función y llamada por nombre cuando el parámetro pasado se evalúa cuando es usado dentro de la función. Ese es el caso de la función loop.
Las llamadas por nombre son muy útiles cuando queremos pasar una expresión (en este caso se le pasa a loop un bloque) que se evalúe dentro del cuerpo de la función una vez que la función ya se está ejecutando.

Algo importante que debe conocerse es que en Scala, casi todas las estructuras de control son expresiones y no statements: ellas devuelven un valor. Sobre eso ya comentamos cuando usamos el valor que resulta de la estructura de control if.

Ejemplo: Usando el valor de "match"

scala> ("123" match {case "123" => 5 case _ =>"123"}).asInstanceOf[Int] +9
res0: Int = 14

scala>

Imagen de ezamudio

match

Muy cierto. De hecho mientras escribía este último artículo me puse a probar si es que match era un método en Any y/o que le pegaran a Object, por lo que se podría invocar objeto.match({ case bla =>etc }) pero no es así, por lo tanto es palabra reservada, pero me agarraron las prisas y lo dejé mencionado como "operador" (y considero ya muy revisionista estar modificándolo tanto tiempo después).

Interesante eso de las estructuras de control (que sean expresiones y no sentencias). Me parece que eso es esencial para lograr la programación funcional, no?

Ah y adelante con lo de los ejemplos, para eso son... de hecho quiero organizarlos bien y luego subirlos a GitHub, para facilitar que todo mundo los baje y juegue con ellos.

style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">