Translate

Mostrando las entradas con la etiqueta Clojure. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Clojure. Mostrar todas las entradas

lunes, 25 de septiembre de 2023

Juego de Serie en Clojure


Cuando quiero aprender un nuevo lenguaje desarrollo un juego de series, es decir aparece una serie con un valor faltante y el jugador debe completarlo.

Uno de los requerimientos no funcionales es que se pueda agregar una serie nueva fácilmente.

Vamos a desarrollar este juego en Clojure:

Empecemos desarrollo de la serie, la serie tiene como responsabilidad generarse y si lo hacemos de esta manera podemos utilizar la potencia del polimorfismo, para que no quede acoplado la generación de la serie con el desarrollo del juego:

(ns secuencia-clojure.core

  (:gen-class))


(import '(java.util Scanner))

(def scan (Scanner. *in*))


   

(defn generateSecEven []

  (def seed (rand-int 30))

  (for [x (range seed (+ seed 4))] (* x 2))

)

   

(defn generateSecOdd []

  (def seed (rand-int 30))

  (for [x (range seed (+ seed 4))] (+ (* x 2) 1))

)


(defn generateSecFibo []

  (def seed (rand-int 30))

  (def seed2 (* seed 2))

  (def seed3 (* seed 3))

  (list seed seed seed2 seed3)

)

Y listo!! ahora en el main tenemos que llamar a esta función y ver si esta bien el resultado: 



(defn generateSec []
  (def seed (rand-int 3))
  (cond 
    (= seed 0) (generateSecEven)
    (= seed 1) (generateSecOdd)
    (= seed 2) (generateSecFibo)   
  )
)

(defn printSec [sec]
  (print (nth sec 0))
  (print " ")
  (print (nth sec 1))
  (print " ")
  (print " __ ")
  (print " ")
  (println (nth sec 3))
)

(defn game
  [points]
  (def sec (generateSec))
  (def sol (nth sec 2))
  
  (printSec sec)
  
  (let [data (read-line)]
  (if (= data (str sol))
    (do 
      (println "Correct!! the point is " (str (+ points 1)))
      (game (+ points 1))
    )
    (do 
      (println "Wrong!! the point is " (str (- points 1)))
      (game (- points 1))
    )))
)


(defn -main
  [& args]
  (game 0))

Y eso es todo a jugar se a dicho!!

Dejo el repositorio git: 

sábado, 9 de septiembre de 2023

Como crear un nuevo proyecto con Clojure y Leiningen?



Bueno, todo tiene un principio y vamos a empezar a crear un proyectito de consola con Clojure y Leiningen

$ lein new app my-project

Fijate vos que si queres utilizar nombre de proyecto con mayuscula, no te deja, ojo ahi. 

Ahora vamos a ejecutarlo :

$ lein run

podemos generar un jar con : 

$ lein jar

Si estamos trabajando en un proyecto de aplicación, Leiningen nos brinda la posibilidad de construir lo que se llama un uberjar. Este es un archivo JAR que contiene el proyecto en sí y todas las dependencias y está configurado para permitir que se ejecute tal cual.

$ lein uberjar

Compiling secuencia-clojure.core

$ java -jar target/uberjar/el-jar-standalone.jar 

Y ya podemos empezar!!

miércoles, 9 de agosto de 2023

Veamos Pedestal y Clojure


Clojure es un lenguaje de programación funcional y dinámico que se ejecuta en la máquina virtual de Java (JVM). Diseñado para ser simple, eficiente y expresivo, Clojure se basa en los principios de la programación funcional y proporciona herramientas para manejar la concurrencia y la inmutabilidad de manera efectiva. Su sintaxis concisa y su énfasis en la inmutabilidad lo convierten en una excelente elección para construir aplicaciones robustas y escalables.

Pedestal es un framework web desarrollado en Clojure que se enfoca en la construcción de aplicaciones web escalables y de alto rendimiento. A diferencia de algunos otros frameworks web, Pedestal no se basa en el paradigma de controladores y vistas, sino que se centra en la manipulación de datos y el flujo de información. Esto lo convierte en una opción ideal para aplicaciones modernas que requieren manejo eficiente de datos en tiempo real.

Las caracteristicas claves son: 

  • Modelo de Manejo de Datos: Pedestal se basa en el modelo de manejo de datos (data-driven) en lugar del enfoque tradicional de controladores y vistas. Esto permite una manipulación más eficiente y coherente de los datos a medida que fluyen a través de la aplicación.
  • Componentes Reutilizables: El framework fomenta la creación de componentes reutilizables que pueden ser ensamblados fácilmente para construir aplicaciones complejas. Esto mejora la modularidad y el mantenimiento del código.
  • Enfoque Asincrónico: Pedestal está diseñado para manejar concurrencia y operaciones asincrónicas de manera efectiva. Esto es esencial para aplicaciones que necesitan manejar múltiples solicitudes y actualizaciones en tiempo real.
  • Rendimiento Optimizado: Gracias a su enfoque asincrónico y a la optimización para el manejo eficiente de datos, Pedestal es capaz de ofrecer un alto rendimiento incluso en situaciones de alta carga.


La integración de Pedestal con Clojure es perfectamente natural, ya que ambos comparten la misma plataforma de ejecución (JVM) y una filosofía similar centrada en la simplicidad y la eficiencia. Al aprovechar las capacidades funcionales de Clojure, Pedestal puede manejar de manera elegante la manipulación de datos y el flujo de información en las aplicaciones web.


Vamos a hacer un hola mundo, para ello vamos a crear el proyecto con Leiningen : 


lein new pedestal-app hello-world


Esto creará una estructura de proyecto básica en el directorio hello-world.


Ejecutamos :

lein run

El servidor Pedestal se ejecutará en el puerto 8080 por defecto. Abre tu navegador web y navega a http://localhost:8080. Deberías ver el mensaje "¡Hola, Mundo desde Pedestal y Clojure!" en la página.

Dejo link: https://github.com/pedestal/pedestal

lunes, 18 de julio de 2022

Apis en diferentes lenguajes y frameworks en la plataforma Java


Estoy haciendo una aplicación con n microservicios en n tipos de tecnologías y lenguajes todos en la plataforma Java. 

Los de n tipos de tecnologías es mentiroso solo 2 framework use por ahora Spring boot y micronaut y a decir verdad use un 80% de spring boot pero quiero escribir un post tipo draft, por las dudas si me canso y nunca termino. 

Por ende, vamos a ver como me fue con Spring boot y diferentes lenguajes y luego hablo de micronaut. 

Java: Bueno, esta es la tupla más utilizada y creo que ustedes saben que anda muy bien. No sé si puedo agregar algo más que no sepan. 

Kotlin: Me encanto, por varias razones me parece mejor que java. Kotlin es un lenguaje, muy bueno y muy compatible con java lo que lo hace una opción super fácil de usar y no se sufre nada con la compatibilidad con spring boot. 

Groovy: Igual que Java, no encuentro una buena razón para elegir este lenguaje, esta bueno, pero el tipado dinamico, no ofrece mayor ventaja y si lo usamos con tipando estático, es similar a java o kotlin. 

Scala: Un infierno de incompatibilidad, no le recomiendo utilizar spring boot con scala, tenes que estar siempre cambiando tipos, de tipos de Scala a Java y de Java a Scala. No es una opción práctica...

Clojure: Otro infierno, no solo por los tipos, sino tambien por la generación de clases con anotaciones. Muy trabajoso, al final cuando te funciona una Api, no te acordas para que la querías. 

Micronaut lo utilice muy poco con Groovy y Java y me fue muy bien, casi casi puedo decir que me gusta más que spring boot. Y ahora sigo con Micronaut y Quarkus. 

jueves, 23 de junio de 2022

Agregar código java a un proyecto Clojure en Leiningen


 Quiero hacer unas clases en java y utilizarlas en mis funciones clojure. Es muy fácil, tengo que agregar   :java-source-paths a mi proyecto y ya toma las clases.  

Veamos un ejemplo

(defproject ejemplo "0.1.0-SNAPSHOT"

  :description "FIXME: write description"

  :url "http://example.com/FIXME"

  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"

            :url "https://www.eclipse.org/legal/epl-2.0/"}

  :dependencies [[org.clojure/clojure "1.10.3"],

                 ...                 ]

  :java-source-paths ["src-java"]

  :main com.assembly.ejemplo.core

  :aot :all

  :target-path "target/%s"

  :profiles {:uberjar {:aot :all

                       :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})


Y listo!!

sábado, 18 de junio de 2022

Crear un proyecto Spring boot, con Leiningen y Clojure


La idea es crear un proyecto spring boot, con Clojure y Leiningen. (ya lo dice el titulo)

Primero hacemos un proyecto hello word con Leiningen (ojo tienen que tener Leiningen instalado, esta bueno instalarlo con sdkman, así :  sdk install leiningen) 

lein new app ejemplo

Luego agregamos las dependencias de spring boot al proyecto, para que quede algo así: 

(defproject github "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
            :url "https://www.eclipse.org/legal/epl-2.0/"}
  :dependencies [[org.clojure/clojure "1.10.3"],
                 [org.springframework.boot/spring-boot-starter-web "2.6.7"],
                 [org.springframework.boot/spring-boot-configuration-processor "2.6.7"]]
  :main com.assembly.example.core
  :aot :all
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all
                       :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})

