domingo, 9 de junio de 2024

Recursividad de listas en Gleam



import gleam/io


pub fn main() {

  let sum = sum_list([18, 56, 35, 85, 91], 0)

  io.debug(sum)

}


fn sum_list(list: List(Int), total: Int) -> Int {

  case list {

    [first, ..rest] -> sum_list(rest, total + first)

    [] -> total

  }

}


Si bien es más común usar funciones en el módulo gleam/list para iterar a través de una lista, en ocasiones es posible que prefieras trabajar con la lista directamente.

El patrón [primero, ..rest] coincide en una lista con al menos un elemento, asignando primero el primer elemento a la variable y el resto de la lista a la variable resto. Al usar este patrón y un patrón para la lista vacía [], una función puede ejecutar código en cada elemento de una lista hasta llegar al final.

Este código suma una lista recurriendo a la lista y agregando cada int a un argumento total, devolviéndolo cuando se llega al final.

viernes, 7 de junio de 2024

Más Funciones Recursivas en Erlang parte 2


Hay una propiedad interesante que podemos "descubrir" cuando comparamos funciones recursivas y recursivas de cola escribiendo una función inversa/1, que invertirá una lista de términos. Para tal función, el caso base es una lista vacía, para la cual no tenemos nada que revertir. Podemos simplemente devolver una lista vacía cuando eso suceda. Cualquier otra posibilidad debería intentar converger al caso base llamándose a sí mismo, como con duplicado/2. Nuestra función iterará a través de la lista haciendo coincidir el patrón [H|T] y luego poniendo H después del resto de la lista:

reverse([]) -> [];

reverse([H|T]) -> reverse(T)++[H].


En listas largas, esto será una verdadera pesadilla: no solo acumularemos todas nuestras operaciones de agregados, sino que también necesitaremos recorrer toda la lista para cada uno de estos agregados hasta el último. Para los lectores visuales, los numerosos controles se pueden representar como:


reverse([1,2,3,4]) = [4]++[3]++[2]++[1]

                      ↑    ↵

                   = [4,3]++[2]++[1]

                      ↑ ↑    ↵

                   = [4,3,2]++[1]

                      ↑ ↑ ↑    ↵

                   = [4,3,2,1]


Aquí es donde la recursividad de cola viene al rescate. Debido a que usaremos un acumulador y le agregaremos un nuevo encabezado cada vez, nuestra lista se invertirá automáticamente. Primero veamos la implementación:


tail_reverse(L) -> tail_reverse(L,[]).

 

tail_reverse([],Acc) -> Acc;

tail_reverse([H|T],Acc) -> tail_reverse(T, [H|Acc]).


Si representamos ésta de manera similar a la versión normal, obtenemos:


tail_reverse([1,2,3,4]) = tail_reverse([2,3,4], [1])
                        = tail_reverse([3,4], [2,1])
                        = tail_reverse([4], [3,2,1])
                        = tail_reverse([], [4,3,2,1])
                        = [4,3,2,1]   


Lo que muestra que la cantidad de elementos visitados para revertir nuestra lista ahora es lineal: no solo evitamos hacer crecer la pila, ¡sino que también hacemos nuestras operaciones de una manera mucho más eficiente!

miércoles, 5 de junio de 2024

Más Funciones Recursivas en Erlang


Escribiremos algunas funciones recursivas más, sólo para acostumbrarnos un poco más. Después de todo, al ser la recursividad la única construcción de bucle que existe en Erlang (excepto las listas por comprensión), es uno de los conceptos más importantes que hay que comprender. También es útil en cualquier otro lenguaje de programación funcional.

