Mostrando entradas con la etiqueta Scala. Mostrar todas las entradas
Mostrando entradas con la etiqueta Scala. Mostrar todas las entradas

martes, febrero 21, 2012

Scala Implicits: simple examples

Durante la entrada anterior analizábamos los conceptos básicos de los implicits en Scala desde un punto de vista "teórico", dejando a un lado los ejemplos de código con el objetivo de no convertir el post en algo completamente inmanejable e incomprensible. A lo largo de la entrada que nos ocupa analizaremos diversos ejemplos de aplicación, describiendo con la mayor exactitud posible el mecanismo de funcionamiento de los implicits aquí descritos.

Como punto de partida realizaremos un sencillo ejemplo en el que veremos como realizar conversiones automáticas a un tipo determinado (el compilador las realizará por nosotros). Supongamos que disponemos de la siguiente definición de clase

class User (private val username:String)

y que deseamos obtener instancias de la clase anterior sin la necesidad de realizar llamadas del tipo 

val user:User = new User("migue")

El primer paso que debemos dar será llevar a cabo la definición del implicit correspondiente. En este caso la conversión deberá realizarse desde String hacia User. Para ello definimos algo como lo que sigue:

object UserImplicits {
    implicit def stringToUsername(user: String) = new User(user)
}

Nuestra siguiente tarea será hacer que el implicit anterior esté disponible en el ámbito donde lo queramos utilizar (una buena práctica suele ser realizar la definición de todos nuestros implicits en un singleton object llamado Preamble e importar dicho objeto al comienzo de nuestras clases):


import UserImplicits.stringToUsername
val user:User = "migue"

¿De verdad el código anterior compila? ¿Que está pasando? El proceso es "muy sencillo", el compilador detecta que hay un error de tipos puesto que no puede asignar una cadena a una variable de tipo User. Antes de declarar que existe un error ocasionado por una asignación de tipos incorrecta, el compilador busca si existe una conversión implicita disponible que le permita resolver el error encontrado. En este caso, el compilador buscará si existe un implicit que nos permita convertir una cadena de texto en un objeto de la clase User. Si revisamos la definición de nuestro primer implicit veremos que disponemos de una función que cumple los requisitos que el compilador está intentando satisfacer, por lo que este último, de manera interna, realizará algo similar a


val user:User = stringToUsername("migue")

El segundo ejemplo de uso de los implicits que vamos a tratar son las conversiones de objetos sobre los que se realiza la llamada de un método. Nuestro objetivo será añadir nuevos métodos a una clase externa que podría estar definida en una librería third-party sobre la que no tenemos ningún tipo de control.

Imaginemos la siguiente definición de clase en una librería externa (por sencillez esta clase estará definida en el propio archivo de código fuente junto al resto de ejemplos)


class ExternalClass {
def f1() = {
"f1"
}


def f2() = {
"f2"
}
}

en la que nos gustaría incluir el método def f3() = {"f3"}. Para llevar a cabo nuestra tarea construiremos una clase auxiliar en la que definiremos el método que deseamos incluir:



class MethodHelper {
def f3(): String = {
"f3"
}
}

e incorporamos un nuevo implicit


implicit def newMethodsToExternalClass(externalClass:ExternalClass) : MethodHelper = {new MethodHelper }



A continuación podremos escribir un código similar al que sigue:


import UserImplictis.newMethodsToExternalClass


println("Method f3() is added via an implicit conversion: " + externalClass.f3())


¿Qué está ocurriendo en esta ocasión? Durante el proceso de compilación, Scala detectará que existe un error puesto que el método f3() no forma parte de la definición de la clase ExternalClass. Antes de emitir un error, el compilador buscará una conversión implícita en la que se convierta el objecto receptor de la llamada del método f3() en un objeto que realmente posea dicho método. En nuestro ejemplo newMethodsToExternalClass es el elemento que nos permite que el código anterior compile correctamente. ¿Que está ocurriendo internamente? El compilador estará emitiendo un código similar al siguiente:

newMethodsToExternallClass(externalClass).f3()

Como último ejemplo para esta entrada, revisaremos cómo podemos hacer uso de parámetros implícitos en las llamadas a funciones. Incorporemos el siguiente método a la definición de la clase User

def encodePassword(password:String)(implicit encoder: Encoder) : String = {

encoder.encode(password)
}