Ojo, tambien hay que agregar :aot y :all 

Ahora hacemos el Application, yo modifique el hello wold que vino por default, pero si lo hacen tengan cuidado de ponerlo en un paquete. 

(ns com.assembly.example.core
  (:import (org.springframework.boot SpringApplication))
  (:gen-class
    :name ^{org.springframework.boot.autoconfigure.SpringBootApplication []
            org.springframework.context.annotation.ComponentScan {:basePackages ["com.assembly.example"]}}
    com.assembly.example.core.Application
    :main true)
  )

(defn -main []
  (SpringApplication/run (Class/forName "com.assembly.example.core.Application") (into-array String '()))
  )

Y ahora hacemos un endpoint, en este ejemplo voy a hacer un hola mundo : 

(ns com.assembly.exanple.controller.greet

  (:import (org.springframework.web.bind.annotation PathVariable RequestMapping RequestMethod RestController)

           (org.springframework.http ResponseEntity))

  )


(gen-class

  :name ^{RestController {}

          RequestMapping {:value ["/v1/greet"]}} com.assembly.exanple.controller.greet.GreeterEndPoint

  :methods [[^{RequestMapping {:value ["/hello"]

                               :method [RequestMethod/GET]}} sayHello [] Object ]

            [^{RequestMapping {:value ["/helloto/{name}"]

                               :method [RequestMethod/GET]}} sayHelloTo [^{PathVariable {:value "name"}} String] Object ]

            ]

  :state injected

  :init init

)


(defn -init

  "Initialize the class by setting the state to an empty map, which can be populated with injected dependencies."

  []

  [[] (atom {})])


(defn -sayHello

  [this]

  (ResponseEntity/ok "Hello"))


(defn -sayHelloTo

  [this name]

  (ResponseEntity/ok (str "Hello " name)))


Y listo, si corremos el main, vamos a levantar el tomcat embebido de spring y vamos a : 

http://localhost:8080/v1/greet/hello

y veremos como nos saluda spring boot con clojure!! 

sábado, 11 de junio de 2022

Top de lenguajes en la JVM


Buscando que lenguajes están en el top 5 de más usados me encontré con esto en la wikipedia : 

As of April 2022, according to the TIOBE Index[1] of the top 100 programming languages, the top JVM languages are:

Lo que me sorprendio no fue la información sino que la wikipedia este tan actualizada. 

Otra cosa que me llamo la atención es que groovy le gane a scala o a clojure, pense que era diferente.

Y bueno, eso es todo, no me critiquen porque pegue algo en ingles en un blog en español, es de la wikipedia, no voy andar cambiando la fuente.  

Dejo link : https://en.wikipedia.org/wiki/List_of_JVM_languages

sábado, 29 de enero de 2022

Concurrencia en Clojure - parte 7


Seguimos con concurrencia en Clojure

Ahora podemos modificar nuestro servicio web para usar sesiones. Primero, necesitamos una función que cree una nueva sesión:

(defn create-session []

  (let [snippets (repeatedly promise)

    translations (delay (map translate

                         (strings->sentences (map deref snippets))))]

      (new-session {:snippets snippets :translations translations})))


Seguimos usando una secuencia perezosa infinita de promesas para representar los fragmentos entrantes y un mapa sobre esa secuencia para representar las traducciones, pero ahora ambos están almacenados en una sesión.

A continuación, debemos modificar accept-snippet y get-translation para buscar :snippets o :translations dentro de una sesión:

(defn accept-snippet [session n text]

  (deliver (nth (:snippets session) n) text))

(defn get-translation [session n]

  @(nth @(:translations session) n))


Finalmente, definimos las rutas que vinculan estas funciones a las URI:

(defroutes app-routes
  (POST "/session/create" []
    (response (str (create-session))))

  (context "/session/:session-id" [session-id]
    (let [session (get-session (edn/read-string session-id))]
      (routes
        (PUT "/snippet/:n" [n :as {:keys [body]}]
          (accept-snippet session (edn/read-string n) (slurp body))
          (response "OK"))

        (GET "/translation/:n" [n]
          (response (get-translation session (edn/read-string n))))))))

Esto nos brinda un servicio web que hace un uso juicioso de los datos mutables pero aún se siente principalmente funcional.


jueves, 27 de enero de 2022

Concurrencia en Clojure - parte 6



Vamos a crear un servicio web que maneje múltiples transcripciones introduciendo el concepto de una sesión. Cada sesión tiene un identificador numérico único, que se genera de la siguiente manera:

(def last-session-id (atom 0))

(defn next-session-id []

(swap! last-session-id inc))

Esto usa un átomo, last-session-id , que se incrementa cada vez que queremos una nueva ID de sesión. Como resultado, cada vez que se llama a next-session-id, devuelve un número uno más alto que el anterior:

server.core=> (in-ns 'server.session)
#<Namespace server.session>
server.session=> (next-session-id)
1
server.session=> (next-session-id)
2
server.session=> (next-session-id)
3

Vamos a hacer un seguimiento de las sesiones activas con otro átomo llamado sesiones que contiene un mapa de ID de sesión y valores de sesión:

(def sessions (atom {}))

(defn new-session [initial]

  (let [session-id (next-session-id)]

    (swap! sessions assoc session-id initial)

     session-id))


(defn get-session [id]

   (@sessions id))

Creamos una nueva sesión pasando un valor inicial a new-session, que obtiene una nueva ID de sesión y la agrega a las sesiones llamando a swap!. Recuper una sesión con get-session es una simple cuestión de buscarlo por su ID.

Si no vamos a aumentar continuamente la cantidad de memoria que usamos, necesitaremos alguna forma de eliminar las sesiones cuando ya no estén en uso. Nosotros podría hacer esto explícitamente (quizás con una función de eliminación de sesión), pero dado que estamos escribiendo un servicio web en el que no necesariamente podemos confiar en que los clientes limpien correctamente, vamos a implementar la caducidad de la sesión (expiry) en su lugar. Esto requiere un pequeño cambio en el código anterior:

(def sessions (atom {}))

(defn now []

  (System/currentTimeMillis))


(defn new-session [initial]

  (let [session-id (next-session-id)

         session (assoc initial :last-referenced (atom (now)))]

    (swap! sessions assoc session-id session)

    session-id))


(defn get-session [id]

  (let [session (@sessions id)]

     (reset! (:last-referenced session) (now))

     session))


Hemos agregado una función llamada now que devuelve la hora actual. Cuando new-session crea una sesión, agrega una entrada :last-referenced a la sesión, otro átomo que contiene la hora actual. Esto se actualiza con reset! cada vez que get-session accede a la sesión.

Ahora que cada sesión tiene una entrada :last-referenced, podemos caducar sesiones verificando periódicamente si alguna no ha sido referenciada por más de un cierto período de tiempo:


(defn session-expiry-time []

  (- (now) (* 10 60 1000)))


(defn expired? [session]

  (< @(:last-referenced session) (session-expiry-time)))


(defn sweep-sessions []

  (swap! sessions #(remove-vals % expired?)))


(def session-sweeper

  (schedule {:min (range 0 60 5)} sweep-sessions))


Esto utiliza la biblioteca Schejulure para crear un barrido de sesiones, que se ejecuten una vez cada cinco minutos. Cada vez que se ejecuta, elimina cualquier sesión que haya expirado.

miércoles, 26 de enero de 2022

Concurrencia en Clojure - parte 5




Imagina que queremos tener un átomo que nunca tenga un valor negativo. Podemos garantizar esto por medio de una función de validación cuando creamos el átomo:

user=> (def non-negative (atom 0 :validator #(>= % 0)))

#'user/non-negative

user=> (reset! non-negative 42)

42

user=> (reset! non-negative -1)

IllegalStateException Invalid reference state

Un validador es una función que se llama cada vez que se intenta cambiar el valor del átomo. Si devuelve verdadero, el intento puede tener éxito, pero si devuelve falso, el intento se abandonará.

El validador se llama antes de que se haya cambiado el valor del átomo y, al igual que la función que se pasa a swap!. Por lo tanto, los validadores tampoco deben tener efectos secundarios.

Los átomos también pueden tener observadores asociados con ellos:

user=> (def a (atom 0))

#'user/a

user=> (add-watch a :print #(println "Changed from " %3 " to " %4))

#<Atom@542ab4b1: 0>

user=> (swap! a + 2)

Changed from 0 to 2

2

La clave se utiliza para identificar al observador (así, por ejemplo, si hay varios observadores, podemos eliminar uno específico proporcionando la clave correspondiente). La función watch se llama cada vez que cambia el valor del átomo. Se le dan cuatro argumentos: la clave que se le dio a add-watch, una referencia al átomo, el valor anterior y el nuevo valor.

En el código anterior, usamos la macro lectora #(...) nuevamente para definir una función anónima que imprime los valores antiguo (%3) y nuevo (%4) del átomo.

A diferencia de los validadores, las funciones de observación se llaman después de que el valor ha cambiado y solo se llamarán una vez, ¡sin importar la frecuencia con la que se intercambien! 

Por lo tanto, una función watch puede tener efectos secundarios. Que en el momento en que se llama a la función de observación, es posible que el valor del átomo ya haya cambiado nuevamente, por lo que las funciones de observación siempre deben usar los valores pasados como argumentos y nunca eliminar la referencia del átomo.


martes, 25 de enero de 2022

Concurrencia en Clojure - parte 4


Seguimos con concurrencia en Clojure

Las estructuras de datos persistentes son invaluables para la programación concurrente porque una vez que un subproceso tiene una referencia a una estructura de datos, no verá cambios realizados por ningún otro subproceso. Las estructuras de datos persistentes separan la identidad del estado. ¿Cuál es el nivel de combustible en su automóvil? En este momento, podría estar medio lleno. Algún tiempo después estará casi vacío, y unos minutos después de eso (después de que te detengas para llenar) estará lleno. La identidad "nivel de combustible en su automóvil" es una cosa, cuyo estado cambia constantemente. El “nivel de combustible en su automóvil” es en realidad una secuencia de diferentes valores: el 23/02/2012 a las 12:03 era 0,53; en 2012-02-23 14:30 era 0.12; y en 2012-02-23 14:31 era 1.00.

Una variable en un lenguaje imperativo completa (entrelaza, interconecta) la identidad y el estado: una única identidad solo puede tener un único valor, lo que hace que sea fácil perder de vista el hecho de que el estado es realmente una secuencia de valores a lo largo del tiempo. Las estructuras de datos persistentes separan la identidad del estado: si recuperamos el estado actual asociado con una identidad, ese estado es inmutable e muatble, sin importar lo que suceda con la identidad de la que recuperamos en el futuro.

Heráclito lo expresó así: No podías meterte dos veces en el mismo río; porque otras aguas están siempre fluyendo sobre ti. 

La mayoría de los lenguajes se aferran a la falacia de que el río es una sola entidad consistente; Clojure reconoce que está en constante cambio.

Debido a que Clojure es funcional, los átomos pueden no tener bloqueo; internamente, utilizan el método compareAndSet() en java.util.concurrent.AtomicReference. Eso significa que son muy rápidos y no se bloquean (por lo que no hay peligro de interbloqueo). Pero también significa ese intercambio necesita manejar el caso en el que el valor del átomo ha sido cambiado por otro hilo entre llamar a la función para generar un nuevo valor e intentar cambiar ese valor.

Si eso sucede, volverá a intentarlo. Descartará el valor devuelto por la función y lo llamará de nuevo con el nuevo valor del átomo. Ya vimos algo muy similar a esto cuando usamos ConcurrentHashMap. Esto significa que es esencial que la función pase a swap! no tiene efectos secundarios; si los tuviera, entonces esos efectos secundarios podrían ocurrir más de una vez. Afortunadamente, aquí es donde la naturaleza funcional de Clojure vale la pena: el código funcional naturalmente no tiene efectos secundarios.

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.


Concurrencia en Clojure - parte 2


Seguimos con concurrencia en Clojure

Veamos un ejemplo que muestra cómo las estructuras de datos persistentes de Clojure significan que el estado mutable no puede escapar como lo hace en Java.

(def players (atom ()))

(defn list-players []

 (response (json/encode @players)))


(defn create-player [player-name]

  (swap! players conj player-name)

  (status (response "") 201))


(defroutes app-routes
  (GET "/players" [] (list-players))
  (PUT "/players/:player-name" [player-name] (create-player player-name)))

(defn -main [& args]
  (run-jetty (site app-routes) {:port 3000}))

Esto define un par de rutas: una solicitud GET a /players recuperará una lista de los jugadores actuales (en formato JSON) y una solicitud PUT a "/players/:player-name" agregará un jugador a esa lista. El servidor Embedded Jetty es multiproceso, por lo que nuestro código deberá ser seguro para subprocesos.

Veámoslo en acción. Podemos ejecutarlo desde la línea de comandos con curl:

$ curl localhost:3000/players

[]

$ curl -X put localhost:3000/players/john

$ curl localhost:3000/players

["john"]

$ curl -X put localhost:3000/players/paul

$ curl -X put localhost:3000/players/george

$ curl -X put localhost:3000/players/ringo

$ curl localhost:3000/players

["ringo","george","paul","john"]

Ahora veamos cómo funciona este código. El átomo de los jugadores :
  (def players (atom ()))
 se inicializa en la lista vacía (). Se agrega un nuevo jugador a la lista con conj :
  (swap! players conj player-name)
, y se devuelve una respuesta vacía con un estado HTTP 201 (creado). La lista de jugadores se devuelve mediante la codificación JSON del resultado de obtener el valor de los jugadores con @:
 (response (json/encode @players)))

Todo esto parece muy simple (y lo es), pero algo podría estar preocupándote algo al respecto. Tanto las funciones de listar jugadores como las de crear jugadores acceden a los jugadores. ¿Por qué este código no sufre el mismo problema que el código Java? ¿Qué sucede si un subproceso agrega una entrada a la lista de reproductores mientras otro lo itera y lo convierte a JSON? Este código es seguro para subprocesos porque las estructuras de datos de Clojure son persistentes.

Y que significa que sea persistente? y en el próximo post te cuento. 

sábado, 8 de enero de 2022

Concurrencia en Clojure


Clojure proporciona un estilo híbrido de programación funcional y estado mutable para resolver problemas de forma concurrente; el "Clojure Way" aprovecha las fortalezas de ambos para brindar un enfoque particularmente poderoso para la programación concurrente.

Si bien la programación funcional funciona increíblemente bien para algunos problemas, algunos tienen la modificación del estado como un elemento fundamental de la solución. Aunque puede ser posible crear una solución funcional a tales problemas, es más fácil pensar en ellos de una manera más tradicional. 

Un lenguaje funcional puro no proporciona soporte para datos mutables en absoluto. Clojure, por el contrario, es impuro: proporciona varios tipos diferentes de variables mutables conscientes de la concurrencia, cada una de las cuales es adecuada para diferentes casos de uso. Estos, en conjunto con las estructuras de datos persistentes de Clojure (vamos a cubriremos lo que significa persistente en este contexto más adelante) nos permiten evitar muchos de los problemas que tradicionalmente afligen a los programas concurrentes con estado mutable compartido.

La diferencia entre un lenguaje funcional impuro y un lenguaje imperativo es de énfasis. En un lenguaje imperativo, las variables son mutables por defecto y el código idiomático las modifica con frecuencia. En un lenguaje funcional impuro, las variables son inmutables por defecto y el código idiomático modifica aquellas que no lo son solo cuando es absolutamente necesario. Como veremos, las variables mutables de Clojure nos permiten manejar los efectos secundarios del mundo real sin dejar de ser seguros y consistentes.

Veremos cómo las variables mutables de Clojure funcionan en conjunto con las estructuras de datos persistentes para separar la identidad del estado. Esto permite que varios subprocesos accedan a variables mutables simultáneamente sin bloqueos (y el peligro asociado de interbloqueo) y sin ninguno de los problemas de estado mutable escapado u oculto. Comenzaremos por lo que podría decirse que es el más simple de los tipos de variables mutables de Clojure, el átomo.

Un átomo es una variable atómica  (los átomos de Clojure están construidos sobre java.util.concurrent.atomic). A continuación, se muestra un ejemplo de cómo crear y recuperar el valor de un átomo:

user=> (def my-atom (atom 42))

#'user/my-atom

user=> (deref my-atom)

42

user=> @my-atom

42

Un átomo se crea con átomo, que toma un valor inicial. Podemos encontrar el valor actual de un átomo con deref o @.
Si desea actualizar un átomo a un nuevo valor, se usa swap !:

user=> (swap! my-atom inc)
43
user=> @my-atom
43

Esto toma una función y le pasa el valor actual del átomo. El nuevo valor del átomo se convierte en el valor de retorno de la función. También podemos pasar argumentos adicionales a la función, como en este ejemplo:

user=> (swap! my-atom + 2)
45

El primer argumento pasado a la función será el valor actual del átomo, y luego cualquier argumento adicional dado para intercambiar. Entonces, en este caso, el nuevo valor se convierte en el resultado de (+ 43 2).

En raras ocasiones, es posible que desee establecer un átomo en un valor que no dependa de su valor actual, en cuyo caso puede usar reset !:

user=> (reset! my-atom 0)
0
user=> @my-atom
0

Los átomos pueden ser de cualquier tipo; muchas aplicaciones web utilizan un mapa atómico para almacenar datos de sesión, como en este ejemplo:

user=> (def session (atom {}))
#'user/session
user=> (swap! session assoc :username "paul")
{:username "paul"}
user=> (swap! session assoc :session-id 1234)
{:session-id 1234, :username "paul"}

Ya se que no vimos nada de concurrencia aun, pero paciencia, como me queda largo el post seguimos en otro... 

lunes, 3 de mayo de 2021

Como correr los test proyecto clojure?


Si vieron este post : https://emanuelpeg.blogspot.com/2021/04/como-crear-un-proyecto-clojure.html

Deben haber pensado: y los test, donde están los test?

Bueno, vamos a hacer una pequeña función cuadrado por ejemplo : 

(defn cuadrado [n] (* n n))

Y vamos a hacer un test : 

(ns recfun.core-test
(:require [clojure.test :refer :all]
[recfun.core :refer :all]))

(deftest a-test-of-cuadrado
(testing "2 * 2 = 4 "
(is (= (cuadrado 2) 4))))

Y corremos los test con :

$ lein test

lein test recfun.core-test

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.

Y listo, voy a hacer un test que termine mal, así se ve : 

(deftest other-test-of-cuadrado
(testing "2 * 2 = 5 (solo para que termine mal) "
(is (= (cuadrado 2) 5))))

Y termina mal :

$ lein test

lein test recfun.core-test

lein test :only recfun.core-test/other-test-of-cuadrado

FAIL in (other-test-of-cuadrado) (core_test.clj:11)
2 * 2 = 5 (solo para que termine mal) 
expected: (= (cuadrado 2) 5)
  actual: (not (= 4 5))

Ran 2 tests containing 2 assertions.
1 failures, 0 errors.
Tests failed.

jueves, 29 de abril de 2021

Como crear un proyecto clojure?

 Esto es muy fácil hacemos así : 


$ lein new app nombreDeLaApp

Generating a project called nombreDeLaApp based on the 'app' template.


Como ustedes pueden ver utilice el template app, pero que es eso de los templates? Bueno, leiningen provee un conjunto de template que permiten hacer proyectos con una estructura determinada. 

Y si quieren ver los template acá dejo el link : https://clj-templates.com/

Y listo! Ahora si queremos correr la app hacemos : 

$ cd nombreDeLaApp
$ lein run

Y listo!!!!


martes, 16 de marzo de 2021

Corre Clojure en .net con ClojureCLR


Supongamos que queremos programar en .net y con Clojure. Y se puede! con ClojureCLR. 

Primero debemos instalarlo, utilizando una terminal con el siguiente comando: 

dotnet tool install --global --version 1.10.0-beta1 Clojure.Main

Hoy estoy en powershell, sería algo así  : 

PS C:\dotnet> dotnet tool install --global --version 1.10.0-beta1 Clojure.Main                                          

You can invoke the tool using the following command: Clojure.Main

Tool 'clojure.main' (version '1.10.0-beta1') was successfully installed.


Y luego, escribimos  Clojure.Main en la terminal y podemos acceder al RELP : 


PS C:\dotnet> Clojure.Main

Clojure 1.10.0-beta1

user=> (defn mayor [L]

  (cond

        (empty? (rest L)) (first L)

        (> (first L) (mayor (rest L))) (first L)

        :else (mayor (rest L))

  )

)

#'user/mayor

user=> (mayor '(1 2 3 50 40))

50


Dejo link :  https://github.com/clojure/clojure-clr


sábado, 13 de marzo de 2021

Primeros pasos con Clojure, parte 32

  Seguimos con Clojure... 

Para terminar esta saga de post, vamos a hacer una pequeña función que permita obtener el mayor número de una lista de números. 

(defn mayor [L] 
  (cond 
        (empty? (rest L)) (first L)
        (> (first L) (mayor (rest L))) (first L)
        :else (mayor (rest L))
  )
)

Esta pequeña expresión nos permite saber el mayor de una lista. Su funcionamiento es fácil, si la lista tiene un solo elemento, ese es el mayor y si la lista tiene varios elementos, bueno compara el primero con el mayor del resto de la lista, si es mayor, entonces es el mayor, sino retorna el mayor del resto de la lista. 

Puff espero que se haya entendido... 

Primeros pasos con Clojure, parte 31

 Seguimos con Clojure... 


Clojure provee manejo de excepciones como Java (try/catch/finally), veamos un ejemplo : 

(try

  (/ 2 1)

  (catch ArithmeticException e

    "divide by zero")

  (finally

    (println "cleanup")))


Tambien podemos lanzar excepciones: 


(try

  (throw (Exception. "something went wrong"))

  (catch Exception e (.getMessage e)))


Clojure provee información extra con : 

  • ex-info toma un mensaje y un mapa y genera una excepción
  • ex-data recupera el mapa generado por ex-info

Veamos un ejemplo : 

(try

  (throw (ex-info "There was a problem" {:detail 42}))

  (catch Exception e

    (prn (:detail (ex-data e)))))

viernes, 12 de marzo de 2021

Primeros pasos con Clojure, parte 30

 Seguimos con Clojure..


Clojure no es un lenguaje funcional puro, por lo tanto tiene funciones con efecto secundario. Vamos a ver algunas : 


dotimes :

user=> (dotimes [i 3]

         (println i))

0

1

2

nil

Muy parecido a un for, empieza de 0 y sigue hasta 3 - 1.


doseq :

user=> (doseq [n (range 3)]

         (println n))

0

1

2

nil

Itera sobre una secuencia (evaluandola si es lazy). Tambien existe la variante que procesa todas las permutaciones del contenido de la secuencia, veamos un ejemplo : 

user=> (doseq [letter [:a :b]

               number (range 3)] ; list of 0, 1, 2

         (prn [letter number]))

[:a 0]

[:a 1]

[:a 2]

[:b 0]

[:b 1]

[:b 2]

nil


for : 

user=> (for [letter [:a :b]

             number (range 3)] ; list of 0, 1, 2

         [letter number])

([:a 0] [:a 1] [:a 2] [:b 0] [:b 1] [:b 2])


La función for se utiliza para generar listas, como listas por comprensión.