Monday, May 14, 2012

Implementación funcional del tres en raya en Scala

Aquí va una implementación del tres en raya, o tic-tac-toe, en Scala. Mi nueva contribución a Rosetta Code. El juego es interactivo humano-computador. Comienza el humano. El computador no es muy listo: hace movimientos legales pero puramente aleatorios. La implementación es funcional. El listado completo lo tenéis en Rosetta Code. Voy a comentar aquí algunos de sus elementos más interesantes.
  • val BaseBoard = ('1' to '9').toList Esto define una variable inmutable que contiene una Lista de caracteres entre el 1 y el 9, que serán nuestro tablero base. Representaremos un tablero como una Lista de caracteres.
123
456
789
    • '1' to '9' define un rango de caracteres del '1' al '9'. 1 to 9 definiría un rango de enteros entre el 1 y el 9. 1 until 9 definiría un rango de enteros entre el 1 y el 8
  • val WinnerLines = List((0,1,2), (3,4,5),...,(2,4,6)) En WinnerLines guardamos una Lista de Tuplas, donde cada Tupla contiene uno de los movimientos ganadores (líneas completas). 0,1,2 corresponde a  la primera línea horizontal (ojo, son los índices de las posiciones que corresponden a la primera línea horizontal), mientras que por ejemplo 2,4,6 se corresponde a una de las dos diagonales.
  • class Board(aBoard : List[Char] = BaseBoard) La clase Board representará nuestro tablero y nos permitirá instanciar objetos funcionales (que no guardan estado mutable). Esta clase tiene un constructor que recibe como parámetro una Lista de caracteres. Si no se le pasa parámetro, esa Lista de caracteres será por defecto BaseBoard. El parámetro del constructor (aBoard) automáticamente pasa a ser accesible por las funciones de la clase, aunque no lo es desde fuera.
  • def availableMoves = aBoard.filter(c => c != Human && c != Computer) 
    • Definimos una función de la clase Board que no tiene parámetros, y pero eso no es necesario poner paréntesis después del nombre (aunque podríamos hacerlo; def availableMoves() = ...).
    • Esta función devuelve una Lista de caracteres. Scala es un lenguaje tipado estáticamente, pero el compilador puede deducir muchas veces los tipos de variables y funciones y en estas ocasiones no es necesario decírselos explícitamente. Aunque el compilador lo deduzca, si queremos podemos hacerlo explícito (puede ser una buena documentación): def availableMoves : List[Char] = ...
    • El cuerpo de la función es una llamada a la función filter sobre el objeto aBoard (que es una List[Char]). Sobre una Lista, filter toma como parámetro una función que devuelve un booleano, y lo aplica a cada elemento de la Lista; luego devuelve una nueva Lista en la que sólo se copian aquellos elementos de la original para las que el filtro es cierto. 
    • c => c!= Human && c!= Computer es una función anónima. Toma como parámetro un carácter que llamamos c y devuelve cierto si es distinto de Human y es distinto de Computer (Human y Computer son dos constantes que están definidas como 'X' y 'O'). En este caso Scala puede deducir que c es un Char porque se usa para filtrar una Lista de caracteres. También podríamos ponerlo explícito: (c : Char) => ... Si el parámetro sólo se usara una vez en el cuerpo de la función anónima, ni siquiera habría que darle un nombre. Por ejemplo si sólo queremos quedarnos los elementos distintos de Human: aBoard.filter(_ != Human) es todo lo que necesitaríamos poner.
    • Un ejemplo: si aBoard es List('1','2','X','O'), availableMoves nos devolverá List('1','2')
  • def availableMovesIdxs = for ((c,i) <- aBoard.zipWithIndex if c != Human && c != Computer) yield i Esta función usa una list comprehension. El for ... yield nos permite crear una lista a partir de otra, u otras, con bastante flexibilidad. 
    • En nuestro ejemplo, lo primero es coger aBoard (List[Char]) y llamar a zipWithIndex, que nos devuelve una Lista de pares ([Char], [Int]) en la que se asocia a cada elemento de una Lista su posición en la misma: por ejemplo, List('a','b').zipWithIndex nos devolvería List(('a',0), ('b',1)). 
    • Luego, para aquellos pares que cumplan la condición de que el carácter (c) no es ni Human ni Computer, nos quedaremos con el índice (i). Por ejemplo, si aBoard es List('1','2','X','O'), availableMovesIdxs nos devolverá List(0,1).
  • def computerPlays = new Board(aBoard.updated(availableMovesIdxs(randomGen.nextInt(availableMovesIdxs.length)), Computer)) Esta función devuelve un nuevo objeto Board, después de que el computador haga un movimiento aleatorio. 
    • Primero, hay que hacer notar que este estilo es característico de los objetos funcionales: en lugar de modificar el estado del objeto Board actual, instanciamos uno nuevo con los cambios que queramos. De esta forma podemos trabajar sin tener estado mutable (por ejemplo, sin tener variables en el sentido imperativo de la palabra variable).
    • Usamos la función updated sobre la Lista de caracteres aBoard para crear una copia de esta Lista donde el elemento en la posición que le pasamos como primer parámetro se cambia por el segundo parámetro (Computer en este caso). El primer parámetro availableMovesIdxs(randomGen.nextInt(availableMovesIdxs.length)) selecciona aleatoriamente un elemento de entre availableMovesIdxs. Notad que availableMovesIdxs es una función que no tiene parámetros y aquí parece que le estamos pasando uno. No es verdad, availableMovesIdxs devuelve una Lista, así que el "parámetro" en realidad es el acceso al elemento i-ésimo de esa Lista. Notad que en este caso no podríamos escribir availableMovesIdxs()(0) porque el compilador protestaría porque intentamos acceder a un elemento de una Lista sin especificar cual.
  •   def isWinner(winner : Char) =
        WinnerMoves.exists{case (i,j,k) => aBoard(i) == winner && aBoard(j) == winner && aBoard(k) == winner}
    Esta función determina si el carácter pasado como parámetro (que debería ser 'O' o 'X' aunque no lo estamos comprobando) hace una línea completa en aBoard. Para ello usamos la Lista de Tuplas WinnerMoves donde teníamos guardados los índices de todas las líneas completas posibles en el tablero, y usamos la función exists, que toma como parámetro una función que devuelve un booleano y la aplica a cada elemento de la Lista. Si para algún elemento la función parámetro devuelve cierto, exists termina y devuelve cierto.
    • La función que le pasamos como parámetro tiene una sintaxis un tanto especial. Lo primero es que va entre llaves, en lugar de paréntesis. Y segundo, empieza con la palabra reservada case. Es otro ejemplo de lo que se puede hacer con emparejamiento de patrones en Scala: definir funciones parciales (que sólo devuelven un valor para ciertas entradas). En nuestro caso, sabemos con certeza que todos los elementos de la lista WinnerMoves son 3-tuplas y que todas tienen tres caracteres, así que podemos emparejar con confianza con un patrón que sólo acepte 3-tuplas y comparar sus tres elementos con el parámetro recibido. Si alguna de las 3-tuplas de WinnerMoves tiene todo 'X' o todo 'O' en nuestro tablero, tenemos un ganador.
    • Si queremos mayor seguridad, podemos asegurarnos de que lo que recibe la función es 'O' o 'X'. Para eso añadiríamos require(winner == 'X' || winner == 'O') como primera línea de la función. Esto nos permite añadir fácilmente precondiciones a nuestras funciones: si no se cumple la precondición, la función termina con una excepción de tipo IllegalArgumentException, y si se cumple, se ejecuta normalmente.
  •   def print {
        aBoard.grouped(3).foreach(row => println(row(0) + " " + row(1) + " " + row(2)))
      }
    Aquí tenemos un procedimiento. Notad que no lleva = después del nombre, directamente va el cuerpo entre llaves. En realidad un procedimiento en Scala es una función que devuelve algo de tipo Unit (o sea, que devuelve algo sin interés; Unit es similar al void de Java). En este caso print no devuelve ningún valor de interés, lo que hace es escribir por pantalla el tablero. Primero agrupa la Lista aBoard en grupos de tres, y luego para cada grupo de tres lo escribe en una línea en pantalla.
  • object TicTacToe extends Application {   En Scala podemos crear objetos singleton, que sólo se instancian una vez, con la palabra reservada object. Si creamos uno que extiende el trait (los traits para otro día) Application, el objeto será directamente ejecutable (el trait Application define automáticamente un método main que instancia el objeto singleton que acabamos de crear y ejecuta el código que lleve).
  • def readValidMove() : Char = { Una última cosa que quiero mencionar, y esta función me sirve como ejemplo (no copio aquí el código completo, ya está en Rosetta Code). Tenemos que leer la entrada del usuario de teclado hasta que nos proporcione un movimiento válido. Si creamos un bucle (algo tipo while(!validMove)...) vamos a necesitar leer la entrada del usuario en una variable mutable posiblemente varias veces. Las variables mutable en Scala se declaran con la palabra reservada var (val es para las inmutables). Sin embargo las variables mutables no son funcionales, así que en lugar de un bucle he implementado esto como una función recursiva, y así puedo usar val en lugar de var. En este caso creo que sería perfectamente razonable usar var y un bucle para esta parte del código, y Scala nos da la opción de hacerlo así, pero he preferido mantener un estilo funcional en todo el ejemplo. Lo mismo he hecho con la función play.
    • Por si no queda claro, esto es perfectamente legal en Scala: var a = 5; a = 6;  y hace lo que un programador acostumbrado a un lenguaje imperativo esperaría. Esto sin embargo es ilegal: val a = 5; a = 6; y nos devulve un error.
Para terminar, y por si acaso alguien lo nota, voy a señalar que la entrada/salida en este programa no es "puramente" funcional. Lo que busco es explorar y mostrar las posibilidades que Scala ofrece de combinar programación funcional y orientada a objetos sobre la plataforma Java. Scala también permite programar en un estilo totalmente imperativo, o mezclando el estilo imperativo y el funcional. El que busque programación funcional "pura" debería aprender Haskell.

Saturday, May 12, 2012

Detección de palíndromos en Scala

Mi primera, y pequeña, contribución a Rosetta Code: la versión recursiva de la detección de palíndromos en Scala:

def isPalindrome(s : String) : Boolean = s match {
  case s if s.length > 1 => (s.head == s.last) && isPalindrome(s.slice(1, s.length-1))
  case _ => true
}

Y aquí un pequeño ejemplo de uso:

scala>isPalindrome("holaloh")
res4: Boolean = true

Breve explicación de su funcionamiento:
  • def isPalindrome(s : String) : Boolean Definimos una función, llamada isPalindrome, que toma como entrada una cadena y devuelve un booleano. 
  • s match { ...} Usaremos emparejamiento de patrones (pattern matching). No es la única forma, ni mucho menos, de escribir esta función, pero yo diría que es la más idiomática en Scala (aparte de la versión no recursiva que aparece en Rosetta Code, que es sin duda la opción más sencilla).
  • case s if s.length > 1 Si la cadena s tiene longitud > 1
  • => (s.head == s.last) && isPalindrome(s.slice(1, s.length-1)) La función devuelve cierto si el primer carácter de la cadena es igual al último, y si el resto de la cadena (lo que está entre el primer carácter y el último sin incluirlos) es también un palíndromo
  • case _ => true En otro caso la función devuelve cierto (esto se emparejará con cadenas de longitud <= 1, y todas esas las consideramos palíndromos, incluyendo la cadena vacía).
Si nos pasan null como parámetro, la función terminará con una excepción de tipo NullPointerException, porque intentará averiguar si un null tiene longitud > 1. Para protegernos, podríamos añadir otro case al match (tendría que ser el primero): case null => false

Las cadenas en Scala son Strings de Java, pero tienen métodos añadidos para soportar todas las operaciones de las secuencias de Scala (Seq) (por eso podemos usar head, last o slice).

El emparejamiento de patrones da muchísimo más juego, esto apenas da una pequeña idea de la sintaxis que tiene y lo que se puede hacer en Scala.

Tuesday, May 08, 2012

The object-relational mapping problem: my two cents

This is a big problem, nicely summarized by Martin Fowler in http://martinfowler.com/bliki/OrmHate.html.

In my view, the main issue is that we have different abstraction levels in memory and in the database. If you work with classes like Person and Flight and Passenger in memory, and you work with varchars and ints in the database, you are calling for trouble. If your business logic is simple, and your objects look more like data than like objects, these differences may be a minor issue, but an issue after all: you can write things like select * from PASSENGERS, FLIGHTS where PASSENGERS.DESTINY = FLIGHTS.DESTINY and PASSENGERS.AGE > 18, which is OK, but also things like select * from PASSENGERS, FLIGHTS where PASSENGERS.DESTINY = FLIGHTS.DESTINY and PASSENGERS.AGE > FLIGHTS.NUMBER which does not make sense, but it works because both are ints, and may return some data.

In a higher abstraction level, you could possibly want to write things like select * from PASSENGERS, BAGGAGE_PIECES where PASSENGERS.ID = BAGGAGE_PIECES.PASSID and PASSENGERS.AGE > BAGGAGE_PIECES.WEIGHT and be warned that you are trying to compare an Age and a Weight, and that it is not allowed. To achieve this, you need support for Abstract Data Types (ADTs) in your relational database, and current commercial and open source database managers provide this support. The relational model allows this, although some vendors prefer to call it object-relational. If you are thinking that storing ADTs would violate the first normal form in relational databases, you are missing the A in ADT.

Working at the highest possible abstraction level, and the one which is closest to your problem domain, is usually a good idea, specially if there are a number of developers involved and if you plan to create a maintainable application. Your relational database is a part of your application and, as long as you have the choice, you should aim at the highest possible abstraction level there too.