Translate

lunes, 13 de marzo de 2023

Vamos a implementar un framework de procesamiento paralelo simple con Cats parte 2

Dado que en el post anterios refrescamos nuestra memoria de Futures, veamos cómo podemos dividir el trabajo en lotes. Podemos consultar la cantidad de CPU disponibles en nuestra máquina mediante una llamada API desde la biblioteca estándar de Java:

Runtime.getRuntime.availableProcessors

// res11: Int = 2

Podemos particionar una secuencia (en realidad cualquier cosa que implemente Vector) usando el método agrupado. Usaremos esto para dividir lotes de trabajo para cada CPU:


(1 to 10).toList.grouped(3).toList

// res12: List[List[Int]] = List(

//List(1, 2, 3),

//List(4, 5, 6),

//List(7, 8, 9),

//List(10)

// )


Implemente una versión paralela de foldMap llamada parallelFoldMap:


def parallelFoldMap[A, B: Monoid] (values: Vector[A]) (func: A => B): Future[B] = {

    // Calculate the number of items to pass to each CPU:

    val numCores = Runtime.getRuntime.availableProcessors

    val groupSize = (1.0 * values.size / numCores).ceil.toInt

    // Create one group for each CPU:

    val groups: Iterator[Vector[A]] = values.grouped(groupSize)

    // Create a future to foldMap each group:

    val futures: Iterator[Future[B]] =

        groups map { group =>

            Future {

                group.foldLeft(Monoid[B].empty)(_ |+| func(_))

            }

    }


    // foldMap over the groups to calculate a final result:

    Future.sequence(futures) map { iterable =>

    iterable.foldLeft(Monoid[B].empty)(_ |+| _)

    }

}

val result: Future[Int] = parallelFoldMap((1 to 1000000).toVector)(identity)

Await.result(result, 1.second)

// res14: Int = 1784293664


Aunque implementamos foldMap nosotros mismos arriba, el método también está disponible como parte de la clase de tipo Foldable:


import cats.Monoid

import cats.instances.int._

// for Monoid

import cats.instances.future._ // for Applicative and Monad

import cats.instances.vector._ // for Foldable and Traverse

import cats.syntax.foldable._// for combineAll and foldMap

import cats.syntax.traverse._// for traverse

import scala.concurrent._

import scala.concurrent.duration._

import scala.concurrent.ExecutionContext.Implicits.global


def parallelFoldMap[A, B: Monoid](values: Vector[A]) (func: A => B): Future[B] = {

    val numCores = Runtime.getRuntime.availableProcessors

    val groupSize = (1.0 * values.size / numCores).ceil.toInt

    values.grouped(groupSize).toVector.traverse(group => Future(group.toVector.foldMap(func)))

        .map(_.combineAll)

}

val future: Future[Int] = parallelFoldMap((1 to 1000).toVector)(_ * 1000)

Await.result(future, 1.second)

// res18: Int = 500500000

Vamos a implementar un framework de procesamiento paralelo simple con Cats

Vamos a implementar un framework de procesamiento paralelo simple pero poderoso utilizando Monoids, Functors y una serie de otras cosas.

Si ha utilizado Hadoop o Spark o ha trabajado en "grandes datos", habrá oído hablar de MapReduce, que es un modelo de programación para realizar el procesamiento de datos en paralelo en grupos de máquinas (también conocidos como "nodos"). Como sugiere el nombre, el modelo se construye alrededor de una fase de map, que es la misma función de map que conocemos de Scala y la clase de tipo Functor, y una fase de reducción, que generalmente llamamos plegar o fold en Scala.

Recuerde que la firma general para el mapa es aplicar una función A => B a una F[A], devolviendo una F[B]

map transforma cada elemento individual en una secuencia de forma independiente. Podemos paralelizar fácilmente porque no hay dependencias entre las transformaciones aplicadas a diferentes elementos (la firma de tipo de la función A => B).

