lunes, 18 de marzo de 2013

Sobre el polimorfismo en clojure y la oop

(Este post nace como comentario a esta discusión acerca de la programación orientada a objetos y la funcional)

No es mi intención "combatir" ni "batir" a la oop, aunque es verdad que algunos defensores de la programación funcional, sobre todo los recién llegados a la misma tienen la típica actitud de los herejes conversos de atacar con ferocidad aquello que defendían. Sin embargo 20 años de hegemonía casi absoluta y un tanto arrogante en algunos momentos de la oop se merecen señalar, al menos durante una temporada, sus limitaciones.

La oop supuso un avance respecto a la programación estructurada aunque para "batirla" tuvo que hacerle concesiones. Por ejemplo es perfectamente posible escribir programación estructurada con java y me atrevería a decir que la gran mayoría del uso de java dentro del mundo enterprise tiene mucho (¿demasiado?) de estructurada.

De la misma manera la oop y la funcional pueden combinarse, siendo scala uno de los mejores ejemplos: para algunos sus características funcionales son las que precisamente le permiten superar esas concesiones a la programación estructurada y llevar la oo mas lejos que java. Por otro lado clojure también comparte algunos elementos (¿concesiones? :-P) que son claves en la oop y que os seran familiares.


Uno de estos elementos es el polimorfismo. Uno de los ejes de la defensa de la oop que no es en absoluto exclusiva de la misma aunque hay que reconocer que ha sido el primer paradigma que le ha dado una papel central y lo ha popularizado. Al igual que con la encapsulación son conceptos "ortogonales" a la oo: lenguajes no oo lo tienen, incluso lenguajes anteriores a la oop.

En el debate que originó este post se han tratado dos temas que tienen que ver directamente con el polimorfismo. El ejemplo trata sobre la "entidad" usuario y un método de expiracion que debe modificar el estado del usuario y guardar ese cambio de forma permanente:
  1. Una de las cuestiones es donde "vive" el código que expira al usuario (o tal vez cualquier cosa que se pueda expirar). Esto esta relacionado con el tema del "modelo anemico" y la tensión entre la programación estructurada y la oop. @Alfredocasado propone una solución dinámica en la que el código vive en un sitio pero se inyecta temporalmente al usuario ya que en determinados contextos ahí es donde parece (o se espera) que deba estar. @jmarranz prefiere una situación mas estática en la que es explicito en tiempo de compilación que código se esta usando. Segun su punto de vista ese código no debiera pertenecer al "espacio de nombres" del usuario, sino a otro que tome al usuario como parámetro. Un punto de vista más "estructural" pero también más funcional.
  2. Otra es como unificar y simplificar el espacio de nombres de las funciones/métodos para evitar cosas como write-to-db, write-to-file o peor, write-to-db(data,flag1,flag2,...) 
En cuanto al punto primero sin saber más del contexto de expire no se muy bien donde lo pondría. Tiendo a pensar que no en un módulo donde haya funciones que tengan que ver con el usuario sino en uno que tuviera que ver con el objetivo de expirar un usuario o cualquier otra cosa (si hay mas cosas que se puedan expirar) O sea en el módulo que se encargara de agrupar las funciones que actuan sobre "éso" donde que algo haya expirado o no tenga sentido.
En cuanto al segundo en clojure (y cualquier otro lenguaje funcional moderno) tienes varias opciones para hacer polimórfica una función: multimétodos y protocolos.

Multimétodos

Los multimétodos pueden ser polimórficos en función de cualquier combinación de cualquiera de los valores (no solo el tipo) de sus parámetros. Esta forma de polimorfismo es estrictamente mas genérica y mas poderosa tiene una serie de ventajas que la hacen más flexible que ciertas implementaciones del polimorfismo en la oop. En estas el dispatch se realiza solo en función del tipo (clase) del primer argumento del método que muchas veces es implicito (this). 

Se puede indicar cual es el método que se usa por defecto si no hay uno definido para unos valores particulares y cual es el preferido si existen dos definiciones de métodos que choquen.

Protocolos

Los protocolos son algo así como interfaces pero más flexibles: cualquier tipo puede extenderse hacia un protocolo estaticamente o al vuelo, incluso los que no han sido definidos por el programador. Los protocolos se añadieron en clojure porque los multimetodos eran mas lentos que el dispatch usando interfaces de la jvm y para que los recien llegados de la oop estuvieran mas comodos :-P

Referencias

Otra cuestión es que el código que llama a write tiene que tener un "writer" o "repositorio". Este puede estar definido estaticamente importando el módulo adecuado (o módulos si se quieren diferentes tipos) O también se le puede asociar dinamicamente al usuario:

O se pueden usar variables globales al módulo que pueden ser dinamicamente redefinidas:

Anda, ¿es que entonces se pueden modificar variables globales en clojure?. Pues sí, pero como comente anteriormente la mutación es por defecto segura concurrentemente. En el caso de las variables globales (o vars) estas pueden ser redefinidas por "binding" solo dentro del ámbito lexico de ese binding y dicha redinifición es local al thread que ejecuta ese bloque de código.
Y ya que nos ponemos y las funciones en clojure se guardan en variables globales, ¿por qué no modificar la funcion write misma? Pero eso tal vez en otro capitulo.

Nota: recientemente he leido en el blog de un importante miembro de la comunidad que el uso de variables globales dinámicas tiene una serie de limitaciones en cuanto a la concurrencia y otros aspectos. Para evitarlas recomienda siempre exponer una llamada con el recurso como parámetro para que el llamador tenga la opción de llamar de una u otra forma. Así la habia escrito yo aunque sin ser consciente de todas las limitaciones que lo hacen necesario.