Mostrando entradas con la etiqueta Software Programming. Mostrar todas las entradas
Mostrando entradas con la etiqueta Software Programming. 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

lunes, enero 02, 2012

Algunos libros para comenzar el año

Unos cuantos libros para comenzar el año de la mejor manera posible: aprendiendo cosas nuevas.

The Garbage Collection Handbook. Este libro ofrece una visión actualizada del estado del arte de los sistemas de recolección de basura y la gestión automática de memoria. He leido varias críticas de Gil Tene (CTO de Azul Systems) y en todas ellas pone al libro por las nubes.

No he tenido ocasión de leerlo, todavía, pero está en las primeras posiciones de la lista de libros que me gustaría leer este comienzo de año.




Para aquellos que estéis interesados en los sistemas distribuidos y/o trabajeis con ellos aquí podréis encontrar una magnífica y extensa referencia sobre este tipo de sistemas: fundamentos, algoritmos , etc.

No estoy seguro si ya he hablado de este libro con anterioridad pero la verdad es que se trata de una magnífica y reconfortante lectura en la que se pueden aprender infinidad de cosas.



Excelente lectura si os gusta conocer los "internals" del sistema operativo Linux y cómo funcionan la mayoría de sus principales subsistemas: gestión de procesos, gestión de memoria, etc. Posiblemente nunca escribamos ni una línea de código relacionada con el núcleo de este sistema operativo pero estoy convencido de que esta lectura puede ayudarnos como desarrolladores a escribir mejores programas.









Hasta pronto!

lunes, noviembre 28, 2011

Algunos libros sobre Java y JVM

Durante estas últimas semanas he tenido la ocasión de terminar varios de los libros que tenía pendientes así como de realizar alguna nueva adquisición que, de momento, está encolada en mi lista de lectura.

Me gustaría aprovechar esta entrada para compartir con vosotros algunos de los libros anteriores que, bajo mi punto de vista, podrían resultar interesantes para tod@s aquell@s que os dediquéis, de algún modo, a trabajar con la plataforma Java (lenguaje y/o máquina virtual).

El primero de ellos es Java Performance from The Java Series. Escrito por Charlie Hunt, JVM performance lead engineer en Oracle, y  Binu John, senior performance engineer en Ning. Ha sido mi última adquisición y únicamente he podido leer los dos primeros capítulos pero creo que estamos ante uno de las lecturas obligatorias para tod@s aquell@s que se dedican y/o les gusta el mundo del rendimiento de aplicaciones sobre la JVM.

JVM Tuning, profiling, mediciones, herramientas, escritura de benchmarks, internals de la JVM, algoritmos de recolección de basura y tuneo de los mismos, gestión de la memoria,  . . .  son sólo algunas de las temáticas tratadas en este magnífico manuscrito.


Otro magnífica referencia  relativa al mundo Java: The well-grounded Java developer. Se trata de un early access por lo que sólo he podido leer los capítulos disponibles hasta el momento. Pensado para desarrolladores con unos sólidos conocimientos de Java el libro abarca un amplio espectro de temáticas como pueden ser:
  • Algunas de las novedades de Java 7
  • Nuevo mecanismo de IO y concurrencia
  • Classfiles y bytecode
  • Inyección de dependencias
  • Lenguajes alternativos en la JVM: Clojure, Groovy y Scala
  • Integración continua
  • . . .
En lineas generales creo que se trata de una lectura recomendada (a la espera de que se publique la segunda mitad del libro). Los capítulos pueden ser leidos de manera independiente, saltándose aquellos en los que ya se conoce de manera profunda la temática tratada en el mismo.

Un libro que había leido hace tiempo y que he vuelto a leer hace  unas semanas ha sido Java Concurrency in Practice. Poco puedo decir de este libro que no se haya dicho ya. Otra de las lecturas obligatorias para todos aquellos desarrolladores que trabajen con el lenguaje Java.

Ofrece una visión práctica de cómo se deben escribir programas escalables y seguros aprovechando la capacidad de las nuevas arquitecturas hardware.









Functional Programming for Java Developers. Escrito por Dean Wampler. Es un libro en el que se introducen los conceptos básicos de la programación funcional (que está "renaciendo" gracias a lenguajes como Scala o Clojure, además de sus beneficios a la hora de escribir programas concurrentes entre otras muchas cosas).