La primera función que escribiremos será duplicar/2. Esta función toma un número entero como primer parámetro y luego cualquier otro término como segundo parámetro. Luego creará una lista de tantas copias del término como especifique el número entero. Como antes, pensar primero en el caso base es lo que podría ayudarle a ponerse en marcha. Para duplicado/2, pedir repetir algo 0 veces es lo más básico que se puede hacer. Todo lo que tenemos que hacer es devolver una lista vacía, sin importar cuál sea el término. Todos los demás casos deben intentar llegar al caso base llamando a la función misma. También prohibiremos los valores negativos para el número entero, porque no puedes duplicar algo -n veces:


duplicate(0,_) -> [];

duplicate(N,Term) when N > 0 -> [Term|duplicate(N-1,Term)].


Una vez que se encuentra la función recursiva básica, resulta más fácil transformarla en una función recursiva de cola moviendo la construcción de la lista a una variable temporal:


tail_duplicate(N,Term) -> tail_duplicate(N,Term,[]).

 

tail_duplicate(0,_,List) -> List;

tail_duplicate(N,Term,List) when N > 0 -> tail_duplicate(N-1, Term, [Term|List]).




martes, 4 de junio de 2024

Recursión por cola en Gleam


import gleam/io


pub fn main() {

  io.debug(factorial(5))

  io.debug(factorial(7))

}


pub fn factorial(x: Int) -> Int {

  // The public function calls the private tail recursive function

  factorial_loop(x, 1)

}


fn factorial_loop(x: Int, accumulator: Int) -> Int {

  case x {

    0 -> accumulator

    1 -> accumulator


    // The last thing this function does is call itself

    // In the previous lesson the last thing it did was multiply two ints

    _ -> factorial_loop(x - 1, accumulator * x)

  }

}


Cuando se llama a una función, se crea un nuevo marco de pila en la memoria para almacenar los argumentos y las variables locales de la función. Si se crean muchos de estos fotogramas durante la recursividad, entonces el programa utilizará una gran cantidad de memoria o incluso bloqueará el programa si se alcanza algún límite.

Para evitar este problema, Gleam admite la optimización de llamadas de cola, lo que permite al compilador reutilizar el marco de pila para la función actual si una llamada a función es lo último que hace la función, eliminando el costo de memoria.

Las funciones recursivas no optimizadas a menudo se pueden reescribir en funciones optimizadas de llamada final mediante el uso de un acumulador. Un acumulador es una variable que se pasa además de los datos, similar a una variable mutable en un lenguaje con bucles while.

Los acumuladores deben estar ocultos a los usuarios de su código, son detalles de implementación interna. Para hacer esto, escriba una función pública que llame a una función privada recursiva con el valor inicial del acumulador.

domingo, 2 de junio de 2024

Alpaca, un lenguaje funcional, de tipado estático que corre en la VM de Erlang