Fijémonos que hemos marcado el segundo argumento de la función como implicit por lo que el compilador intentará completar las llamadas a la función añadiendo los parámetros necesarios. Definamos un encoder de ejemplo (que no realiza ningún tipo de operación útil) y utilizemos


implicit object IdentityEncoder extends Encoder {
  override def encode(password:String): String = password
}



import UserImplictis.IdentityEncoder


user2.encodePassword("foo")

Como podemos observar en el fragmento del código anterior en ningún momento estamos indicando el segundo parámetro de la función encodePassword sino que es el propio compilador el que está intentado incluir en la lista de parámetros alguno de los implictis disponibles de manera que la llamada de la función sea correcta.

Ha sido una entrada un poco larga pero he considerado que todos los ejemplos encajarían mucho mejor en una misma entrada de manera que pudieramos realizar un comparativa de los diferentes puntos de utilización de este poderoso mecanismo.

Importante: los snippets de código que aparecen a lo largo de esta entrada no están listos para ejecutarse en el REPL por lo que os aconsejo que, si quereis jugar con el código fuente, utilicéis aquel que está disponible aquí (realizaré el merge de esta rama sobre master tan pronto como me sea posible).

Hasta pronto!

Migue


lunes, febrero 20, 2012

Scala implicits (I)

Durante numerosas entradas hemos estado hablando del lenguaje de programación Scala, analizando algunas de sus principales características y/o la interacción con otros lenguajes: conceptos básicos, traits, parser combinators, combinando AspectJ y Scala, . . . Podéis encontrar todas las entradas relacionadas en el siguiente enlace: http://miguelinlas3.blogspot.com/search/label/Scala y un pequeño documento en el que se realiza una somera introducción al lenguaje: https://github.com/migue/blog-examples/tree/master/scala-intro

Durante las siguientes entradas me gustaría ahondar en algunas características más "avanzadas" del lenguaje como "type parametrization", "abstract members" o "implicitis" (estás últimas las abordaremos durante la entrada que nos ocupa).

El principal problema que se nos presenta cuando trabaja mos con una librería de terceros es la dificultad de extender y/o modificar el comportamiento de la misma. A lo largo de los diferentes lenguajes existentes podemos encontrar numerosas alternativas, cada una con sus ventajas e inconvenientes, como las open classes de Ruby o el ExpandoMetaclass de Groovy.

La respuesta de Scala al problema que nos ocupa son los implicits. El compilador intentará incluir en nuestro código fuente aquellos implicits que se encuentren disponibles con el objetivo de solventar cualquier "problema de tipos". De manera general, se rigen por las siguientes reglas:

  • Sólo estarán disponibles para el compilador aquellas definiciones que nosotros marquemos de manera explícita como implicit.
  • Los implicits deben estar disponibles en el ámbito de uso.
  • El compilador solamente intentará incluir un implicit de cada vez
  • El compilador no reemplazará código correcto.
  • ¿Dónde se utilizan los implicits?
    • Conversiones a un tipo determinado
    • Conversiones del objeto que recibe la llamada a un método
    • Parámetros de funciones
Hemos visto una pequeña introducción sobre la teoría de los implicits en Scala, las reglas que rigen el funcionamiento de los mismo y las aproximaciones que otros lenguajes utilizan. Puede que con este contenido la entrada se quede un poquito escueta pero he preferido dividirlo en dos entradas independientes por lo que en el próximo post realizaremos un ejemplo práctico y veremos las aplicaciones reales de esta potente (y a veces compleja) funcionalidad que el lenguaje nos brinda.

Hasta pronto!

Migue

miércoles, junio 29, 2011

AspectJ y Scala (y III)