Nuestro paso de reducción se convierte en un pliegue a la izquierda sobre los resultados del map distribuido.

Al distribuir el paso de reducción, perdemos el control sobre el orden de recorrido. Es posible que nuestra reducción general no sea completamente de izquierda a derecha: podemos reducir de izquierda a derecha en varias subsecuencias y luego combinar los resultados. Para asegurar la corrección necesitamos una operación de reducción que sea asociativa:

reduce(a1, reduce(a2, a3)) == reduce(reduce(a1, a2), a3)

Si tenemos asociatividad, podemos distribuir arbitrariamente el trabajo entre nuestros nodos siempre que las subsecuencias en cada nodo permanezcan en el mismo orden que el conjunto de datos inicial.

Nuestra operación de plegado nos obliga a sembrar el cálculo con un elemento de tipo B. Dado que el plegado se puede dividir en un número arbitrario de pasos paralelos, la semilla no debería afectar el resultado del cálculo. Esto naturalmente requiere que la semilla sea un elemento de identidad:

reduce(seed, a1) == reduce(a1, seed) == a1

En resumen, nuestro pliegue paralelo dará los resultados correctos si:

• requerimos que la función reductora sea asociativa;

• Sembramos el cálculo con la identidad de esta función.

¿Cómo suena este patrón? Así es, hemos completado el círculo de regreso a Monoid. El patrón de diseño monoide para trabajos de reducción de mapas está en el centro de los sistemas de big data recientes, como Summingbird de Twitter.

En este proyecto vamos a implementar un map-reduce de una sola máquina muy simple. Comenzaremos implementando un método llamado foldMap para modelar el flujo de datos que necesitamos.

Vimos foldMap es una de las operaciones derivadas que se encuentra encima de foldLeft y foldRight. Sin embargo, en lugar de usar Foldable, volveremos a implementar foldMap aquí nosotros mismos, ya que proporcionará información útil sobre la estructura de map‐reduce. Comience escribiendo la firma de foldMap. Debe aceptar los siguientes parámetros:

• una secuencia de tipo Vector[A];

• una función de tipo A => B, donde hay un Monoide para B;

Deberá agregar parámetros implícitos o límites de contexto para completar la firma de tipo.

import cats.Monoid

/** Single-threaded map-reduce function.

* Maps `func` over `values` and reduces using a `Monoid[B]`.

*/

def foldMap[A, B: Monoid](values: Vector[A])(func: A => B): B =

???

Ahora implemente el cuerpo de foldMap:

  1. comenzar con una secuencia de elementos de tipo A;
  2. mapear sobre la lista para producir una secuencia de elementos de tipo B;
  3. usa el Monoide para reducir los elementos a una sola B.

Aquí hay algunos resultados de muestra para referencia:


import cats.instances.int._ // for Monoid

foldMap(Vector(1, 2, 3))(identity)

// res1: Int = 6

import cats.instances.string._ // for Monoid

// Mapping to a String uses the concatenation monoid:

foldMap(Vector(1, 2, 3))(_.toString + "! ")

// res2: String = "1! 2! 3! "

// Mapping over a String to produce a String:

foldMap("Hello world!".toVector)(_.toString.toUpperCase)

// res3: String = "HELLO WORLD!"


Veamos la implementación: 


import cats.Monoid

import cats.syntax.semigroup._ // for |+|

def foldMap[A, B : Monoid](as: Vector[A])(func: A => B): B =

as.map(func).foldLeft(Monoid[B].empty)(_ |+| _)


Podemos hacer una pequeña alteración a este código para hacer todo en un solo paso:


def foldMap[A, B : Monoid](as: Vector[A])(func: A => B): B =

as.foldLeft(Monoid[B].empty)(_ |+| func(_))