Me quedo relargo el titulo :( 

Alpaca es un lenguaje de programación funcional inspirado en Elm y Haskell, diseñado para ser simple, seguro y eficiente.

Alpaca es un lenguaje de programación funcional que se ejecuta sobre la máquina virtual de Erlang (BEAM). Está diseñado para aprovechar las ventajas de la concurrencia y la tolerancia a fallos inherentes a la VM de Erlang, mientras proporciona una sintaxis limpia y moderna inspirada en lenguajes como Elm y Haskell. Alpaca está orientado a ser utilizado en el desarrollo de sistemas distribuidos y aplicaciones concurrentes.

Alpaca es un lenguaje puramente funcional, lo que significa que las funciones son ciudadanos de primera clase y no hay efectos secundarios.

Utiliza un sistema de tipos estático y fuerte, lo que ayuda a atrapar errores en tiempo de compilación, mejorando la fiabilidad del código.

Aprovecha la VM de Erlang, famosa por su modelo de actor y su capacidad para manejar grandes volúmenes de procesos concurrentes.

Ideal para desarrollar sistemas distribuidos y aplicaciones que requieren alta disponibilidad.

Alpaca puede interactuar fácilmente con código Erlang, permitiendo a los desarrolladores integrar nuevas funcionalidades en sistemas existentes escritos en Erlang.

Inspirado en Elm y Haskell, Alpaca presenta una sintaxis clara y concisa que facilita la lectura y escritura del código.

Enfocado en la simplicidad, lo que permite a los desarrolladores concentrarse en la lógica del negocio sin distraerse con detalles innecesarios del lenguaje.

Veamos un ejemplo: 


module Factorial


let rec fact n =

  if n == 0 then

    1

  else

    n * fact (n - 1)


En este ejemplo, la función fact calcula el factorial de un número n. Utiliza recursión, una característica común en los lenguajes funcionales, para realizar el cálculo.

Alpaca es un lenguaje de programación funcional prometedor que combina la potencia y fiabilidad de la VM de Erlang con una sintaxis moderna y clara. Es una excelente opción para desarrolladores interesados en sistemas concurrentes y distribuidos, y aquellos que disfrutan de los beneficios de la programación funcional. Con Alpaca, puedes escribir código limpio, eficiente y altamente concurrente, aprovechando al máximo las capacidades de Erlang.


Dejo link: 

https://github.com/alpaca-lang/alpaca

sábado, 1 de junio de 2024

Función Length en erlang


Implementaremos una función para contar cuántos elementos contiene una lista. Entonces sabemos desde el principio que necesitaremos:

  1. un caso base;
  2. una función que se llama a sí misma;
  3. una lista para probar nuestra función.

Con la mayoría de las funciones recursivas, encuentro que el caso base es más fácil de escribir primero: ¿cuál es la entrada más simple a partir de la cual podemos encontrar una longitud? Seguramente una lista vacía es la más simple, con una longitud de 0. Así que tomemos nota mental de que [] = 0 cuando se trata de longitudes. Entonces la siguiente lista más simple tiene una longitud de 1: [_] = 1. Esto parece suficiente para comenzar con nuestra definición. Podemos escribir esto:


len([]) -> 0;

len([_]) -> 1.


Se mencionó anteriormente que las listas se definen recursivamente como [1 | [2| ... [n | []]]]. Esto significa que podemos usar el patrón [H|T] para comparar listas de uno o más elementos, ya que una lista de longitud uno se definirá como [X|[]] y una lista de longitud dos se definirá como [X |[Y|[]]]. Tenga en cuenta que el segundo elemento es una lista en sí. Esto significa que solo necesitamos contar el primero y la función puede llamarse a sí misma en el segundo elemento. Dado que cada valor en una lista cuenta como una longitud de 1, la función se puede reescribir de la siguiente manera:


len([]) -> 0;

len([_|T]) -> 1 + len(T).


Y ahora tienes tu propia función recursiva para calcular la longitud de una lista. Para ver cómo se comportaría len/1 cuando se ejecute, probémoslo en una lista dada, digamos [1,2,3,4]:


len([1,2,3,4]) = len([1 | [2,3,4])

 = 1 + len([2 | [3,4]])

 = 1 + 1 + largo([3 | [4]])

 = 1 + 1 + 1 + len([4 | []])

 = 1 + 1 + 1 + 1 + longitud([])

 = 1 + 1 + 1 + 1 + 0

 = 1 + 1 + 1 + 1

 = 1 + 1 + 2

 = 1 + 3

 = 4


Cuál es la respuesta correcta. 

Recursion en erlang

La recursividad se puede explicar con la ayuda de conceptos y funciones matemáticas. Una función matemática básica como el factorial de un valor es un buen ejemplo de una función que se puede expresar de forma recursiva. El factorial de un número n es el producto de la secuencia 1 x 2 x 3 x ... x n, o alternativamente n x (n-1) x (n-2) x ... x 1. Para dar algunos ejemplos, el factorial de 3 es 3! = 3 x 2 x 1 = 6. ¡El factorial de 4 sería 4! = 4 x 3 x 2 x 1 = 24. Dicha función se puede expresar de la siguiente manera en notación matemática:


Lo que esto nos dice es que si el valor de n que tenemos es 0, devolvemos el resultado 1. Para cualquier valor superior a 0, devolvemos n multiplicado por el factorial de n-1, que se desarrolla hasta llegar a 1:

4! = 4 x 3!

4! = 4x3x2!

4! = 4x3x2x1!

4! = 4 x 3 x 2 x 1 x 1

¿Cómo se puede traducir una función así de la notación matemática a Erlang? La conversión es bastante simple. Eche un vistazo a las partes de la notación: n!, 1 y n((n-1)!) y luego los ifs. Lo que tenemos aquí es un nombre de función (n!), guardias (los if) y el cuerpo de la función (1 y n((n-1)!)). ¡Cambiaremos el nombre de n! a fac(N) para restringir un poco nuestra sintaxis y luego obtenemos lo siguiente:

-module(recursive).

-export([fac/1]).

 

fac(N) when N == 0 -> 1;

fac(N) when N > 0  -> N*fac(N-1).


¡Y esta función factorial ya está hecha! En realidad, es bastante similar a la definición matemática. Con la ayuda de la coincidencia de patrones, podemos acortar un poco la definición:


fac(0) -> 1;

fac(N) when N > 0 -> N*fac(N-1).


Una definición de recursividad podría abreviarse diciendo "una función que se llama a sí misma". Sin embargo, necesitamos tener una condición de parada (el término real es el caso base), porque de lo contrario haríamos un bucle infinito. En nuestro caso, la condición de detención es cuando n es igual a 0. En ese momento ya no le decimos a nuestra función que se llame a sí misma y detiene su ejecución allí mismo.


Recursión en Gleam



import gleam/io


pub fn main() {

  io.debug(factorial(5))

  io.debug(factorial(7))

}


// A recursive functions that calculates factorial

pub fn factorial(x: Int) -> Int {

  case x {

    // Base case

    0 -> 1

    1 -> 1


    // Recursive case

    _ -> x * factorial(x - 1)

  }

}


Gleam no tiene bucles, sino que la iteración se realiza mediante recursividad, es decir, mediante funciones de nivel superior que se llaman a sí mismas con diferentes argumentos.

Una función recursiva debe tener al menos un caso base y al menos un caso recursivo. Un caso base devuelve un valor sin volver a llamar a la función. Un caso recursivo vuelve a llamar a la función con diferentes entradas, volviendo a realizar un bucle.

La biblioteca estándar de Gleam tiene funciones para varios patrones de bucles comunes, algunos de los cuales se introducirán en lecciones posteriores; sin embargo, para bucles más complejos, la recursividad manual suele ser la forma más clara de escribirlo.

La recursividad puede parecer desalentadora o confusa al principio si estás más familiarizado con los lenguajes que tienen características especiales de bucle, ¡pero mantente firme! Con el tiempo, resultará tan familiar y cómodo como cualquier otra forma de iteración.

lunes, 27 de mayo de 2024

Buscar el mayor y el menor con erlang.


Retomando el ejercicio de buscar el mayor con erlang.

Vamos a buscar el menor y el mayor, pero estos algoritmos son muy similares solo hay que cambiar el mayor por el menor. 

El mayor y menor se pueden ver como una función que dado dos objetos retorne el mayor o el menor según corresponda y esta función se la podemos enviar a una función que nos permita encontrar el mayor o menor. Algo así:   


-module(test).

-export([max/1, min/1]).


max(L) -> buscar(L, fun(X, M) -> X > M end).

min(L) -> buscar(L, fun(X, M) -> X < M end).


buscar([X], _) -> X;

buscar([X|T], FX) -> 

    M = buscar(T, FX),

    COND = FX(X, M),

    if COND -> X;  

    true -> M

    end.


Y a compilarlo y probarlo : 

21> c(test).

{ok,test}

22> test:max([1,2,3,4,5]).

5

23> test:min([1,2,3,4,5]).

1

24> 


sábado, 25 de mayo de 2024

Buscar el mayor con erlang.


Vamos a buscar el mayor en una lista con Erlang. Ojo la idea de este algoritmo es practicar recursividad. Seguro que existen funciones que hacen esto super más eficiente. 

El razonamiento es si la lista tiene un elemento, ese elemento es el máximo. Y si tiene más de un elemento el máximo es el primer elemento (si es mayor al máximo del resto) o el máximo del resto si es mayor al primer elemento. 


max([X]) -> X;

max([X|T]) -> 

    M = max(T),

    if X > M -> X;  

    true -> M

    end.


Si probamos : 

> test:max([1,2,3,4,5]).

5

> test:max([2]).

2

> test:max([]). 

** exception error: no function clause matching test:max([]) (test.erl, line 4)

Cuando utilizamos max con una lista vacía nos lanza error, dado que no es posible calcular el máximo de una lista vacía. 


Para proteger un tipo de datos en erlang


Los tipos de datos básicos de Erlang son fáciles de detectar visualmente: las tuplas tienen llaves, las listas tienen corchetes, las cadenas están entre comillas dobles, etc. Por lo tanto, hacer cumplir un determinado tipo de datos ha sido posible con la coincidencia de patrones: una función head/1 tomar una lista solo podría aceptar listas porque de lo contrario, la coincidencia ([H|_]) habría fallado.

Sin embargo, tuvimos un problema con los valores numéricos porque no podíamos especificar rangos. En consecuencia, utilizamos guardias en funciones sobre la temperatura, la edad para conducir, etc. Ahora nos topamos con otro obstáculo. ¿Cómo podríamos escribir una protección que garantice que los patrones coincidan con datos de un tipo específico, como números, átomos o cadenas de bits?

Hay funciones dedicadas a esta tarea. Tomarán un único argumento y devolverán verdadero si el tipo es correcto, falso en caso contrario. Son parte de las pocas funciones permitidas en las expresiones de protección y se denominan BIF de prueba de tipo:


is_atom/1           is_binary/1        

is_bitstring/1      is_boolean/1        is_builtin/3       

is_float/1          is_function/1       is_function/2      

is_integer/1        is_list/1           is_number/1        

is_pid/1            is_port/1           is_record/2        

is_record/3         is_reference/1      is_tuple/1         


Se pueden utilizar como cualquier otra expresión de guardia, siempre que se permitan expresiones de guardia. Quizás te preguntes por qué no existe una función que simplemente proporcione el tipo del término que se está evaluando (algo parecido a type_of(X) -> Type). La respuesta es bastante simple. Erlang se trata de programar para los casos correctos: solo programa para lo que sabe que sucederá y lo que espera. Todo lo demás debería provocar errores lo antes posible. Aunque esto pueda parecer una locura, es de esperar que las explicaciones que obtendrá en Errores y excepciones aclaren las cosas. 

Los BIF de prueba de tipo constituyen más de la mitad de las funciones permitidas en las expresiones de protección. El resto también son BIF, pero no representan pruebas de tipo. Estos son:
abs(Number), bit_size(Bitstring), byte_size(Bitstring), element(N, Tuple), float(Term), hd(List), length(List), node(), node(Pid|Ref|Port), round(Number), self(), size(Tuple|Bitstring), tl(List), trunc(Number), tuple_size(Tuple).

Las funciones nodo/1 y self/0 están relacionadas con Erlang distribuido y procesos/actores. Eventualmente los usaremos, pero todavía tenemos otros temas que cubrir antes de eso.

Puede parecer que las estructuras de datos de Erlang son relativamente limitadas, pero las listas y tuplas suelen ser suficientes para construir otras estructuras complejas sin preocuparse por nada. Como ejemplo, el nodo básico de un árbol binario podría representarse como {nodo, Valor, Izquierda, Derecha}, donde Izquierda y Derecha son nodos similares o tuplas vacías. También podría representarme como:

{person, {name, <<"Fred T-H">>},
{qualities, ["handsome", "smart", "honest", "objective"]},
{faults, ["liar"]},
{skills, ["programming", "bass guitar", "underwater breakdancing"]}}.

Lo que muestra que al anidar tuplas y enumerarlas y llenarlas con datos, podemos obtener estructuras de datos complejas y construir funciones para operar con ellas.

En la versión R13B04 se agregó el BIF binario_to_term/2, que le permite deserializar datos de la misma manera que lo haría binario_to_term/1, excepto que el segundo argumento es una lista de opciones. Si pasa [seguro], el binario no se decodificará si contiene átomos desconocidos o funciones anónimas, lo que podría agotar la memoria.


viernes, 24 de mayo de 2024

Patrones de lista en Gleam

 


import gleam/int

import gleam/io

import gleam/list


pub fn main() {

  let x = list.repeat(int.random(5), times: int.random(3))

  io.debug(x)


  let result = case x {

    [] -> "Empty list"

    [1] -> "List of just 1"

    [4, ..] -> "List starting with 4"

    [_, _] -> "List of 2 elements"

    _ -> "Some other list"

  }

  io.debug(result)

}

Las listas y los valores que contienen pueden coincidir con patrones en expresiones de caso.

Los patrones de lista coinciden en longitudes específicas de lista. El patrón [] coincide con una lista vacía y el patrón [_] coincide con una lista con un elemento. No coincidirán en listas con otras longitudes.

El patrón de distribución... se puede utilizar para que coincida con el resto de la lista. El patrón [1, ..] coincide con cualquier lista que comience con 1. El patrón [_, _, ..] coincide con cualquier lista que tenga al menos dos elementos.


lunes, 20 de mayo de 2024

Lenguajes utilizados en los proyectos apache

Tal vez hace mucho que Apache publico este gráfico pero yo recién lo veo : 



Como se puede ver Java es el lenguaje más utilizado por los proyectos de apache, seguido de python, c++, c, javascript, scala, C#, go, perl, etc ... 




domingo, 19 de mayo de 2024

Patrones en texto de Gleam


import gleam/io


pub fn main() {

  io.debug(get_name("Hello, Joe"))

  io.debug(get_name("Hello, Mike"))

  io.debug(get_name("System still working?"))

}


fn get_name(x: String) -> String {

  case x {

    "Hello, " <> name -> name

    _ -> "Unknown"

  }

}

Se puede utilizar patrones en cadenas de caracteres, el operador <> se puede usar para hacer coincidir cadenas con un prefijo específico.

El patrón "Hello, " <> nombre coincide con cualquier cadena que comience con "Hello, " y asigna el resto de la cadena al nombre de la variable.

jueves, 16 de mayo de 2024

Hacer un servicio con gRPC y go-zero


Hagamos un hola mundo con go-zero utilizando gRPC. Para empezar vamos a hacer el archivo .proto : 

syntax = "proto3";


package hello;


option go_package = "./hello";


message Request {

}


message Response {

  string msg = 1;

}


service Hello {

  rpc Ping(Request) returns(Response);

}


$ goctl rpc protoc hello.proto --go_out=server --go-grpc_out=server --zrpc_out=server

Done.

luego hacemos : 

cd server

go mod tidy 


Completamos el archivo server/internal/logic/pinglogic.go


func (l *PingLogic) Ping(in *hello.Request) (*hello.Response, error) {

return &hello.Response{ Msg: "pong" }, nil

}


y luego en el archivo server/etc/hello.yaml agregamos que estamos trabajando en modo dev: 


Name: hello.rpc
ListenOn: 0.0.0.0:8080
Mode: dev

y por ultimo corremos el proyecto: 

go run hello.go