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:

 

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  , hay que ponerla después de   (que a su vez está después de   porque de hecho   es subclase de  ).

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:

 

Eso a fin de cuentas es un mensaje síncrono; el mensaje es la invocación de  , el destino es   y la ejecución del mismo se hace toda en un solo hilo; lo que devuelva el método se asigna a  . 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.

 

Así que como podemos ver, los mensajes se reciben como ya habíamos visto; solamente hay que tomar en cuenta que debemos invocar   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  , como en los mensajes o en los catch, etc, es importante ver que no pasa como en un   de Java: no es necesario poner   o   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:

 

La palabra reservada match

La palabra reservada   de Scala es bastante más poderosa que un simple   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   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:

 

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

 

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

 

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

 

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.

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:

 

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:

 

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:
 

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í:

 

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:

 

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):
 

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

La sintaxis del parámetro de la función   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  .
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  .

Ejemplo: Usando el valor de "match"
 

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