Ahora que tenemos una implementación funcional de subproceso único de foldMap, veamos cómo distribuir el trabajo para que se ejecute en paralelo. Usaremos nuestra versión de subproceso único de foldMap como bloque de construcción. Escribiremos una implementación de CPU múltiple que simule la forma en que distribuiríamos el trabajo en un clúster de reducción de mapa:

  1. comenzamos con una lista inicial de todos los datos que necesitamos procesar;
  2. dividimos los datos en lotes, enviando un lote a cada CPU;
  3. las CPU ejecutan una fase de mapa a nivel de lote en paralelo;
  4. Las CPU ejecutan una fase de reducción de nivel de lote en paralelo, produciendo un local resultado de cada lote;
  5. reducimos los resultados de cada lote a un único resultado final.

Scala proporciona algunas herramientas simples para distribuir el trabajo entre subprocesos. Podríamos usar la biblioteca de colecciones paralelas para implementar una solución, pero desafiémonos a nosotros mismos profundizando un poco más e implementando el algoritmo nosotros mismos usando Futures.

Ya sabemos bastante sobre la naturaleza monádica de Futures. Tomemos un momento para un resumen rápido y para describir cómo se programan los futuros de Scala detrás de escena.

Los futuros se ejecutan en un grupo de subprocesos, determinado por un parámetro ExecutionContext implícito.

Cada vez que creamos un futuro, ya sea a través de una llamada a Future.apply o algún otro combinador, debemos tener un ExecutionContext implícito en el alcance:

import scala.concurrent.Future

import scala.concurrent.ExecutionContext.Implicits.global

val future1 = Future {

     (1 to 100).toList.foldLeft(0)(_ + _)

}

// future1: Future[Int] = Future(Success(5050))

val future2 = Future {

    (100 to 200).toList.foldLeft(0)(_ + _)

}

// future2: Future[Int] = Future(Success(15150))


En este ejemplo, hemos importado un ExecutionContext.Implicits.global. Este contexto predeterminado asigna un grupo de subprocesos con un subproceso por CPU en nuestra máquina. Cuando creamos un futuro, ExecutionContext lo programa para su ejecución. Si hay un subproceso libre en el grupo, Future comienza a ejecutarse de inmediato. La mayoría de las máquinas modernas tienen al menos dos CPU, por lo que en nuestro ejemplo es probable que future1 y future2 se ejecuten en paralelo.

Algunos combinadores crean nuevos Futuros que programan el trabajo en función de los resultados de otros Futuros. Los métodos map y flatMap, por ejemplo, programan cálculos que se ejecutan tan pronto como se calculan sus valores de entrada y hay una CPU disponible:

val future3 = future1.map(_.toString)

// future3: Future[String] = Future(Success(5050))

val future4 = for {

    a <- future1

    b <- future2

} yield a + b

// future4: Future[Int] = Future(Success(20200))


Podemos convertir una List[Future[A]] en una Future[List[A]] usando Future.sequence:


Future.sequence(List(Future(1), Future(2), Future(3)))

// res6: Future[List[Int]] = Future(Success(List(1, 2, 3)))


Podemos convertir una List[Future[A]] en una Future[List[A]] usando Future.sequence:


import cats.instances.future._ // for Applicative

import cats.instances.list._// for Traverse

import cats.syntax.traverse._// for sequence

List(Future(1), Future(2), Future(3)).sequence

// res7: Future[List[Int]] = Future(Success(List(1, 2, 3)))


Se requiere un ExecutionContext en cualquier caso. Finalmente, podemos usar Await.result para bloquear un futuro hasta que haya un resultado disponible:


import scala.concurrent._

import scala.concurrent.duration._

Await.result(Future(1), 1.second) // wait for the result

// res8: Int = 1


También hay implementaciones de Monad y Monoid para Future disponibles en cats.instances.future:


import cats.{Monad, Monoid}

import cats.instances.int._

// for Monoid

import cats.instances.future._ // for Monad and Monoid

Monad[Future].pure(42)

Monoid[Future[Int]].combine(Future(1), Future(2))


Me quedo medio largo el post, así que vamos a seguir en el proximo post. 



