Translate

lunes, 24 de enero de 2022

Concurrencia en Clojure - parte 3


Seguimos con concurrencia en Clojure

Las estructuras de datos persistentes, no tiene nada que ver con la persistencia en el disco o dentro de una base de datos. En cambio, se refiere a una estructura de datos que siempre conserva su versión anterior cuando se modifica, lo que permite que el código tenga una vista consistente de los datos ante modificaciones. 

Podemos ver esto fácilmente en el REPL:

user=> (def mapv1 {:name "paul" :age 45})

#'user/mapv1

user=> (def mapv2 (assoc mapv1 :sex :male))

#'user/mapv2

user=> mapv1

{:age 45, :name "paul"}

user=> mapv2

{:age 45, :name "paul", :sex :male}


Las estructuras de datos persistentes se comportan como si se hiciera una copia completa cada vez que se modifican. Si esa fuera la forma en que se implementaron realmente, sería muy ineficiente y, por lo tanto, de uso limitado 

Afortunadamente, la implementación es mucho más inteligente que eso y hace uso de la estructura compartida.

La estructura de datos persistentes más fácil de entender es la lista. Aquí hay una lista simple:

user=> (def listv1 (list 1 2 3))

#'user/listv1

user=> listv1

(1 2 3)


Y aquí hay un diagrama de cómo se ve en la memoria:



Ahora vamos a crear una versión modificada con cons, que devuelve una copia de la lista con un elemento agregado al frente:

user=> (def listv2 (cons 4 listv1))

#'user/listv2

user=> listv2

(4 1 2 3)

La nueva lista puede compartir toda la lista anterior, sin necesidad de copiar:

Finalmente, creemos otra versión modificada:

user=> (def listv3 (cons 5 (rest listv1)))

#'user/listv3

user=> listv3

(5 2 3)




En este caso, la nueva lista solo hace uso de una parte del original, pero aún no es necesaria la copia.

No siempre podemos evitar copiar. Las listas solo manejan bien las colas comunes: si queremos tener dos listas con colas diferentes, no tenemos más remedio que copiar.

Aquí hay un ejemplo:

user=> (def listv1 (list 1 2 3 4))
#'user/listv1
user=> (def listv2 (take 2 listv1))
#'user/listv2
user=> listv2
(1 2)

Todas las colecciones de Clojure son persistentes. Los vectores, mapas y conjuntos persistentes son más complejos de implementar que las listas, pero para nuestros propósitos todo lo que necesitamos saber es que comparten estructura y que proporcionan límites de rendimiento similares a sus equivalentes no persistentes en lenguajes como Ruby y Java.