Es un libro muy cortito aunque personalmente no me ha entusiasmado demasiado. Si no tenéis ninguna experiencia en programación funcional puede ser un buen punto de partida. No se cubren mónadas, laziness ni combinators.

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

martes, junio 22, 2010

Aspect Oriented Programming Intro Guide

Hace tiempo que habíamos comenzado una serie de post relativos a la programación orientada a aspectos y los tenemos dispersos a lo largo de diferentes entradas. He decido recolectar todos los post publicados junto a los que estaban por venir y hacer una sola entrega.

Espero que os guste:


Está escrito en docbook por lo que si alguien está interesado en los fuentes no tiene más que pedírmelos.

Hasta pronto!

sábado, junio 19, 2010

AOP: crosscutting (II)

Durante la última entrada de la serie relacionada con la programación orientada a aspectos analizamos el modo en el que se puede alterar el comportamiento de un sistema, analizando las categorías de advices y realizando una comparativa con los métodos de una clase. A lo largo de esta entrada realizaremos un análisis en profundidad de los advices.

Before advice
Este tipo de advices se ejecutan antes de la ejecución del joint point sobre el que actúan. En el siguiente ejemplo:

              before():execution(@Secured * * (..)){
            // asegurarse de que el usuario puede realizar la operación
              }                   

el advice realiza una comprobación de seguridad antes de que se produzca la ejecución de cualquier método anotado con Secured.

En caso de que el advice dispare un excepción, el joint point no se ejecutará. Este tipo de advices son comúnmente utilizados en aspectos tales como seguridad o trazabilidad.
After advice

Se ejecutan después de la ejecución del joint point sobre el que actúan.Dentro de esta categoría, AspectJ ofrece tres tipos de advices:
  • Ejecución del advice independientemente del resultado de la ejecución del joint point.
  • Ejecución del advice única y exclusivamente si el joint point ha finalizado correctamente.
  • Ejecución del advice después que el joint point haya disparado una excepción.
Veamos en detalle cada uno de los tres tipos anteriores:

1.- Advice After
Este tipo de advices se ejecutan independientemente del resultado de la ejecución del joint point sobre el que actúan.Habitualmente se conoce a este tipo de advices como after finally puesto que su semántica es similar a la de un bloque finally.

El siguiente advice:
                after(): call(@Logging * ServiceManager.*(..)){
                   // registrar el resultado de la operación
               }

registra el resultado de todas las operaciones de la clase ServiceManager que estén marcadas con la anotación Logging, independientemente si retornan correctamente o terminan su ejecución de forma inesperada mediante el disparo de una excepción.

2.-Advice After Returning
En muchas ocasiones,será necesario ejecutar el código de nuestro advice única y exclusivamente cuando la ejecución del joint point haya terminado de forma correcta. Continuando con el ejemplo anterior:
               
            after () returning: call(@Logging * ServiceManager.*(..)){
              // registrar el resultado de la operación
            }

se seguirá registrando el resultado de las operaciones, siempre y cuando, la ejecución haya terminado correctamente, sin el disparo de ninguna excepción.

AspectJ ofrece una pequeña variante para este tipo de advices:

after() returning (ReturnType returnObject)

gracias a la cual se permite recuperar el objeto retornado por la ejecución del joint point dentro del advice. Veamos un pequeño ejemplo ilustrativo:
                           
        after() returning (java.sql.Connection connection):
            call(java.sql.Connection DriverManager.getConnection(..)){
                    System.out.println("Se ha recuperado la conexión "
                                   + connection);
       }

Es importante tener claro que no se puede retornar un objeto nuevo (sí puede ser modificado pero no retornar uno nuevo).

3.- Advice After Exception
Este tipo de advices son similares a los descritos en el apartado anterior. En este caso, el advice se ejecutará única y exclusivamente cuando el joint point dispare una excepción. Presentan la siguiente estructura:
                            
after() throwing:execution (* ServiceManager+.*(..))
                       
El advice del ejemplo anterior se ejecutará siempre y cuando algún método de la clase ServiceManager (o alguna de sus subclases) dispare una excepción. En el supuesto de que la ejecución del joint point termine correctamente, este tipo de advices no serán ejecutados.

