Durante los últimos meses he estado trabajando en un proyecto utilizando Scala como lenguaje de programación principal. La mezcla de programación orientada a objectos y programación funcional que permite Scala resulta interesante, pero cada vez estoy más convencido de que la verdadera potencia del mismo se consigue cuando se aplica un estilo puramente funcional. Al fin y al cabo, si únicamente queremos un lenguaje con algunas características funcionales que hagan nuestro código un poco más claro y limpio, hay otras opciones que nos dan eso (véanse la última versión de Java o Kotlin), sin las complejidades con las que hay que lidiar en Scala.

Supongo que escribiré en el futuro un artículo más elaborado sobre el tema, pero dejadme que hoy me centre en algo que llevo viendo bastante cuando examino código en Scala, casi siempre cuando éste ha sido escrito por desarrolladoras que tienen amplia experiencia en algún lenguaje orientado a objetos (como Java) y empiezan a escribir código en Scala: cómo hacer inyección de dependencias.

Empezaré proponiendo un escenario muy básico para analizar el problema y algunas de las soluciones que proporciona el lenguaje. Supongamos que queremos crear un servicio de informes que nos devuelva los balances del último mes de todas las cuentas bancarias abiertas por un cliente. Nuestros clientes están relacionados con todas cuentas que tienen abiertas (serían nuestro Aggregate Root si hablásemos de Domain Driven Design) y se identifican por una secuencia alfanumérica de longitud diez. Dichas cuentas, mantienen todos los movimientos ordenados por su fecha de ejecución. Un balance contiene, en definitiva, el número que identifica a una cuenta, la fecha hasta donde el balance ha contabilizado y una cantidad de dinero.

De esta forma definimos nuestro servicios con la siguiente operación:

type Id = String

case class Balance(acNo: String, balanceAt: Date, amount: Money)

trait Reporting {
   def monthlyBalance(customerId: Id, year: Int, month: Int): List[Balance]
   ...
}

Los clientes son accessibles dados sus identificadores vía un repositorio.

trait CustomerRepository {
   def retrieve(customerId: Id): Option[Customer]
   ...
}

Nuestra implementación del servicio de informes necesitará una instancia válida del repositorio de clientes para poder tener acceso al cliente solicitado y hacer el cáculo de los balances de sus cuentas. Hasta el momento, lo que he visto es que un porcentaje bastante algo de desarrolladores que vienen de otros lenguajes como Java harán algo así para implementar dicho servicio:

class ReportingInterpreter(repo: CustomerRepository) implements Reporting {
   override def monthlyBalance(customerId: Id, year: Int, month: Int): List[Balance] = //do something here
   ...
}

A muchos desarrolladores acostumbrados al paradigma orientado a objetos, esta implementación le parecerá bastante natural (y acertada). Sin embargo, desde el punto de vista de la programación funcional, tiene el principal inconveniente de que en ninguna parte se hace explícito que la operación monthlyBalance necesita de un repositorio de clientes. Alguien que utilice esta función nunca sabrá que, sin un repositorio de clientes, dicha operación no podrá ser ejecutada. El repositorio de clientes es parte del contexto de ejecución de la clase ReportingInterpreter, a quien se lo estamos inyectando a través de su constructor.

Esta situación puede mejorarse un poco aplicando un diseño algo más funcional a nuestra función monthlyBalance. ¿Por qué no pasarle explícitamente el repositorio de clientes como un párametro de entrada a dicha función? De esta forma tendríamos:

trait Reporting {
   def monthlyBalance(customerId: Id, year: Int, month: Int, repo: CustomerRepository): List[Balance]
   ...
}

Ahora ya hemos hecha explícita la dependencia de la función monthlyBalance con el repositorio de clientes y podemos decir que nuestro diseño es más funcional. Sin embargo, todavía hay algunas desventajas con esta solución. Una de ellas es que añadimos un parámetro extra a nuestra función, lo que hace que ésta sea más difícil de ser llamada. Si nuestro módulo Reporting crece y ofrece muchas otras funciones que también dependen de CustomerRepository (y es probable que dichas funciones también dependan de este repositorio), siempre tendremos que pasar éste como último parámetro en todas nuestras llamadas a funciones del módulo Reporting. Esto resulta especialmente engorroso si queremos componer funciones de dicho módulo para ofrecer nuevas funciones.