viernes, 10 de marzo de 2023

Recursos sobre scala


 Queres empezar en scala y no sabes de donde sacar info? 

Te paso una lista de recursos: 

Platforms

Community

Coding Tools:

Programming Environments

Build Tools

Code Formatting / Linting

Free Books, Tutorials and Guides:

Non-free Books:

Advanced!:

Free Scala Courses: * Functional Programming Principles in Scala
Functional Program Design in Scala
Parallel Programming * Big Data Analysis with Scala and Spark
Introduction to Programming with Dependent Types in Scala (advanced)

Non-Free Courses:

Scala Conferences: * Functional Scala (UK/Remote)
LambdaConf (USA) * Typelevel Summits (Misc.) * Scala by the Bay (USA) * flatMap (Norway) * Scala Up North (Canada) * Scala Days (USA, Europe)
Scala World (UK)
Scala Swarm (Portugal)
Scala.io (France)
Scalar (Central Europe) * Scala Sphere (Poland)
nescala (USA)
LX SCALA (South-West Europe)
ScalaConf (Russia)

Podcasts:

Scala Jobs:

Scala Libraries:

Web Development and Microservices
ZIO HTTP * Caliban (https://github.com/ghostdogpr/caliban) * Play
Akka HTTP
Lagom * Sttp (HTTP Client) * http4s * Finch * Udash - Frontend and Backend
Lift
Scalatra
Skinny
Vert.x
Sangria - GraphQL

Web Front End (Scala.js)

Database Access

Functional Programming

Concurrency / Parallelism

Mathematics

Distributed Computing

Blockchain

Miscellaneous:

Open Source Applications written in Scala

Related Communities:

Blogs/Periodicals:

jueves, 9 de marzo de 2023

Gleam, un lenguaje funcional que corre en la Vm de Erlang.


Gleam es un lenguaje funcional que corre sobre la maquina virtual de Erlang. Es de tipado estatico, funcional y sintaxis similar a Erlang y Elixir.   

Gleam se ejecuta en la máquina virtual Erlang que impulsa sistemas a escala planetaria como WhatsApp y Ericsson, está listo para cargas de trabajo de cualquier tamaño. Gracias a un sistema de simultaneidad basado en múltiples núcleos que puede ejecutar millones de tareas simultáneas, estructuras de datos rápidas e inmutables y un recolector de basura simultáneo, su servicio puede escalar y mantenerse con facilidad.

Gleam viene con compilador, herramienta de compilación, formateador, integraciones de editor y administrador de paquetes, todo integrado, por lo que crear un proyecto Gleam es simplemente ejecutar gleam new.

Como parte del ecosistema BEAM más amplio, los programas Gleam pueden usar miles de paquetes publicados, ya sea que estén escritos en Gleam, Erlang o Elixir.

Sin valores nulos, sin excepciones, mensajes de error claros y un sistema de tipo práctico. Ya sea que esté escribiendo código nuevo o manteniendo código antiguo, Gleam está diseñado para hacer que su trabajo sea lo más divertido y libre de estrés posible.

Gleam también puede compilar en JavaScript, lo que le permite usar su código en el navegador o en cualquier otro lugar donde se pueda ejecutar JavaScript. También genera definiciones de TypeScript, por lo que puede interactuar con su código Gleam con confianza, incluso desde el exterior.


Veamos un pequeño hola mundo hecho en Gleam:

import gleam/io


pub fn main() {

  io.println("hello, friend!")

}


Por ejemplo veamos código asincrono: 


fn spawn_task(i) {

  task.async(fn() {

    let n = int.to_string(i)

    io.println("Hello from " <> n)

  })

}


pub fn main() {

  // Run a million threads, no problem

    list.range(0, 1_000_000)

  |> list.map(spawn_task)

  |> list.each(task.await_forever)

}

Dejo link: https://gleam.run/

martes, 7 de marzo de 2023

¿Tu empresa está lista para dejar que los datos hagan el trabajo pesado?

 

domingo, 5 de marzo de 2023

Primeros pasos con Phoenix


Antes de empezar de manera muy rápida aclaremos que Phoenix es un framework web o para hacer API en Elixir. 

Phoenix está escrito en Elixir, y nuestro código de aplicación también estará escrito en Elixir. Por ende el primer paso es instalar Elixir , en mi caso lo voy a instalar con asdf

asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git

asdf install elixir 1.14.3-otp-25

El código de Elixir se compila en el código de bytes de Erlang para ejecutarse en la máquina virtual de Erlang. Sin Erlang, el código de Elixir no tiene una máquina virtual para ejecutarse, por lo que también debemos instalar Erlang. 

asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git

asdf install erlang 25.0.3

Y ahora vamos a setear las versiones: 

asdf global erlang 25.0.3

asdf global elixir 1.14.3-otp-25

Pero pueden ver como instalar en su equipo en el siguiente link : https://elixir-lang.org/install.html

Cuando instalamos Elixir siguiendo las instrucciones de la página de instalación de Elixir, normalmente también obtendremos Erlang. Para checkear esto hacemos : 

emanuel@crespo:~$ elixir --version

Erlang/OTP 25 [erts-13.1.5] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]