Última entrada (en esta ocasión mucho menos ladrillo) de esta mini-comparativa que estamos llevando a cabo entre AspectJ y Scala (podéis consultar las anteriores entradas en los siguientes enlaces: AspectJ y Scala (I), AspectJ y Scala (II).

Hemos estado revisando cómo llevar a cabo la aplicación de varias funcionalidades como el uso de cachés o la gestión de transacciones mediante el uso de AspectJ y Scala, haciendo hincapié en las ventajas y desventajas de las dos aproximaciones. Nótese que hemos estado hablando de funcionalidad transversal como las transacciones y la caché pero no hemos hablado de otro tipo de funcionalidades las cuales abarcan un espectro mucho más amplio como por ejemplo FFDC (First Faiulure Data Capture), Profiling, gestión de la concurrencia, etc. En este tipo de situaciones el uso de Scala y HFOs resultaría mucho más tedioso que los ejemplos vistos en las entradas anteriores; derivando en los síntomas clásicos de funcionalidad "difuminada" por toda nuestra apliación.

¿Conclusión? El uso de cualquiera de las dos aproximaciones que hemos estado analizando dependerá, como en el 90 por ciento de todas las situaciones, del problema que estemos resolviendo, por lo que tendremos que escoger la "herramienta" más adecuada para el problema que se nos plantea.

¿Y por qué no un mixin de las anteriores? Esta es una de las alternativas que más me convence:
  • Compilamos nuestros programas Scala de la manera habitual.
  • Definimos nuestros aspectos para llevar a cabo la funcionalidad transversal deseada.
  • Realizamos un proceso de binary weaving (ya sea build time o load time)
Mediante el mecanismo anterior estaremos aplicando nuestros aspectos a nuestro código Scala compilado.

En mi cuenta de GitHub podréis encontrar dos proyectos Eclipse (scala-simple-service y aspectj-advice-scala) que deben ser utilizados de manera conjunta. Mediante estos dos pequeños proyectos de ejemplo se ilustra cómo podemos utilizar conjuntamente aspectos escritos en AspectJ que hagan advice sobre código Scala compilado.

Hasta pronto!


martes, junio 28, 2011

AspectJ y Scala (II)

En la entrada anterior analizamos algunas de las similitudes y diferencias existentes entre Scala y AspectJ, llevando a cabo una implementación de una caché extremadamente simple en ambos lenguajes, describiendo las ventajas y desventajas presentes en cada una de las aproximaciones. Durante esta entrada analizaremos un ejemplo de aplicación de una funcionalidad transversal como la gestión de transacciones. 

El ejemplo que aquí nos ocupa no persigue realizar una implementación detallada de control de transacciones sino simplemente ilustrar como podríamos aplicar una funcionalidad transversal mediante el uso de AspectJ o Scala.

Del mismo modo que en el post anterior, comenzamos por la aproximación basada en AspectJ. Para ello en primer lugar definimos una simple anotación que nos sirva como elemento de marcado de aquellas clases y/o métodos que se deben ejecutar bajo un contexto transaccional.

Una vez definida la anotación anterior definiremos un aspecto que sea capaz de detectar aquellos joint point en los que deseamos establecer un contexto transaccional. 

pointcut transactionalOperation() : execution(@Transactional * * (..) );

Con el pointcut anterior estaremos capturando las llamadas realizadas sobre cualquier método anotado con nuestra anotación @Transactional. Ahora necesitaremos definir un advice en el que llevamos a cabo la gestión de la transacción (nótese que este gestor de transacciones es completamente absurdo y simplemente imprime por pantalla la funcionalidad real que debería ser llevada a cabo por un gestor de transacciones operativo)


En muchas ocasiones todos los métodos de una clase necesitarán ser transaccionales, y no querremos anotar todos los métodos de dicha clase. En este supuesto podríamos utilizar anotaciones a nivel de clase que nos permitan seleccionar aquellos métodos que deseemos (por ejemplo, todos los métodos públicos).

Adicionalmente, podríamos hacer uso de los ITD y establecer la transaccionalidad a todas aquellas clases  que nosotros deseemos. Por ejemplo, supongamos que todos los nombres de nuestros servicios, los cuales deseamos que sean transaccionales, terminan con el sufijo Service. Podríamos, mediante el uso de un aspecto, añadir la anotación @Transactional a todos nuestros servicios.

declare @type : *Service : @Transactional ;

Dicho aspecto, en conjunción con el anterior, provocaría que todos nuestros servicios fueran transaccionales

En Scala, de nuevo, haremos uso de funciones de alto nivel (high order functions) de manera muy similar a la solución de la caché descrita en la entrada anterior:

Si deseamos que un método de uno de nuestros servicios sea transaccional no tendremos más que extender de la clase abstracta y utilizar la HOF anterior:

Si comparamos las soluciones aportadas por cada una de las dos alternativas tendremos que
  • En el caso de AspectJ no necesitamos anotar cada método de manera independiente puesto que podríamos hacer uso de anotaciones a nivel de clase. El nivel de granularidad que podemos alcanzar está definido a nivel de método (aunque tampoco es un problema excesivamente grande puesto que podríamos utilizar el patrón Extract Method para extraer la funcionalidad transaccional a un método sobre el cual podríamos aplicar nuestro aspecto).
  • En el caso de Scala necesitaremos recubrir nuestra lógica con la HOF definida en nuestro ejemplo anterior. En esta situación, el nivel de granularidad es notablemente superior puesto que podemos recubrir una simple parte de nuestro método
Durante esta segunda entrada hemos vuelto a confrontar Scala y AspectJ a la hora de aplicar una funcionalidad transversal, intentando plasmar las ventajas y desventajas de cada uno de ellos.

En la siguiente y última entrada de esta serie analizaremos cómo aunar lo mejor de ambos mundos en un ejemplo práctico que nos sirva como base para futuros ejemplos.

Como siempre podéis encontrar el código fuente de todos los ejemplos del blog (o casi todos :) ) en mi cuenta de GitHub, bajo el proyecto blog-examples.

