DEV Community

loading...
Cover image for Refactorización mágica con Unison: algo más que un nuevo lenguaje de programación

Refactorización mágica con Unison: algo más que un nuevo lenguaje de programación

Gonzalo Ruiz de Villa
CTO@GFT, Google Developer Expert, Founder partner@Kenobi Ventures, Founder of Adesis Netlife
・4 min read

Lo reconozco, soy un obseso de la refactorización. Cuando programo, no dejo de cambiar los nombre de las variables, los métodos o las clases. O, por ejemplo, no hay funciones a las que no les cambie los argumentos sin parar, hasta que por fin me quede ligeramente satisfecho. Siempre me quedo con la sensación de que se puede hacer algo más.

Por eso, me sorprendió la capacidad de refactorización que tiene de este nuevo lenguaje de programación llamado Unison. Como he dicho, renombro las cosas obsesivamente, pero esto tiene un coste. Por ejemplo, al renombrar una función, hay que modificar todas las referencias, o lo que es lo mismo, hay tocar todos los ficheros donde haya funciones o métodos que invoquen la función renombrada. Pongamos que en el proyecto hay mil ficheros con llamadas a dicha función: esto implica que tras el renombrado, en el commit, de los 1001 ficheros que contiene, solo uno tiene un cambio relevante, mientras que los cambios en el resto de ficheros solo generan ruido. Pero, ¿podría acaso ser de otra forma?

Y, evidentemente, la respuesta es si (si no este artículo no tendría sentido, ¿no? 🤷🏼‍♀️).

En Unison, para cambiar el nombre de una función, usamos el comando move:

.> move.term base.List.foldl base.List.foldLeft

Si ahora revisamos la implementación de una función que use la antigua foldl, ahora foldLeft, vemos que la magia ya ha hecho su efecto:

.> view base.List.reverse
base.List.reverse : [a] -> [a] 
base.List.reverse as = 
  use base.List +: 
  base.List.foldLeft (acc a -> a +: acc) [] as

Al renombrar una función con Unison, instantáneamente todas las referencias quedan corregidas. Pero para hacerlo, lo que no va a hacer Unison es mutar textos en tu nombre, actualizando miles de ficheros, generando un diff gigantesco y rompiendo las librerías de usuarios que esperan que la función siga usando el nombre antiguo. Menuda locura ¿no?

Unison es un lenguaje tipado muy influenciado por Haskell, Erlang y un lenguaje de investigación llamado Frank. Hay una idea de fondo muy sencilla sobre la que se apoya Unison: identificar las definiciones no por su nombre sino por su contenido. Es decir, si definimos una función factorial como el producto de los números naturales entre 1 y un número dado n, no importará si el nombre con el que definimos la función es “factorial”, “lairotcaf” o cualquier otra cosa. De igual manera, no tiene ninguna importancia que el parámetro se llame “n”, “z” o cualquier otro nombre: la función no cambia en su esencia.

Lo que hace Unison es lo siguiente: primero, Unison calcula el hash de la implementación. Segundo, en lugar de almacenar ficheros de texto, lo que guardar es el abstract syntax tree de la función, donde las referencias a otras funciones se realiza mediante los correspondientes hashes. De esta forma, se consigue una gestión del código base que permite entre otras cosas:

  • no tener que recompilar nada
  • renombrar de forma trivial
  • cachear los resultados de los tests
  • eliminar los conflictos de dependencias
  • almacenamiento persistente tipado y sencillo

El gestor de código base de Unison es la pieza que posibilita todas estas cosas al almacenar el AST y convertirse en la única fuente de la verdad y no representaciones textuales en ficheros de texto. El gestor de código base tiene unas propiedades muy interesantes:

  • append-only: nunca se modifican o borran las definiciones, solo se añaden nuevas.
  • como resultado de lo anterior, se puede versionar y sincronizar con Git o herramientas similares sin generar conflictos. como solo se puede añadir, se pueden cachear muchos tipos de información sin preocuparse por la caducidad de la cache.
  • los nombres se almacenan separados de las definiciones, por lo que renombrar es rapidísimo y preciso al 100%. Además, se pueden agregar alias fácilmente.

Unison proporciona unos ficheros, los scrach files, donde puedes explorar las definiciones del codebase y realizar y probar los cambios antes de guardarlos en el codebase manager.

Otra de las motivaciones detrás de Unison que he encontrado muy interesante es la de poder describir de forma precisa programas que se puedan desplegar por sí mismos y describir sistemas elásticos distribuidos. Por ejemplo, observa la siguiente implementación de merge sort:

dsort: (a -> a -> Boolean) -> [a] -> {Remote} [a]
dsort lte as =
  if size as < 2 then as
  else case halve as of (left, right) ->
    resL = at spawn '(dsort lte left)
    resR = at spawn '(dsort lte left)
    merge lte (force resL) (force resR)

Parece una implementación típica en memoria, donde se divide la lista en mitades que se ordenan y entonces se mezclan los resultados. Lo particular es que las llamadas recursivas se están haciendo en paralelo en dos nuevos y recién aprovisionados recursos computacionales.

Esta es parte de la magia que promete Unison, programas distribuidos sin configuraciones, sin JSONs, sin gestión de conexiones de red, etc. Solo código anotando donde quieres ejecución concurrente o distribuida y Unison hace su magia.

Conclusión

Unison es una apuesta muy interesante que prueba conceptos nuevos. No se si tendrá mucho futuro o no, pero lo que que sin duda ya está haciendo es ayudar a replantear como enfocamos ciertos aspectos del desarrollo de software con los lenguajes de programación. No se trata de una sintaxis nueva, sino de ejercicio bastante más ambicioso.

Referencias

Discussion (0)