Al igual que los advices del apartado anterior, AspectJ ofrece un modo de recuperar la excepción que ha sido disparada por el joint point de manera que esté disponible en el cuerpo del advice. Siguiendo una sintaxis similar a la anterior, tendríamos:
                          
after() throwing (ExceptionType exceptionObject):
                       
Un after throwing advice nunca podrá tragarse la excepción, por lo que seguirá subiendo por la pila de llamadas hasta llegar al objeto que realizó la invocación del joint point.

Around advice
Este clase de advices engloban al joint point, pudiendo ejecutar la lógica del mismo un número indefinido de veces. Incluso pueden omitir la ejecución del propio joint point. Algunos de los usos principales de este tipo de advices son los siguientes:
  • Ejecución de lógica adicional antes y después de la ejecución de un joint point, como por ejemplo, acciones de profiling.
  • Omitir la ejecución original, y realizar otra en su lugar, como por ejemplo, operaciones con cachés.
  • Envolver la operación con el objetivo de aplicar una política de gestión de excepciones. Un ejemplo de este uso sería la gestión de transacciones.
Este advice ofrece una potencia superior a todos los advices vistos hasta el momento, puesto que podrían sustituir a los anteriores. De todos modos, se considera una buena práctica utilizar el advice más sencillo que cumpla las necesidades de la tarea que necesita ser llevada a cabo.

Si desde el around advice se desea llevar a cabo la ejecución del joint point, será necesario hacer uso de la palabra reservada proceed() dentro del cuerpo del advice. Recuérdese que, puesto que la invocación de proceed() ejecuta el joint point, deberán pasarse el mismo número de argumentos que han sido recolectados por el advice.
Asimismo, puesto que la invocación de proceed() supone la ejecución del joint point, el valor de retorno será el retornado por éste último.