Hasta pronto!

PD: perdón por el ladrillo de entrada :(.

martes, junio 21, 2011

AspectJ y Scala (I)

¿Podemos establecer alguna conexión entre estos dos mundos? Si nos detenemos por un instante veremos que comparten muchas más cosas de las que a simple vista podría parecer:
  • Son lenguajes de tipado estático.
  • Ambos producen código compatible con la máquina virtual de Java (JVM)
  • Las funciones de alto nivel de Scala comparten algunas características con los advices de AspectJ
  • Los traits de Scala comparten algunas características con el static crosscuting de AspectJ.

Durante el resto de la entrada que nos ocupa (y la siguiente) realizaremos una pequeña comparativa en la que pondremos de manifiesto el modo en que cada uno de estos lenguajes resuelve determinado tipo de problemas. Pongámonos manos a la obra.

Cachés

A través de este ejemplo se pretende presentar el modo en que ambos lenguajes solucionan el problema de aplicar una funcionalidad transversal en un punto determinado del código. Implementaremos un mecanismos de caché extremadamente sencillo de manera que podamos centrar nuestra atención en los aspectos relevantes de las alternativas que estamos planteando

En el caso de AspectJ se está tendiendo a utilizar las anotaciones como un elemento de marcado (y parece que está siendo recibido con una aceptación más que notable). Nuestro primer paso será definir la anotación con la que realizaremos el "marcado" de aquellos métodos que deseamos establecer como cacheados:

@Retention(RetentionPolicy.RUNTIME)
public @interface Cachable {
   String scriptKey () default "";
}


El atributo keyScript de la anotación anterior actuaría (en una caché real), como un pequeño lenguaje de scripting de manera que pudiera ser evaluado por la caché para generar la clave bajo la que se almacenará una determinada llamada (obviaremos esta parte para intentar no distraernos de nuestro principal objetivo).

Una vez definido el elemento de marcado, definiremos nuestro aspecto, el cual será capaz de capturar las llamadas de aquellos métodos anotados con @Cacheable y realizar la lógica necesaria de nuestra sencilla caché



En el caso de Scala llevaremos a cabo nuestra funcionalidad transversal mediante el uso de funciones de primer nivel (high order functions)

Gracias a la funcionalidad anterior, podemos pasar al método cache una función que será ejecutada en caso de ser necesario, devolviendo los valores de la caché en el supuesto de que haya sido calculado en un paso anterior.

Si comparamos las dos alternativas que hemos planteado hasta este momento:
  • En el caso de AspectJ, cada uno de los elementos que deseamos cachear tendremos que anotarlos con, valga la redundancia, la anotación que hemos definido anteriormente. Mientras tanto, en el caso de Scala, tendremos que recubrir cada uno de los métodos que deseamos cachear con la función de alto nivel (high order function).
  • En el caso de AspectJ estamos utilizando un "lenguaje" externo que actua como elementos de las claves de nuestra caché mientras que en el caso de Scala estamos utilizando el propio lenguaje de manera nativa, con el consiguiente beneficio que ello conlleva.
Esta ha sido nuestra primera aproximación a Scala y AspectJ. Durante las siguientes entradas analizaremos algunos ejemplos adicionales y veremos como podemos integrar lo mejor de ambos mundos.

Podéis encontrar el código fuente de los ejemplos anteriores en el siguiente repositorio de GitHub, concretamente en los proyectos AspectJCacheExample y ScalaCacheExample.

Hasta pronto!

    sábado, mayo 07, 2011

    Parser Combinators: Parsing simple bytecode

    Anteriormente, http://miguelinlas3.blogspot.com/2011/03/parser-combinators-un-poco-de-teoria.html, introducíamos de manera somera el concepto de parser combinators y parte de la teoría relacionada con los mismos. Durante esta entrada vamos a construir un sencillo parser que nos permita analizar un conjunto mínimo de instrucciones de la máquina virtual java (simplemente permitiremos la definición de funciones y la capacidad de ejecución de las mismas).

    No analizaremos en detalle el funcionamiento de la máquina virtual java ni el conjunto de instrucciones  de la misma (podríamos llevarnos algo más que unos cuantos posts :) ) dado que el objetivo principal de esta entrada es profundizar en el concepto de parser combinators y presentar un ejemplo de uso real de los mismos. Pongámonos manos a la obra.

    El código fuente que nos servirá como conductor de la entrada será el de la función factorial mostrado a continuación:

    static int fact(int);
    Code:
    Stack=2, Locals=3, Args_size=1
    0: iconst_1
    1: istore_1
    2: iconst_1
    3: istore_2
    4: iload_2
    5: iload_0
    6: if_icmpgt 19
    9: iload_1
    10: iload_2
    11: imul
    12: istore_1
    13: iinc 2, 1
    16: goto 4
    19: iload_1
    20: ireturn

    Podréis objener el código desensamblado como el mostrado anteriormente mediante el comando javap -v Class donde Class represente un archivo bytecode.

    El proceso de parsing se lleva a cabo mediante el uso de parser combinators (no perdamos de vista el objetivo de nuestra entrada ;) ) y presenta las siguientes características

    1. Para cada una de las diferentes tipos de instrucciones soportadas definimos un parser combinator que es el encargado de parsear este tipo de instrucciones.
    2. Cada uno de los parsers anteriores sabe cómo generar un objeto del tipo correspondiente. Se utiliza una jerarquía de tipos en la que cada una de las instrucciones bytecode soportadas está representada mediante una clase Scala.
    3. El parser global construye un AST (en este caso es un simple lista de objetos) y un conjunto de infraestructura adicional con el objetivo de implementar un intérprete que ejecute la secuencia de instrucciones obtenida (fuera del ámbito de esta entrada podéis echarle un vistazo al código fuente)
    ¿Cómo actúan nuestros diferentes parsers? Analicemos por ejemplo el componente encargado de parsear las instrucciones ISTORE

    lazy val beginInstructionFragment: Parser[Int] = integer <~ ":" ^^ { _.toInt}

    lazy val iStoreBytecode: Parser[List[BytecodeInstruction]] = beginInstructionFragment ~> "istore_" ~ integer ^^ {
        case name ~ value => new IStoreInstruction(name,value)  :: List()
      }

    El parser beginInstructionFragment es el encargado de parsear la primera parte de la instrucción, la que corresponde al lugar que ocupa la instrucción en el array de instrucciones. 

    El segundo parser hace uso del primero y además sabe cómo parsear el texto restante de manera adecuada. En este caso nuestro este analizador es capaz de parsear las instrucciones istore_N donde N representa un número entero positivo cualquiera y devolver un objeto (dentro de una lista) de tipo IStoreInstruction en el que se incluyen el nombre de la instrucción y el entero correspondiente.

    Como seguramente a muchos de los lectores de este post el fragmento de código anterior les suene un poco raro intentaremos analizar las diferentes opciones de combinaciones de parsers disponibles:
    • Si escribimos un parser en el que utilizamos una cadena, como por ejemplo "istore", el resultado es la propia cadena.
    • Podemos utilizar expresiones regulares, como por ejemplo, "[A-Z]".r . En este caso el resultado también sería la propia cadena
    • Si utilizamos el símbolo ~ (composición secuencial) el resultado del proceso de parseo será la combinación de ambos resultados. Por tanto, si A devuelve "X" y B devuelve "true", el resultado de A~B sería  ~("X","true") (el tipo de este resultado sería una case class de tipo ~ . Adicionalmente podemos hacer uso de los operadores <~ y ~> los cuales mantienen los valores a la izquierda y a la derecha respectivamente.
    • Otro elemento de combinación de parsers sería | . En este caso el valor de retorno sería el de aquel parser que pudiera parsear la entrada con éxito.
    • rep(P) o rep(P,separator) retornan una lista de las múltiples ejecuciones de P.
    • opt(P) retorna una instancia de tipo Option.
    Adicionalmente a la combinación de los parsers, necesitaremos un mecanismo que nos permita reescribir la salida de los mismos, en nuestro caso para instanciar los objetos de tipo necesario y configurar nuestro AST. Para ello tendremos que hacer uso del operador de transformación ^^.

    Una definición formar del operador anterior sería: Dado un parser P y una funcion f cualesquiera, una expresión del tipo P^^f implica que para cada resultado R de P el resultado de la expresión global es f(R) 

    En el ejemplo que estamos tratando nuestra función es un simple pattern matching que transforma la case class retornada por nuestra combinación de parsers en un objeto de tipo IStoreInstruction.

    No me ha resultado sencillo ponerlo por escrito y tampoco estoy seguro de que me haya explicado con la mayor claridad posible. Si estáis interesados os recomiendo que clonéis el repositorio alojado en GitHub

    git://github.com/migue/parser-combinators.git

    Hasta pronto!

    miércoles, marzo 30, 2011

    Parser Combinators: un poco de teoría

    Puede que en algunas ocasiones os hayais tenido que enfrentar con la necesidad de escribir un pequeño lenguaje que realice una tarea muy concreta (profundizaremos en el tema de los DSLs en un post futuro): como, por ejemplo, procesar determinados archivos de configuración de vuestra aplicación o la definición de interfaces de usuario de manera sencilla.

    Independientemente de las razones por las que estamos desarrollando este componente, necesitaremos un parser que nos ayude a transformar el lenguaje de entrada en una estructura de datos que nuestro programa pueda comprender y procesar. Algunas de las alternativas que se nos plantean son:
    • Escribir nuestro propio parser (conllevaría escribir también el analizador léxico). Si no somos expertos en la materia estaremos enfrentándonos a una tarea relativamente complicada.
    • Utilizar herramientas para la generación de parsers como Antlr,Bison o JavaCC entre otras muchas. En este caso la dificultad estriba en la necesidad de aprender a manejar una nueva herramienta/lenguaje e integrarlo en nuestro desarrollo y ecosistema.
    Durante esta entrada, y posiblemente la siguiente, vamos a presentar un enfoque alternativo a las dos opciones anteriores. En lugar de utilizar un DSL externo como podría ser el ofrecido por Antlr, vamos a utilizar un DSL interno. Dicho DSL estará formado por parser combinators (funciones y operadores definidos en Scala que servirán como base para la construcción de nuestros futuros parsers).

    Siendo originales :), imaginemos que deseamos cosntruir un parser de expresiones aritméticas de números enteros. En primer lugar, definamos la gramática de nuestro lenguaje:

    expr  ::= term {"+" term | "-" term}. 
    term  ::= factor {"*" factor | "/" factor}.
    factor  ::=  integer | "(" expr ")".

    El fragmento de código anterior representa una gramática libre de contexto (no vamos a profundizar en este tema porque tendríamos que escribir miles de posts) que modela nuestro lenguaje de expresiones aritméticas de números enteros. ¿Y ahora?

    Una vez definida la gramática anterior hemos llevado a cabo la tarea más complicada de todas. Si utilizais los parser combinators ofrecidos por Scala tendremos casi todo el trabajo sucio realizado. A modo de ejemplo:

    import scala.util.parsing.combinator._

    class IntegerArithmetics extends JavaTokenParsers {
       def expr: Parser[Any] = term~rep("+"~term | "-"~term)
       def term: Parser[Any] = factor~rep("*"~factor | "/"~factor)
       def factor: Parser[Any] = integer | "("~expr~")"
    }

    Si comparamos el código Scala con la definición en notación EBNF de nuestra gramática observaremos que podríamos inferir nuestro código fuente Scala sin más que realizar una serie de reemplazos en nuestra notación EBNF:
    1. Cada regla se convierte en un método por lo que tendremos que prefijarlas con def.
    2. El tipo de retorno de cada uno de los métodos anteriores es Parser[Any] (veremos en la siguiente entrada que significa esto) por lo que tendremos que cambiar el símbolo "::=" por ":Parser[Any] ="
    3. Insertar el símbolo ~ entre todos los elementos de cada una de las reglas (en la notación EBNF esto es implícito)
    4. La repetición se refleja mediante el uso de rep(...) en lugar de {...}
    5. El punto al final de cada regla no es necesario (podríamos poner ; si lo deseamos)

    Esto no ha sido más que una toma de contacto con el mundo de los parser combinators en Scala. En la siguiente entrada descubriremos cómo realizar construcciones más complejas, formatear la salida de manera que podamos construir las estructuras de datos requeridas para nuestro procesamiento o ejecutar nuestros parsers. 

    Para todo ello diseñaremos y construiremos un pequeño parser que nos permita analizar el bytecode de la máquina virtual una vez desemsamblado (para los más inquietos javap -v ClassFile (sin .class))

    miércoles, febrero 23, 2011

    Introducción a Scala

    El siguiente documento es un pequeño resumen (introductorio) sobre el lenguaje de programación Scala y alguno de sus beneficios. No es nada nuevo que no podáis encontrar en los libros de referencia :) pero a lo mejor a alguien le sirve para dar sus primeros pasos en este genial lenguaje de programación. 

    Espero que os guste:

    viernes, enero 28, 2011

    Scala: Clases y objetos

    Durante esta entrada analizaremos algunas de las características básicas del paradigma orientado a objetos presentes en Scala. Pongámonos manos a la obra:

    Del mismo modo que en todos los lenguajes orientados a objetos Scala permite la definición de clases en las que podremos añadir métodos y atributos:

    class MyFirstClass{
      val a = 1
    }

    Si deseamos instanciar un objeto de la clase anterior tendremos que hacer uso de la palabra reservada new


    val v = new MyFirstClass


    En Scala existen dos tipos de variables, vals y vars, que deberemos especificar a la hora de definir las mismas:

    • Se utilizará la palabra reservada val para indicar que es inmutable. Una variable de este tipo es similar al uso de final en Java. Una vez inicializada no se podrá reasignar jamás.
    • De manera contraria, podremos indicar que una variable es de clase var, consiguiendo con esto que su valor pueda ser modificado durante todo su ciclo de vida.

    Uno de los principales mecanismos utilizados que garantizan la robustez de un objeto es la afirmación de que su conjunto de atributos (variables de instancia) permanezca constante a lo largo de todo el ciclo de vida del mismo. El primer paso para evitar que agentes externos tengan acceso a los campos de una clase es declarar los mismos como private. Puesto que los campos privados sólo podrán ser accedidos desde métodos que se encuentran definidos en la misma clase, todo el código podría modificar el estado del mismo estará localizado en dicha clase.

    Por defecto, si no se especifica en el momento de la definición, los atributos y/o métodos, de una clase tienen acceso público. Es decir, public es el cualificador por defecto en Scala

    El siguiente paso será incorporar funcionalidad a nuestras clases; para ello podremos definir métodos mediante el uso de la palabra reservada def:

    class MyFirstClass{
      var a = 1
      def add(b:Byte):Unit={
    a += b
      }
    }


    Una característica importante de los métodos en Scala es que todos los parámetros son inmutables, es decir, vals. Por tanto, si intentamos modificar el valor de un parámetro en el cuerpo de un método obtendremos un error del compilación:

    def addNotCompile(b:Byte) : Unit = {
      b = 1  // Esto no compilará puesto que el 
     // parámetro b es de tipo val
      a += b
    }

    Otro aspecto relevante que podemos apreciar en el código anterior es que no es necesario el uso explícito de la palabra return, Scala retornará el valor de la última expresión que aparece en el cuerpo del método. Adicionalmente, si el cuerpo de la función retorna una única expresión podemos obviar la utilización de las llaves.

    Habitualmente los métodos que presentan un tipo de retorno Unit tienen efectos colaterales, es decir, modifican el estado del objeto sobre el que actúan. Otra forma diferente de llevar a cabo la definición de este tipo de métodos consiste en eliminar el tipo de retorno y el símbolo igual y englobar el cuerpo de la función entre llaves, tal y como se indica a continuación:
    class MyFirstClass {
      private var sum = 0
      def add(b:Byte) { sum += b }
    }

    En la siguiente entrada introduciremos el concepto de singleton objects. También intentaré que la apariencia de las entradas sea un poquito más atractiva utilizando el genial servicio ofrecido por http://pastie.org

    jueves, enero 27, 2011

    Scala: Primeros pasos (cont)

    En la entrada anterior comenzabamos nuestra pequeña aventura en Scala analizando las características principales del paradigma orientado a objetos y revisando alguna de las contribuciones que este lenguaje presenta al modelo. Durante la entrada que nos ocupa revisaremos brevemente los fundamentos del paradigma funcional y analizaremos cómo Scala incorpora y unifica lo mejor de ambos paradigmas en un lenguaje estáticamente tipado.

    La programación funcional es un paradigma en el que se trata la computación como la evaluación de funciones matemáticas y se evitan los programas con estado y datos que puedan ser modificados. Se adopta una visión más matemática del mundo en el que los programas están compuestos por numerosas funciones que esperan una determinada entrada y producen una determinada salida y, en muchas ocasiones, otras funciones.

    Otro de los aspectos de la programación funcional es la ausencia de efectos colaterales gracias a los cuales los programas desarrollados son mucho más sencillos de comprender y probar. Adicionalmente, se facilita la programación concurrente, evitando que se convierta en un problema gracias a la ausencia de cambio.

    Los lenguajes de programación que soportan este estilo de programación deberían ofrecer algunas de las siguientes características:
    • Funciones de primer nivel
    • Closures
    • Asignación simple
    • Evaluación tardía
    • Inferencia de tipos
    • Optimización del tail call
    • Efectos monadic

    Es importante tener claro que Scala no es un lenguaje funcional puro dado que en este tipo de lenguajes no se permiten las modificaciones y las variables se utilizan de manera matemática (un ejemplo de lenguaje funcional puro sería Haskell).

    Durante las dos últimas entradas hemos centrado nuestra atención en algunos de los aspectos más teóricos por lo que posiblemente a los (pocos) que hayáis leído esto se os haya hecho demasiado aburrido.

    Esperemos que la siguiente entrada sea más entretenida.

    martes, enero 18, 2011

    Scala: Primeros pasos

    Me gustaría comenzar una serie de post relativos al lenguaje de programación Scala para que, si alguien se pasa por aquí y tiene interés, pueda dar sus primeros pasos en este fantástico lenguaje. Dejar claro desde el principio que no soy, ni por asomo,  un experto :) , aunque intentaré explicarme con la mayor claridad que me sea posible.


    Para comenzar, un poquito de teoría e historia:

    Scala es un lenguaje de propósito general diseñado para expresar los patrones de programación más comunes de una manera sencilla, elegante y segura. Integra de manera sencilla características de orientación a objetos y lenguajes funcionales, permitiendo de este modo que los desarrolladores puedan ser más productivos. Su creador, Martin Odersky, y su equipo comenzaron el desarrollo de este nuevo lenguaje en el año 2001, en el laboratorio de métodos de programación en EPFL (École Polytechnique Fédérale de Lausanne)

    Scala hizo su aparación pública sobre la plataforma JVM (Java Virtual Machine) en enero de 2004 y unos meses después haría lo propio sobre la plataforma .NET. Aunque se trata de un elemento relativamente novedoso dentro del espacio de los lenguajes de programación, ha adquirido una notable popularidad la cual se acrecenta día tras día.


    Orientación a objetos


    Un lenguaje orientado a objetos "puro" debería presentar las siguientes características:

    Ocultación de información.

    • Herencia.
    • Polimorfismo/Enlace dinámico.
    • Todos los tipos predefinidos son objetos.
    • Todas las operaciones son llevadas a cabo mediante en envío de mensajes a objetos.
    • Todos los tipos definidos por el usuario son objetos.
    • Scala da soporte a todas las características anteriores mediante la utilización de un modelo puro de orientación a objetos muy similar al presentado por Smalltalk (lenguaje creado por Alan Kay sobre el año 1980).

    De manera adicional Scala añade algunas innovaciones en el espacio de los lenguajes orientados a objetos:
    • Composición modular de mixin. Mecanismo que permite la composición de clases para el diseño de componentes reutilizables evitando los problemas presentados por la herencia múltiple. Similar a los interfaces Java y las clases abstractas. Por una parte se pueden definir múltiples "contratos" (del mismo modo que los interfaces). Por otro lado, se podrían tener implementaciones concretas de los métodos.
    • Self-type. Los mixin no dependen de ningún método y/o atributo de aquellas clases con las que se está entremezclando aunque en determinadas ocasiones será necesario hacer uso de las mismas. Esta capacidad es conocida en Scala como self-type
    • Se ha optado por mantener el término original disponible en la documentación del lenguaje.
    • Abstracción de tipos. Existen dos mecanismos principales de abstracción en los lenguajes de programación: la parametrización y los miembros abstractos. Scala soporta ambos estilos de abstracción de manera uniforme para tipos y valores
    Vamos a dejar la entrada en este punto para no convertirla en un ladrillo (más aun de lo que ya  es) y en el próximo post analizaremos, de manera general, las características generales del paradigma funcional.