Translate

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



martes, 14 de mayo de 2024

Conversiones de tipos en Erlang


Erlang, como muchos lenguajes, cambia el tipo de un término al convertirlo en otro. Esto se hace con la ayuda de funciones integradas, ya que muchas de las conversiones no se pudieron implementar en Erlang. Cada una de estas funciones toma la forma <tipo>_to_<tipo> y se implementa en el módulo erlang. Éstos son algunos de ellos:


1> erlang:list_to_integer("54").

54

2> erlang:integer_to_list(54).

"54"

3> erlang:list_to_integer("54.32").

** exception error: bad argument

     in function  list_to_integer/1

        called as list_to_integer("54.32")

4> erlang:list_to_float("54.32").

54.32

5> erlang:atom_to_list(true).

"true"

6> erlang:list_to_bitstring("hi there").

<<"hi there">>

7> erlang:bitstring_to_list(<<"hi there">>).

"hi there"


Etcétera. Estamos topando con un problema de lenguaje: debido a que se usa el esquema <tipo>_to_<tipo>, cada vez que se agrega un nuevo tipo al lenguaje, es necesario agregar una gran cantidad de BIF de conversión. Aquí está la lista:


atom_to_binary/2, atom_to_list/1, binary_to_atom/2, binary_to_existing_atom/2, binary_to_list/1, bitstring_to_list/1, binary_to_term/1, float_to_list/1, fun_to_list/1, integer_to_list/1, integer_to_list/2, iolist_to_binary/1, iolist_to_atom/1, list_to_atom/1, list_to_binary/1, list_to_bitstring/1, list_to_existing_atom/1, list_to_float/1, list_to_integer/2, list_to_pid/1, list_to_tuple/1, pid_to_list/1, port_to_list/1, ref_to_list/1, term_to_binary/1, term_to_binary/2 and tuple_to_list/1.



lunes, 13 de mayo de 2024

Tipos en Erlang


Erlang se escribe dinámicamente: cada error se detecta en tiempo de ejecución y el compilador no siempre le gritará al compilar módulos donde las cosas pueden resultar en fallas.

Un punto de fricción clásico entre los defensores de los lenguajes de tipado estático y dinámico tiene que ver con la seguridad del software que se escribe. Una idea sugerida con frecuencia es que los buenos sistemas de tipo estático con compiladores que los aplican con fervor detectarán la mayoría de los errores que esperan ocurrir antes de que puedas ejecutar el código. Como tal, los lenguajes escritos estáticamente deben considerarse más seguros que sus homólogos dinámicos. Si bien esto podría ser cierto en comparación con muchos lenguajes dinámicos, Erlang no está de acuerdo y ciertamente tiene un historial que lo demuestra. El mejor ejemplo son los nueve nueves (99,9999999%) de disponibilidad que se ofrecen en los conmutadores ATM Ericsson AXD 301, que constan de más de 1 millón de líneas de código Erlang. Tenga en cuenta que esto no es una indicación de que ninguno de los componentes de un sistema basado en Erlang haya fallado, sino que un sistema de conmutación general estuvo disponible el 99,9999999 % del tiempo, incluidas las interrupciones planificadas. Esto se debe en parte a que Erlang se basa en la noción de que una falla en uno de los componentes no debería afectar a todo el sistema. Se tienen en cuenta los errores provenientes del programador, fallas de hardware o [algunas] fallas de red: el lenguaje incluye características que le permitirán distribuir un programa a diferentes nodos, manejar errores inesperados y nunca dejar de ejecutarse.

Para abreviar, mientras que la mayoría de los lenguajes y sistemas de tipos tienen como objetivo hacer que un programa esté libre de errores, Erlang utiliza una estrategia en la que se supone que los errores ocurrirán de todos modos y se asegura de cubrir estos casos: El sistema de tipos dinámicos de Erlang no es una barrera para confiabilidad y seguridad de los programas. Esto suena como un montón de palabras proféticas, pero verás cómo se hace.

Históricamente se eligió la escritura dinámica por razones simples; Aquellos que implementaron Erlang al principio procedían en su mayoría de lenguajes escritos dinámicamente y, como tal, tener Erlang dinámico era la opción más natural para ellos.

Erlang también está fuertemente tipado. Un lenguaje débilmente tipado haría conversiones de tipos implícitas entre términos. Si Erlang tuviera un tipo débil, posiblemente podríamos hacer la operación 6 = 5 + "1". mientras que en la práctica, se lanzará una excepción:


1> 6 + "1".

** exception error: bad argument in an arithmetic expression

     in operator  +/2

        called as 6 + "1"


Por supuesto, hay ocasiones en las que es posible que desee convertir un tipo de datos en otro: cambiar cadenas normales en cadenas de bits para almacenarlas o un número entero en un número de punto flotante. La biblioteca estándar de Erlang proporciona una serie de funciones para hacerlo.