Elixir 1.14.3 (compiled with Erlang/OTP 25)


También podemos escribir erl que es el comando para la relp de Erlang. 

Si acabamos de instalar Elixir por primera vez, también necesitaremos instalar el administrador de paquetes Hex. Hex es necesario para ejecutar una aplicación de Phoenix (mediante la instalación de dependencias) y para instalar cualquier dependencia adicional que podamos necesitar en el camino. En mi caso es : 

mix local.hex


Ahora debemos instalar phoenix, en mi caso voy a instalar la ultima versión

mix archive.install hex phx_new 

Por lo visto, ya esta todo instalado, ahora vamos a crear nuestro primer proyecto: 

mix phx.new hello_world --module HelloWorld

Y si todo salio bien vamos a ejecutarlo: 

cd hello_world/
mix phx.server

Si vamos a http://localhost:4000/ nos aparecerá una pagina. 

Van a ver que tira como 5000 errores porque no configuramos la base de datos, pero eso lo vamos hacer en el proximo post. 

Dejo link; https://www.phoenixframework.org/

viernes, 3 de marzo de 2023

Asdf, multiples sdks en una sola aplicación

 


Asdf, es como sdkman pero para multiples plataformas y lenguajes. Con Asdf puedo instalar node, elixir, python, etc... 

Para instalarlo en linux tenemos que tener git y curl instalados : 


apt install curl git


Usamos git para bajar le código: 

git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.11.2


Luego ejecutamos lo siguiente: 

. "$HOME/.asdf/asdf.sh"

. "$HOME/.asdf/completions/asdf.bash"


Y ya esta!! 


Ahora instalemos nodejs por ejemplo, primero instalamos el plugin para la plataforma: 

asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git


y instalamos node : 

$ asdf install nodejs latest

Trying to update node-build... ok

Downloading node-v19.7.0-linux-x64.tar.gz...

-> https://nodejs.org/dist/v19.7.0/node-v19.7.0-linux-x64.tar.gz

Installing node-v19.7.0-linux-x64...

Installed node-v19.7.0-linux-x64 to /home/emanuel/.asdf/installs/nodejs/19.7.0

En mi caso instalo la versión 19.7.0, ahora tengo que setearla : 

asdf local nodejs 19.7.0

con "asdf list nodejs" puedo saber las versiones que tengo instaladas. 

Y listo!


Dejo link: 



miércoles, 1 de marzo de 2023

The best Golang Learning Resources on the Web


Quiero recomentarles el sitio golang resources que contiene un conjunto de recursos clasificados por nivel de conocimiento. Es muy visual y muy completa. Sin más....

Dejo link: https://golangresources.com/