A continuación se adjunta un pequeño ejemplo de utilización de advices de este tipo:
                          
        void around(User user,int credits)
                   throws InsufficientCreditsException:
                call(* User.pay*(int)) && target(user) & &  args(credits){
                    try
                    {
                        proceed(user,credits);
                    }catch(InsufficientCreditsException ex){
                        if(!processException()){
                            throw ex;
                        }
                    }

Analicemos en detalle la construcción anterior:
  1. El pointcut selecciona cualquier llamada a los métodos de la clase User cuyo nombre comience por pay y disparen una excepción de tipo InsufficientCreditsException.
  2. La segunda parte del pointcut recolecta el contexto del joint point: el usuario sobre el que se está realizando la llamada y el número de créditosque se están pasando como argumento del método que se está ejecutando.
  3. En el cuerpo del advice, se engloba la ejecución del método con un bloque de gestión de excepciones, para realizar una protección adicional en caso de que se produzca una excepción. En el caso de que la protección adicional no sea correcta, la excepción será disparada de nuevo.
Todos los around advices deben declarar un valor de retorno (pudiendo ser void). Habitualmente el tipo de retorno de éstos se corresponde con el tipo de retorno de los joint points sobre los que está actuando.
En algunas ocasiones, todos los joint points sobre los que actúa el advice no presentan el mismo tipo de retorno, como puede ocurrir cuando estamos añadiendo soporte transaccional a diferentes  operaciones. En estas situaciones el tipo de retorno que debe declarar el advice será Object. AspectJ acomodará el valor de retorno de acuerdo a las  siguientes reglas:
  • Si se está retornando un tipo primitivo, AspectJ realizará el boxing/unboxing correspondiente. Esta característica es similar a la incluida a partir de Java 5, pero AspectJ no precisa de dicha versión de Java para realizar la operación.
  • En el caso en el que el tipo de retorno no sea primitivo, AspectJ realizará los casts oportunos antes de retornar el valor.
Muchas ocasiones es necesario acceder a los objetos que conforman la ejecución del joint point para que el advice pueda llevar a cabo la lógica correspondiente. Por tanto, los pointcuts, necesitan exponer el contexto disponible en la ejecución del joint point de modo que pueda estar disponible en el cuerpo del advice. Dicho contexto puede definirse de dos modos diferentes:
  • Objetos (incluyendo los tipos primitivos) que conforman el joint point.
  • Anotaciones asociadas al joint point.
La siguiente tabla describe el conjunto de pointcuts que AspectJ ofrece para recuperar el contexto en los joint points.

Table 1.14. Pointcuts para recuperar el contexto en un joint point
Pointcut Contexto recuperado
this(obj) Objeto this en el joint point que se está
ejecutando
target(obj) Objetivo de la llamada en el joint
point
que se está ejecutando. En el caso de un
joint point de una
llamada a un método, el target será el objeto que realiza la llamada.
Para la ejecución de un método, el target será el objeto this. En los accesos a campos, el target será el objeto que se está
accediendo. En el resto de joint
points
no existe un target disponible
args(obj1,obj2,...) Objetos que representa los argumentos en el joint point. Para las
llamadas/ejecuciones de métodos/constructores, recupera los
argumentos de los mismos. En el caso de los manejadores de
excepciones, recupera la excepción producida. Para los accesos
en modo escritura a un campo, recupera el nuevo valor del
campo.
@this(annot) Anotación asociada con el tipo del objeto this del joint point
@target(annot) Anotación asociada con el tipo del objeto target del joint point
@args(annot1, annot2,
...)
Anotación asociada con el tipo de los argumentos del joint point
@within(annot) Anotación asociada con el tipo "enclosing" del joint point
@withincode(annot) Anotación asociada con el método "enclosing" del joint point
annotation(annot) Anotación asociada con el asunto actual del joint point.

Durante la próxima entrada analizaremos el concepto de Aspecto.

Hasta pronto!

domingo, abril 18, 2010

Some signatures examples

En la última entrada dedicada a AOP (podeis verla aquí ) analizamos de manera teórica las características básicas del lenguaje de definición de pointcuts. A continuación realizaremos una serie de ejemplos que intentarán clarificar los conceptos teóricos vistos en anteriores entradas.

  • Signaturas de tipos
Table 1.2. Ejemplos de signaturas de tipos
Patrón de la signaturaDescripciónEjemplos de tipos concordantes
AstVisitorEl tipo AstVisitorSólo el tipo AstVisitor concuerda (ni tipos base ni derivados)
*AstVisitorCualquier tipo cuyo nombre termine en AstVisitorPor ejemplo, HighlightingAstVisitor o SemanticAstVisitor concuerdan con el patrón
java.*.DateEl tipo Date en cualquier subpaquete directo del paquete javajava.util.Date o java.sql.Date son ejemplos de tipos concordantes
javax..*Cualquier tipo en el paquete javax y en sus subpaquetes (tanto directos como indirectos)Cualquier tipo en el paquete javax.security así como cualquiera de sus subpaquetes indirectos como javax.security.auth.login
javax..*ModelTodos los tipos en el paquete javax (subpaquetes directos e indirectos) cuyo nombre termine en Model, y todos los subtipos de estos.TableModelTreeModel y sus subtipos como DefaultTreeModel oDefaultTableModel.



  • Signaturas de tipos: anotaciones
Table 1.3. Ejemplos de signaturas de tipos: anotaciones
Patrón de la signaturaDescripciónEjemplos de tipos concordantes
@Secured UserEl tipo User anotado con la anotación Secured@Secured class User{. . .}
@Entity *Cualquier tipo anotado con la anotación Entity@Entity class Section {. . .},@Entity class Report {. . .}
@Transactional* Manager+El tipo Manager y cualquiera de sus clases derivadas que estén anotados con una anotación cuyo nombre comience por Transactional@TransactionalDefault class Manager{},@TransactionalOracle class OracleManager extends Manager{}




  • Signaturas de tipos: generics
Table 1.4. Ejemplos de signaturas de tipos: generics
Patrón de la signaturaDescripciónEjemplos de tipos concordantes
Map El tipo Map cuyo primer argumento genérico está fijado a Integer y el segundo aStringEn este caso únicamente concordará el tipo Map
*Cualquier tipo genérico cuyo único argumento genérico sea de tipo UserCollection,List, . . .
CollectionEl tipo Collection con un parámetro de tipo User o derivadoCollectionCollection, . . .
CollectionEl tipo Collection, cuyo parámetro será uno de los tipos base de UserCollectionCollection, asumiendo que, User extiende o implementa, directa o indirectamente, Serializable y BaseUser


  • Combinando pointcuts: operadores
Table 1.5. Signaturas de tipo: operadores
Patrón de la signaturaDescripciónEjemplos de tipos concordantes
!CollectionCualquier tipo excepto CollectionUser,ModelList (aunque sea subclase de Collection)
Set || MapLos tipos Set o MapLos tipos Set y Map únicamente
!@TransactionalCualquier tipo que se encuentre anotado por la anotación Transactionalclass NonTransactionalManager{}
@Serializable @Encrypted *Cualquier tipo que se encuentre anotado por las dos anotaciones@Serializable @Encrypted class UserCredentials{}
(@Serializable || @Encrypted) *Cualquier tipo que esté anotado por alguna de las dos anotaciones@Serializable class User {}


  • Signaturas de métodos y constructores
Table 1.6. Signaturas de métodos
Patrón de la signaturaDescripciónEjemplos de métodos concordantes
public void User.set*(*)Cualquier método público de la clase User cuyo nombre comience por set, cuyo tipo de retorno sea void, y que espere un único argumentoclass User{ public void setName(String name){} }
public void User.*()Cualquier método público de la clase User cuyo tipo de retorno sea void y que no espere argumentosclass User { public void updateInfo(){}}
public * User.*()Cualquier método público de la clase User que no espera argumentos y retorna cualquier tipoclass User{ public UserInfo getUserInfo(){}}
public * User.*(..)Cualquier método público de la clase User que retorna cualquier tipo, y espera cualquier número y tipo de argumentos (incluido cero)class User{ public UserInfo getUserInfo(){},public Date updatesBetween(Date first,Date last){}}
* *.*(..) o * * (..)Cualquier método independientemente de su tipo, del tipo de retorno, de su nombre y de los argumentos que espereCualquier método del sistema
!public * User.*(..)Cualquier método que no sea público (privado,protegido o paquete)class User {protected getUserInfo(){}}
* * (..) throws Recognition ExceptionCualquier método que declare que puede lanzar una excepción de tipoRecognitionExceptionclass AntlRBasedParser { public void parser(String file) throws RecognitionException}
* User+.*(..)Cualquier método en la clase User y sus subclasesclass UserDetails extends User {}
User UserService.*(..)Cualquier método del tipo UserService cuyo tipo de retorno sea User. Si alguna de las subclases sobrescribe el tipo de retorno con un subtipo de User (mediante el tipo de retorno covariante introducido en Java 5) también será seleccionado.class UserService{public User retrieveUser(String name){}} class DetailUserService extends UserService{public DetailedUser retrieveUser(String name){}}

La signatura en el caso de los constructores difiere de la signatura de métodos en los siguientes aspectos:
  • Los constructores no tienen tipo de retorno por lo que no se permite utilizar el valor de retorno en la especificación de un pointcut de este tipo.
  • Dado que los nombres de los constructores no pueden ser libres (tienen que utilizar el mismo nombre de la clase), la parte de la signatura destinada al nombre será sustituida con la palabra new.
  • Por último, puesto que los constructores no pueden ser estáticos, no se podrá utilizar la palabra reservada static
  • Signaturas de campos
Table 1.7. Signaturas de campos de clase
Patrón de la signaturaDescripciónEjemplos de campos concordantes
private String User.usernameCampo privado (tanto campo de una instancia como estático) de la clase Userclass User { private String username;}
* User.*Cualquier campo de la clase User independiente de sus calificadores, tipo y nombre.class User{private String username;protected int credits;private UserDetails details;}
* User+.*Cualquier campo de la clase User y todas sus subclases, independientemente de sus calificadores, tipo y nombre.El ejemplo de la fila anterior o class SpecificUser extends User {private String address;}


sábado, abril 10, 2010

Software Disasters

De sobra es sabido por todos que algunos desastres históricos han sido ocasionados por errores en el software.

La siguiente presentación resume alguno de los más conocidos y describe, de manera concreta, dos ejemplos: un fallo de división en un chip de Intel, y un fallo en un sistema de detección de misiles americano.


Hasta pronto!

miércoles, diciembre 16, 2009

Groovlets

Me imagino que la gente dedicada al desarrollo web estará acostumbrada al concepto de servlet, y posiblemente, hayan escrito muchos de ellos.

Voy a mostraros una forma sencilla de escribir y configurar servlets en el lenguaje Groovy. Esta funcionalidad compilará nuestros archivos .groovy en el bytecode correspondiente, cargará la clase, y la cacheará, hasta que se produzca un cambio en el archivo groovy inicial.

El primer paso que vamos a dar es añadir el soporte para los Groovlets, para lo que necesitaremos añadir en el archivo web.xml de la aplicación web el siguiente fragmento de código:


<servlet><servlet-name>Groovy</servlet-name>
<servlet-class>groovy.servlet.GroovyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Groovy</servlet-name>
<url-pattern>*.groovy</url-pattern>
</servlet-mapping>
Ahora estamos en disposición de escribir un servlet en el lenguaje Groovy (llamemoslo hello.groovy). Veamos un ejemplo sencillito:



if (!session) {
  session = request.session(true)
}

if (!session.counter) {
      session.counter = 1
}

html.html {    // utilizamos del MarkupBuilder 
               // para construir una página tonta de respuesta
  head {
      title("Esto es un Groovy Servlet")
  }
  body {
    p("Bienvenido, ${request.remoteHost}: ${session.counter}! ${new Date()}")
  }
}


// incrementamos el contador en la session
session.counter = session.counter + 1

En el servlet (groovlet) anterior existen una serie de variables implícitas tales como, request, response, context, application, session, params, headers, out, sout y html.

Un paso adicional de configuración, sumamente importante, para poner nuestro groovlet en marcha, consiste en incluir las dependencias necesarias. La opción más sencilla es incluir el archivo groovy-all-XXX.jar en el directorio WEB-INF/lib de la aplicación web.

Si, por ejemplo, colocamos los archivos .groovy en el raiz de nuestra aplicación el servlet de groovy se encargará de compilar los archivos. En el caso de tomcat podríamos editar el archivo tomcat/conf/server.xml del siguiente modo:




<Context path="/groovy" docBase="c:/groovy-servlet-base"/>



Podríamos acceder al archivo creado anteriormente mediante la siguiente ur:l http://localhost:8080/groovy/hello.groovy


Hasta pronto,
Migue

viernes, noviembre 13, 2009

Definición de DSL

Los DSL's son lenguajes de programación específicos desarrollados para resolver problemas en un dominio especializado.

El dominio anterior podría ser multitud de cosas diferentes, por ejemplo, podría ser relativo a la industria: inteligencia analítica, banca, seguros, marketing, . . . o relativo a la tecnología: JEE,.Net, servicios de mensajes, arquitectura o bases de datos.

Existe tres tipos diferentes de DSL: externos, internos, y workbench languages
donde cada uno de los anteriores tiene una audiencia definida, y, por normal general, no suelen solaparse:
  • Los DSL externos utilizan un lenguaje diferente al de la aplicación que los está utilizando. Generalmente tienen un lenguaje específico, aunque en otras ocasiones, utilizan otra sintáxis diferente (XML es el ejemplo más claro). Algunos ejemplos de DSL externos que seguramente os puedan sonar son: SQL,awk o los ficheros xml de configuración de herramientas como Hibernate o Spring.
  • Los DSL's internos utilizan el mismo lenguaje de propósito general más utilizado en el sistema, aunque haciendo un uso limitado del mismo. Se utiliza un subconjunto de las construcciones del lenguaje, enfocadas a un aspecto determinado de la aplicación. Tanto Groovy como Ruby han generado una gran cultura en la construcción de DSL's. Mucha gente ve los frameworks más populares asociados a los dos lenguajes anteriores (Grails y Rails) como una colección de DSL's.
  • Language Workbenches (este término ha sido acuñado por Martin Fowler) son IDE's diseñados para la construcción de DSL's. Estas herramientas permitirán definir la sintaxis del lenguaje, junto a editores y generadores. Los primeros nos permitirán disponer de sofisticados entornos de programación para los DSL's, de manera idéntica a los actuales IDE's. Ejemplos de este tipo pueden ser los proyectos de eclipse de GMF o las herramientas de ANTLR para Eclipse.
En próximos post intentaremos profundizar en el tema.

Hasta pronto!