¿Cómo podemos mejorar esta situación? Teniendo en cuenta que intentamos utilizar un estilo de programación cada vez más funcional, podemos hacer uso de la idea de que en un lenguaje funcional las funciones son objetos de primera clase que pueden ser utilizados como cualquier otro tipo de objeto. Por ejemplo, pueden ser pasados como parámetros de entrada a otras funciones (creando de esta forma funciones de orden superior). Otra cosa que a veces olvidamos quienes venimos de lenguajes orientados a objetos y no tenemos demasiada experiencia en programación funcional, es que las funciones pueden devolver otras funciones. ¿Qué pasaría si hacemos que nuestra función monthlyBalance devolviera otra función que toma como único parámetro de entrada un repositorio de clientes y devuelve los balances mensuales de las cuentas de un cliente? Vemos cómo podríamos definir esto en Scala:

trait Reporting {
   def monthlyBalance(customerId: Id, year: Int, month: Int): CustomerRepository => List[Balance]
   ...
}

Ahora podemos utilizar nuestra función para obtener el balance mensual sin tener que proporcionar el repositorio de clientes:

val reporter: Reporting = ...
val balance = reporter.monthlyBalance("ad82-fdad9-2429-a23bc-2b9c0", 2017, 6)

Por supuesto, lo que obtenemos no es el balance todavía pues, para obtener el balance, necesitamos proporcionar el repositorio de clientes, sino una función capaz de devolver los balances cuando se le pasa un repositorio adecuado. De esta forma, hemos conseguido obtener una definición de nuestro balance sin tener que resolver la dependencia todavía y, de momento, no hemos ejecutado nada aún. Estamos aplicando un principio bastante interesante y repetido en programación funcional: define pronto, ejecuta tarde.

Supongamos que del balance queremos obtener la cantidad total, podemos aplicar composición de funciones para obtenerlo:

val amount = reporter.monthlyBalance("ad82-fdad9-2429-a23bc-2b9c0", 2017, 6) _ andThen (map(_.amount))

Y, de nuevo, todavía no hemos proporcionado el repositorio de clientes ni hemos ejectuado nada. Cuando tengamos disponible una instancia válida de CustomerRepository, podemos hacer:

val repo: CustomerRepository = ...

print("this is the monthly balance amount: " + amount(repo)) //amount is a function

Por último, podemos valernos de una herramienta común en programación funcional: las mónadas. Más concretamente, la mónada Reader resuelve nuestro problema de una forma puramente funcional y muy elegante.

En primer lugar, debemos cambiar la definición de nuestra función para que devuelva una instancia de Reader en lugar de una función (podemos decir que, en cierta forma, Reader encapsula una función que, a partir de un contexto que se le pasa como parámetro de entrada, nos devuelve un resultado). En este ejemplo, utilizaré la implementación de Reader proporcionada en por la librería scalaz.

trait Reporting {
   def monthlyBalance(customerId: Id, year: Int, month: Int): Reader[CustomerRepository, List[Balance]]
   ...
}

Ahora, nuestro método monthlyBalance nos devolverá una instancia de Reader. ¿Qué podemos hacer con ella? Muchas cosas, como, por ejemplo, extraer las cantidades de los balances de una forma más limpia que como hicimos anteriormente gracias a la función map:

val amount = reporter.monthlyBalance("ad82-fdad9-2429-a23bc-2b9c0", 2017, 6).map(b => b.map(_.amount))

Como se trata de una mónada, podemos componer instancias de Reader dentro de una estructura for comprehension, por ejemplo, para obtener la evolución de los balances en los primeros tres meses del año:

val thisYear = ...
val firstQuarterReport = for {
   balances1 <- monthlyBalance(customerId, thisYear, 1)
   balances2 <- monthlyBalance(customerId, thisYear, 2)

   balances3 <- monthlyBalance(customerId, thisYear, 3)
} yield balances1 ++ balances2 ++ balances3

De nuevo, todavía no hemos ejecutado nada, solamente hemos proporcionado una definición de los balances del primer trimestre. ¿Cómo resolvemos la dependencia para tener el resultado final? Para ello, utilizamos el método run proporcionado por la mónada Reader:

val balancesQ1 = firstQuarterReport.run(customerRepository)

Es justo en ese momento cuando se ejecuta nuestro código y todo queda resuelto.

Hemos visto tres alternativas funcionales a hacer inyección de dependencias mediante el constructor de clase:

  • Proporcionar la dependencia a través de un parámetro extra añadido a nuestra función
  • Dividir la lista de parámetros para poder hacer currying, obteniendo una función a que devuelve otra función
  • Utilizar la mónada Reader como tipo de retorno

Todas ellas, alternativas mucho más funcionales que, además, nos permiten diferir la resolución de nuestra dependencia hasta justo el momento en el que resulta imprescindible.


Cuando escribes código (o cuando lo estás probando) te encuentras realidades que tienen distintos efectos en tu trabajo: desde hacerte perder (un poco de) tiempo a convertir tu sistema en un auténtico infierno donde no quieres pasar ni un minuto más del estrictamente necesario.
Una variedad especial que se me ha repetido continuamente son las relacionadas con el tiempo. Por diversos motivos, tratar con el tiempo en un sistema puede llegar a ser extremadamente complicado. La cosa se complica todavía más cuando olvidamos tener en cuenta los detalles más básicos, lo que nos lleva a la motivación de este artículo.


Este post es un resumen, sin seguir ningún orden particular, de algunos de los descuidos, malentendidos y paradojas que me he encontrado al trabajar con sistemas que trataban con fechas, horas y el paso del tiempo.
Lo que sigue es bien conocido por todo el mundo y cualquiera podrá decir, con razón, que olvidar estas cosas es de tontos. Sin embargo, me he visto más de una vez empantadado en mi propio software por culpa de ellas.

No todos los meses tienen el mismo número de días

Es algo que todos saben y, sin embargo, no es infrecuente encontrarte código que, por ejemplo, cuando queremos saber qué día es exactamente dentro de un mes hacen cosas como sumar siempre 30 días a la fecha actual (o sumar siempre 31 días que, para el caso, es igualmente inválido con carácter general).
Y es que mayo tiene 31 días, junio tiene 30, julio tiene 31 y agosto tiene también 31. Y, por supuesto, tenemos febrero, que tiene 28 días, la mayoría de las veces aunque...

No todos los febreros tienen 28 días

Algo que normalmente se aprende en la escuela de pequeño y que se suele olvidar todavía con más frecuencia que el apartado anterior. Pero sí, resulta que, por una mezcla de cienca e historia, algunos meses de febrero tienen 28 días y otros tienen 29. Y, como, colorario...

No todos los años tienen 365 días

Por el mismo motivo por el que hay meses de febrero más largos que otros (y como consecuencia de ello), hay años que tienen 366 días. Se llaman bisiestos, ocurren cada cuatro años y hasta podemos hacer un sencillo cálculo para saber si un año es bisiesto.
Pero no siempre lo tenemos esto en cuenta y, más veces de las que deberíamos, damos por sentado que, por ejemplo, si tenemos que programar algo para que se ejecute automáticamente dentro de un año exactamente, podemos sumar 365 días a la fecha actual y tendremos la fecha en que queremos que nuestra tarea sea ejecutada (error).

Y no todos los minutos tienen 60 segundos

Y por eso hay que meter un segundo aquí y allá de vez en cuando -aunque nunca hayas tenido que tratar con este problema directamente, hay quien se lo toma muy en serio.
De acuedo, éste ha sido un poco más friki de la cuenta. Volvamos con algunos errores más ordinarios, por ejemplo, cuando tratamos con intervalos de tiempo. Empecemos por uno muy simple.

Un intervalo de tiempo de una hora no siempre empieza y acaba en el mismo día

Como ocurre, por ejemplo, con el intervalo de una hora que empieza a las 23:48 de hoy, que terminará mañana. Sencillo,¿ verdad? No sé por qué me he encontrado código que daba por hecho lo contrario (por ejemplo, asumiendo que al sumar una hora a la hora actual tendrías otra hora en el mismo día que tenías al principio).

Tampoco todos los intervalos de una hora empiezan y terminan el mismo mes

Si, por ejemplo, el intervalo que empieza a las 23:48 resulta que lo hace el 30 de abril. Así que, ya sabéis, sumar una hora a un instante de tiempo puede dar lugar a un instante de tiempo en otro mes distinto. Más aún...

No todos los intervalos de una hora ni siquieran empiezan y terminan el mismo año

Cuando el intervalo del que hablamos antes, en vez de ser el 30 de abril, es el 31 de diciembre, ya la hemos liado.
Bueno, creo que ya he explotado bastante el ejemplo y la idea se capta. Pero, ¡ah, recuerda!
  • No todas las semanas empiezan y terminan el mismo mes
  • Ni el mismo año -eso de que el uno de enero caiga en lunes es lo menos frecuente. De hecho, no volverá a ocurrir hasta el 2018.
Es momento ahora de ponerse un poco más técnico. La siguiente es obvia pero provoca bastantes problemas.

El reloj del sistema no siempre tiene la hora correcta

Y es que hay muchas formas de que te dé una hora equivocada. Por ejemplo cuando

El reloj del sistema está configurado como si estuviera en una zona horaria diferente

Y es que al que montó la máquina no le pareció importante asignar la zona horaria correcta cuando instaló el sistema operativo en el servidor de pruebas. Así que la hora parecerá la misma, y servirá para casi todo igual, pero llegará un momento en que tengas que tener en cuenta la zona horaria y, de repente, todo empezará a parecer que ocurre dos horas antes (o dos horas después, o lo que sea) que cuando debería ocurrir.
O también puede ser que sencillamente...

El reloj del sistema puede ser aleatoriamente inconsistente

No tiene sentido, pero ves que el sistema está configurado con un reloj exactamente adelantado 1 hora y 37 minutos y eso ha hecho que pierdas dos horas intentado encontrar la causa de que las horas de los cambios hechos en una tabla de tu base de datos sean incorrectas. Y entonces es cuando uno que pasa por ahí te dice que ayer cambió la hora del sistema porque quería ver si un proceso batch que normalmente se ejecuta a las seis de la tarde terminaba correctamente y se tenía que ir a casa a las cinco y cuarto.
Hablando de relojes cambiados, recuerda que:

La hora en tu servidor no tiene que coincidir con la hora que tienen los clientes

Y es que quien se conecta puede ser un mentiroso y haber cambiado su reloj para que parezca que solicitaron una oferta antes de que terminara el plazo en que expiraba.
O puede que sencillamente ellos viven en Sindey, tú en Madrid y tu servidor está en alguna parte de Irlanda. Lo que nos lleva a un subtipo especial.

Los relojes de tu servidor y de los clientes no siempre están en la misma zona horaria

Así que usa la differencia entre ambas zonas horarias para hacer tus cálculos. Y cuando los hagas, acuérdate también de tener en cuenta en qué día y qué mes estamos porque...

La diferencia entre regiones situadas en zonas horarias distintas no es siempre la misma

Puede variar a lo largo del año, por ejemplo, si en una zona aplican el horario especial de verano mientras que en otra, no.
Y es que trabajar con el tiempo nunca fue fácil y son muchas las cosas que hay que tener en cuenta, por lo que no es tan infrecuente que pasemos por alto algo que luego nos dará problemas. Espero que ahora que las he puesto por escrito no se me olviden más.