Translate

jueves, 11 de julio de 2024

Como podemos manejar las referencias nulas?


El error más frecuente en Java es NullPointerException y me imagino que en otros lenguajes alguno similar...  Para abordar esto, se han introducido estructuras y operadores que ayudan a manejar la ausencia de valores de manera más segura y explícita. 

Por ejemplo en Java se introdujo la clase `Optional` en la versión 8 para manejar valores potencialmente nulos de una manera más segura. `Optional` es un contenedor que puede o no contener un valor no nulo.

import java.util.Optional;


public class OptionalExample {

    public static void main(String[] args) {

        Optional<String> optional = Optional.of("Hello, World!");

        

        // Verificar si hay un valor presente

        if (optional.isPresent()) {

            System.out.println(optional.get());

        }

        

        // Uso del método ifPresent

        optional.ifPresent(System.out::println);

        

        // Proveer un valor predeterminado

        String value = optional.orElse("Default Value");

        System.out.println(value);

        

        // Proveer un valor predeterminado usando un Supplier

        value = optional.orElseGet(() -> "Default Value from Supplier");

        System.out.println(value);

    }

}


Scala utiliza la clase `Option` para representar un valor opcional. `Option` tiene dos subclases: `Some` y `None`, lo que proporciona una forma elegante y funcional de manejar valores que pueden estar ausentes. Esta idea es similar a la monada `Maybe` en Haskell.


object OptionExample extends App {

  val someValue: Option[String] = Some("Hello, World!")

  val noneValue: Option[String] = None


  // Uso de getOrElse

  println(someValue.getOrElse("Default Value"))

  println(noneValue.getOrElse("Default Value"))


  // Uso del patrón de coincidencia (Pattern Matching)

  someValue match {

    case Some(value) => println(value)

    case None => println("No value")

  }


  noneValue match {

    case Some(value) => println(value)

    case None => println("No value")

  }

}


Scala "copio" esta forma de Haskell. Haskell utiliza el tipo de datos `Maybe` para manejar valores opcionales `Maybe` puede ser `Just` un valor o `Nothing`.


main :: IO ()

main = do

    let someValue = Just "Hello, World!"

    let noneValue = Nothing


    -- Uso de fromMaybe

    putStrLn (fromMaybe "Default Value" someValue)

    putStrLn (fromMaybe "Default Value" noneValue)


    -- Uso del patrón de coincidencia (Pattern Matching)

    case someValue of

        Just value -> putStrLn value

        Nothing -> putStrLn "No value"


    case noneValue of

        Just value -> putStrLn value

        Nothing -> putStrLn "No value"


Kotlin es similar a Scala en muchos aspectos pero no en este. Kotlin introduce el operador `?` para facilitar la gestión de valores nulos. Este operador se utiliza para declarar tipos de datos que pueden ser nulos y para realizar operaciones seguras contra nulos.


fun main() {

    var nullableString: String? = "Hello, World!"


    // Uso del operador ?. para llamadas seguras

    println(nullableString?.length)


    // Uso del operador ?: para proporcionar un valor predeterminado

    val length = nullableString?.length ?: 0

    println(length)


    nullableString = null


    // Uso de let para ejecutar código solo si el valor no es nulo

    nullableString?.let {

        println(it)

    }

}


C# ha incluido varias características para manejar valores nulos, como el operador `?`, que facilita el manejo seguro de tipos que pueden ser nulos.


using System;


class Program

{

    static void Main()

    {

        string? nullableString = "Hello, World!";

        

        // Uso del operador ?. para llamadas seguras

        Console.WriteLine(nullableString?.Length);


        // Uso del operador ?? para proporcionar un valor predeterminado

        int length = nullableString?.Length ?? 0;

        Console.WriteLine(length);


        nullableString = null;


        // Uso de pattern matching para verificar nulos

        if (nullableString is string nonNullString)

        {

            Console.WriteLine(nonNullString);

        }

    }

}


Rust maneja la ausencia de valores y los errores de una manera robusta utilizando los tipos `Option` y `Result`. `Option` puede ser `Some` o `None`, mientras que `Result` puede ser `Ok` o `Err`.


fn main() {

    let some_value: Option<String> = Some("Hello, World!".to_string());

    let none_value: Option<String> = None;


    // Uso de unwrap_or

    println!("{}", some_value.unwrap_or("Default Value".to_string()));

    println!("{}", none_value.unwrap_or("Default Value".to_string()));


    // Uso del patrón de coincidencia (Pattern Matching)

    match some_value {

        Some(value) => println!("{}", value),

        None => println!("No value"),

    }


    match none_value {

        Some(value) => println!("{}", value),

        None => println!("No value"),

    }

}


Go no tiene un tipo de datos específico para manejar valores opcionales, pero utiliza la convención de retornar múltiples valores, incluyendo un valor y un `error`. Que la verdad no me gusta, te pasas preguntando todo el tiempo si hay error o si los valores son nulos. 


package main


import (

    "errors"

    "fmt"

)


func getValue() (string, error) {

    return "Hello, World!", nil

}


func getNullableValue() (string, error) {

    return "", errors.New("no value")

}


func main() {

    value, err := getValue()

    if err != nil {

        fmt.Println("Error:", err)

    } else {

        fmt.Println("Value:", value)

    }


    nullableValue, err := getNullableValue()

    if err != nil {

        fmt.Println("Error:", err)

    } else {

        fmt.Println("Value:", nullableValue)

    }

}


Python utiliza la palabra clave `None` para representar la ausencia de valor. Aunque no tiene una estructura específica como `Optional`, los desarrolladores pueden utilizar condicionales y manejo de excepciones.


def get_value():

    return "Hello, World!"


def get_nullable_value():

    return None


value = get_value()

nullable_value = get_nullable_value()


if value is not None:

    print(value)

else:

    print("Default Value")


if nullable_value is not None:

    print(nullable_value)

else:

    print("Default Value")


Ruby utiliza `nil` para representar la ausencia de valor. Al igual que en Python, no tiene una estructura específica para valores opcionales, pero proporciona métodos para manejar `nil`.


value = "Hello, World!"

nullable_value = nil


# Uso del operador ||

puts value || "Default Value"

puts nullable_value || "Default Value"


# Uso de condicionales

puts value.nil? ? "Default Value" : value

puts nullable_value.nil? ? "Default Value" : nullable_value


C++ utiliza punteros inteligentes (`smart pointers`) para gestionar la memoria y prevenir errores relacionados con punteros nulos. Los punteros inteligentes, como `std::unique_ptr` y `std::shared_ptr`, se encargan de la gestión automática de la memoria.


#include <iostream>

#include <memory>


int main() {

    std::unique_ptr<int> uniquePtr(new int(42));

    if (uniquePtr) {

        std::cout << *uniquePtr << std::endl;

    }


    std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);

    if (sharedPtr) {

        std::cout << *sharedPtr << std::endl;

    }


    // Uso de weak_ptr para evitar ciclos de referencia

    std::weak_ptr<int> weakPtr = sharedPtr;

    if (auto lockedPtr = weakPtr.lock()) {

        std::cout << *lockedPtr << std::endl;

    }


    return 0;

}


TypeScript, un superconjunto de JavaScript, permite tipos opcionales y tiene un soporte robusto para manejar valores `null` y `undefined`.


let nullableString: string | null = "Hello, World!";


// Uso del operador ? para llamadas seguras

console.log(nullableString?.length ?? 0);


// Uso de if para asegurar valores no nulos

if (nullableString !== null) {

    console.log(nullableString);

}


TypeScript utiliza tipos opcionales para manejar valores que pueden ser `null` o `undefined`, proporcionando un enfoque seguro para evitar errores comunes relacionados con valores nulos. El operador `?.` permite realizar llamadas seguras, y el operador `??` proporciona valores predeterminados en caso de valores `null` o `undefined`.

En fin, aunque la gestión de valores nulos varía entre lenguajes, la idea subyacente es la misma: proporcionar mecanismos más seguros y expresivos para manejar la ausencia de valores. Ya sea mediante clases contenedoras como `Optional` en Java y `Option` en Scala, tipos de datos como `Maybe` en Haskell, operadores específicos como `?` en Kotlin y C#, punteros inteligentes en C++, o enfoques específicos en Rust, Go, Python y Ruby, estos enfoques ayudan a reducir los errores y a escribir un código más robusto y mantenible.


lunes, 8 de julio de 2024

SQLAlchemy un ORM para Python


SQLAlchemy es una librería de SQL para Python que facilita el manejo de bases de datos relacionales. Combina un conjunto completo de herramientas de alto nivel y bajo nivel para la creación y manipulación de esquemas de bases de datos, la construcción de consultas SQL y la gestión de transacciones.

1. SQLAlchemy Core: Proporciona una capa de abstracción de SQL que permite escribir consultas SQL directamente, pero de una manera más segura y con mejor soporte para múltiples bases de datos.

2. SQLAlchemy ORM: Permite mapear clases de Python a tablas de bases de datos y proporciona una forma orientada a objetos para interactuar con las bases de datos.


Puedes instalar SQLAlchemy utilizando pip:


pip install sqlalchemy


Además, si planeas usar una base de datos específica, como SQLite, PostgreSQL o MySQL, necesitarás instalar el conector adecuado. Por ejemplo, para PostgreSQL:


pip install psycopg2


Vamos a empezar con un ejemplo básico de cómo definir un modelo de base de datos usando SQLAlchemy ORM.


from sqlalchemy import create_engine, Column, Integer, String

from sqlalchemy.ext.declarative import declarative_base

from sqlalchemy.orm import sessionmaker


engine = create_engine('sqlite:///example.db', echo=True)


Base = declarative_base()


class User(Base):

    __tablename__ = 'users'


    id = Column(Integer, primary_key=True)

    name = Column(String)

    age = Column(Integer)


    def __repr__(self):

        return f"<User(name={self.name}, age={self.age})>"


# Crear todas las tablas

Base.metadata.create_all(engine)


# Crear una sesión

Session = sessionmaker(bind=engine)

session = Session()

```


En este ejemplo, hemos configurado un motor de base de datos SQLite y definido una clase `User` que mapea a la tabla `users`. Luego, creamos todas las tablas necesarias y configuramos una sesión para interactuar con la base de datos.

Para insertar datos en la base de datos, simplemente creamos instancias de la clase del modelo y las añadimos a la sesión.


# Crear un nuevo usuario

new_user = User(name='John Doe', age=30)


# Añadir el usuario a la sesión

session.add(new_user)


# Confirmar la transacción

session.commit()


SQLAlchemy proporciona una API poderosa y flexible para consultar datos.


# Obtener todos los usuarios

users = session.query(User).all()

print(users)


# Obtener un usuario específico

user = session.query(User).filter_by(name='John Doe').first()

print(user)


Para actualizar un registro, primero lo consultas, modificas los atributos y luego confirmas la transacción.


# Obtener el usuario

user = session.query(User).filter_by(name='John Doe').first()


# Modificar el usuario

user.age = 31


# Confirmar la transacción

session.commit()


Para eliminar un registro, lo consultas, lo eliminas de la sesión y confirmas la transacción.


# Obtener el usuario

user = session.query(User).filter_by(name='John Doe').first()


# Eliminar el usuario

session.delete(user)


# Confirmar la transacción

session.commit()


SQLAlchemy es una herramienta poderosa y flexible para gestionar bases de datos en Python. Su combinación de mapeo orientado a objetos y capacidades de SQL crudo lo hace adecuado para una amplia variedad de aplicaciones. 

Dejo link: https://www.sqlalchemy.org/

sábado, 6 de julio de 2024

Registros en Gleam



import gleam/io


pub type SchoolPerson {

  Teacher(name: String, subject: String)

  Student(String)

}


pub fn main() {

  let teacher1 = Teacher("Mr Schofield", "Physics")

  let teacher2 = Teacher(name: "Miss Percy", subject: "Physics")

  let student1 = Student("Koushiar")

  let student2 = Student("Naomi")

  let student3 = Student("Shaheer")


  let school = [teacher1, teacher2, student1, student2, student3]

  io.debug(school)

}

Una variante de un tipo personalizado puede contener otros datos. En este caso la variante se llama registro.

A los campos de un registro se les pueden asignar etiquetas y, al igual que las etiquetas de argumentos de funciones, se pueden usar opcionalmente al llamar al constructor del registro. Normalmente se utilizarán etiquetas para las variantes que las definen.

Es común tener un tipo personalizado con una variante que contiene datos; este es el equivalente de Gleam de una estructura u objeto en otros lenguajes.

miércoles, 3 de julio de 2024

Aprende a programar en Java desde cero con Java Magician


Si empiezan en el mundo java, les quiere recomendar este sitio que contiene muy buenos tutoriales en español. Los cuales están escritos de forma divertida.  


Dejo link: https://javamagician.com/

martes, 2 de julio de 2024

Tipos personalizados en Gleam



import gleam/io

pub type Season {
  Spring
  Summer
  Autumn
  Winter
}

pub fn main() {
  io.debug(weather(Spring))
  io.debug(weather(Autumn))
}

fn weather(season: Season) -> String {
  case season {
    Spring -> "Mild"
    Summer -> "Hot"
    Autumn -> "Windy"
    Winter -> "Cold"
  }
}


Gleam tiene algunos tipos integrados como Int y String, pero los tipos personalizados permiten la creación de tipos completamente nuevos.

Un tipo personalizado se define con la palabra clave type seguida del nombre del tipo y un constructor para cada variante del tipo. Tanto el nombre del tipo como los nombres de los constructores comienzan con letras mayúsculas.

Las variantes de tipos personalizados pueden coincidir con patrones utilizando una expresión de caso.

lunes, 1 de julio de 2024

Funciones anónimas en Erlang


Las funciones anónimas, o funs, permiten declarar un tipo especial de función en línea, sin nombrarlas. Pueden hacer prácticamente todo lo que pueden hacer las funciones normales, excepto llamarse a sí mismas de forma recursiva (¿cómo podrían hacerlo si son anónimas?). Su sintaxis es:

fun(Args1) -> Expression1, Exp2, ..., ExpN;

     (Args2) -> Expression1, Exp2, ..., ExpN;

     (Args3) -> Expression1, Exp2, ..., ExpN

end


Y se puede utilizar de la siguiente manera:


7> Fn = fun() -> a end.

#Fun<erl_eval.20.67289768>

8> Fn().

a

9> hhfuns:map(fun(X) -> X + 1 end, L).

[2,3,4,5,6]

10> hhfuns:map(fun(X) -> X - 1 end, L).

[0,1,2,3,4]


Y ahora estás viendo una de las cosas que hace que a la gente le guste tanto la programación funcional: la capacidad de hacer abstracciones en un nivel muy bajo de código. De este modo, se pueden ignorar conceptos básicos como los bucles, lo que le permite centrarse en lo que se hace en lugar de en cómo hacerlo.

Las funciones anónimas ya son bastante buenas para este tipo de abstracciones, pero aún tienen más poderes ocultos:


11> PrepareAlarm = fun(Room) ->
11>                     io:format("Alarm set in ~s.~n",[Room]),
11>                     fun() -> io:format("Alarm tripped in ~s! Call Batman!~n",[Room]) end
11>                   end.
#Fun<erl_eval.20.67289768>
12> AlarmReady = PrepareAlarm("bathroom").
Alarm set in bathroom.
#Fun<erl_eval.6.13229925>
13> AlarmReady().
Alarm tripped in bathroom! Call Batman!
ok


¡Sostén el teléfono Batman! ¿Que está pasando aqui? Bueno, antes que nada, declaramos una función anónima asignada a PrepareAlarm. Esta función aún no se ha ejecutado: solo se ejecuta cuando PrepareAlarm("baño"). se llama. En ese momento se evalúa la llamada a io:format/2 y se emite el texto "Alarma configurada". La segunda expresión (otra función anónima) se devuelve a la persona que llama y luego se asigna a AlarmReady. Tenga en cuenta que en esta función, el valor de la variable Habitación se toma de la función 'principal' (PrepareAlarm). Esto está relacionado con un concepto llamado cierres.

Para entender los cierres, primero hay que entender el alcance. El alcance de una función se puede imaginar como el lugar donde se almacenan todas las variables y sus valores. En la función base(A) -> B = A + 1., A y B están definidos como parte del alcance de base/1. Esto significa que en cualquier lugar dentro de base/1, puede hacer referencia a A y B y esperar que se les vincule un valor. Y cuando digo "en cualquier lugar", no bromeo, chico; Esto también incluye funciones anónimas:


base(A) ->

     B = A + 1,

     F = fun() -> A * B end,

     F().


B y A todavía están vinculados al alcance de base/1, por lo que la función F aún puede acceder a ellos. Esto se debe a que F hereda el alcance de base/1. Como la mayoría de los tipos de herencia en la vida real, los padres no pueden obtener lo que tienen los hijos:


base(A) ->

    B = A + 1,

    F = fun() -> C = A * B end,

    F(),

    C.


En esta versión de la función, B sigue siendo igual a A + 1 y F seguirá ejecutándose bien. Sin embargo, la variable C solo está en el alcance de la función anónima en F. Cuando base/1 intenta acceder al valor de C en la última línea, solo encuentra una variable independiente. De hecho, si hubiera intentado compilar esta función, el compilador habría dado un error. La herencia sólo va en un sentido.

Es importante tener en cuenta que el alcance heredado sigue a la función anónima dondequiera que esté, incluso cuando se pasa a otra función:


a() ->

     Secret = "pony",

    fun() -> Secret end.

 

b(F) ->

    "a/0's password is "++F().


Entonces si la compilamos:

14> c(hhfuns).
{ok, hhfuns}
15> hhfuns:b(hhfuns:a()).
"a/0's password is pony"

¿Quién le dijo la contraseña a/0? Bueno, a/0 lo hizo. Si bien la función anónima tiene el alcance de a/0 cuando se declara allí, aún puede transportarla cuando se ejecuta en b/1, como se explicó anteriormente. Esto es muy útil porque nos permite transportar parámetros y contenido fuera de su contexto original, donde todo el contexto ya no es necesario (exactamente como hicimos con Batman en un ejemplo anterior).

Es más probable que utilices funciones anónimas para transportar el estado cuando tienes funciones definidas que toman muchos argumentos, pero tienes uno constante:


16> math:pow(5,2).
25.0
17> Base = 2.
2
18> PowerOfTwo = fun(X) -> math:pow(Base,X) end.
#Fun<erl_eval.6.13229925>
17> hhfuns:map(PowerOfTwo, [1,2,3,4]).
[2.0,4.0,8.0,16.0]

Al envolver la llamada a math:pow/2 dentro de una función anónima con la variable Base vinculada a su alcance, hicimos posible que cada una de las llamadas a PowerOfTwo en hhfuns:map/2 usara los números enteros de la lista como exponentes. de nuestra base.

Una pequeña trampa en la que puedes caer al escribir funciones anónimas es cuando intentas redefinir el alcance:

base() ->
    A = 1,
   (fun() -> A = 2 end)().

Esto declarará una función anónima y luego la ejecutará. Como la función anónima hereda el alcance de base/0, al intentar utilizar el operador = se compara 2 con la variable A (vinculada a 1). Está garantizado que esto fracasará. Sin embargo, es posible redefinir la variable si se hace en el encabezado de la función anidada:

base() ->
    A = 1,
   (fun(A) -> A = 2 end)(2).


Y esto funciona. Si intenta compilarlo, recibirá una advertencia sobre el sombreado ("Advertencia: variable 'A' sombreada en 'diversión'"). El sombreado es el término utilizado para describir el acto de definir una nueva variable que tiene el mismo nombre que una que estaba en el ámbito principal. Esto está ahí para evitar algunos errores (generalmente con razón), por lo que es posible que desee considerar cambiar el nombre de sus variables en estas circunstancias.

A partir de la versión 17.0, el lenguaje admite el uso de funciones anónimas con un nombre interno. Así es, funciones anónimas pero con nombre.

El truco es que el nombre es visible sólo dentro del alcance de la función, no fuera de ella. La principal ventaja de esto es que permite definir funciones recursivas anónimas. Por ejemplo, podríamos crear una función anónima que siga siendo ruidosa para siempre:

18> f(PrepareAlarm), f(AlarmReady).
ok
19> PrepareAlarm = fun(Room) ->
19>    io:format("Alarm set in ~s.~n",[Room]),
19>     fun Loop() ->
19>        io:format("Alarm tripped in ~s! Call Batman!~n",[Room]),
19>        timer:sleep(500),
19>         Loop()
19>     end
19> end.
#Fun<erl_eval.6.71889879>
20> AlarmReady = PrepareAlarm("bathroom").
Alarm set in bathroom.
#Fun<erl_eval.44.71889879>
21> AlarmReady().
Alarm tripped in bathroom! Call Batman!
Alarm tripped in bathroom! Call Batman!
Alarm tripped in bathroom! Call Batman!
...

La variable Loop se refiere a la función anónima en sí y, dentro de ese alcance, se podrá utilizar como cualquier otra variable similar que apunte a una función anónima. 

Dejaremos un poco de lado la teoría de funciones anónimas y exploraremos abstracciones más comunes para evitar tener que escribir más funciones recursivas.

Tuplas en Gleam



 import gleam/io


pub fn main() {

  let triple = #(1, 2.2, "three")

  io.debug(triple)


  let #(a, _, _) = triple

  io.debug(a)

  io.debug(triple.1)

}


Las listas son buenas cuando queremos una colección de un tipo, pero a veces queremos combinar múltiples valores de diferentes tipos. En este caso las tuplas son una opción rápida y cómoda.

La sintaxis de acceso a tupla se puede utilizar para obtener elementos de una tupla sin coincidencia de patrones. some_tuple.0 obtiene el primer elemento, some_tuple.1 obtiene el segundo elemento, etc.

Las tuplas son tipos genéricos, tienen parámetros de tipo para los tipos que contienen. #(1, "¡Hola!") tiene el tipo #(Int, String) y #(1.4, 10, 48) tiene el tipo #(Float, Int, Int).

Las tuplas se usan más comúnmente para devolver 2 o 3 valores de una función. A menudo es más claro utilizar un tipo personalizado donde se podría utilizar una tupla.

Funciones de orden superior en erlang


Una parte importante de todos los lenguajes de programación funcionales es la capacidad de tomar una función y pasarla como parámetro a otra función. Esto, a su vez, vincula ese parámetro de función a una variable que puede usarse como cualquier otra variable dentro de la función. Una función que puede aceptar otras funciones transportadas de esa manera se denomina función de orden superior. Las funciones de orden superior son un poderoso medio de abstracción y una de las mejores herramientas para dominar Erlang.

Nuevamente, este es un concepto arraigado en las matemáticas, principalmente en el cálculo lambda. No entraré en muchos detalles sobre el cálculo lambda porque a algunas personas les cuesta entenderlo y está un poco fuera de alcance. Sin embargo, lo definiré brevemente como un sistema donde todo es una función, incluso los números. Debido a que todo es una función, las funciones deben aceptar otras funciones como parámetros y pueden operar sobre ellas con aún más funciones.

Muy bien, esto puede resultar un poco extraño, así que comencemos con un ejemplo:


-module(hhfuns).

-compile(export_all).

 

one() -> 1.

two() -> 2.

 

add(X,Y) -> X() + Y().


Ahora abra el shell de Erlang, compile el módulo y comience:


1> c(hhfuns).

{ok, hhfuns}

2> hhfuns:add(one,two).

** exception error: bad function one

in function  hhfuns:add/2

3> hhfuns:add(1,2).

** exception error: bad function 1

in function  hhfuns:add/2

4> hhfuns:add(fun hhfuns:one/0, fun hhfuns:two/0).

3


¿Confuso? No tanto, una vez que sabes cómo funciona (¿no es siempre así?) En el comando 2, los átomos uno y dos se pasan a add/2, que luego usa ambos átomos como nombres de funciones (X() + Y ()). Si los nombres de las funciones se escriben sin una lista de parámetros, esos nombres se interpretan como átomos y los átomos no pueden ser funciones, por lo que la llamada falla. Esta es la razón por la que la expresión 3 también falla: los valores 1 y 2 tampoco se pueden llamar funciones, ¡y lo que necesitamos son funciones!

Es por eso que se debe agregar una nueva notación al lenguaje para permitirle pasar funciones desde fuera de un módulo. Esto es lo divertido que es Módulo: Función/Arity: le dice a la VM que use esa función específica y luego la vincule a una variable.

Entonces, ¿cuáles son las ventajas de utilizar funciones de esa manera? Bueno, quizá sea necesario un pequeño ejemplo para entenderlo. Agregaremos algunas funciones a hhfuns que funcionan de forma recursiva en una lista para sumar o restar uno de cada número entero de una lista:


increment([]) -> [];

increment([H|T]) -> [H+1|increment(T)].

 

decrement([]) -> [];

decrement([H|T]) -> [H-1|decrement(T)].


¿Ves cuán similares son estas funciones? Básicamente hacen lo mismo: recorren una lista, aplican una función en cada elemento (+ o -) y luego se llaman a sí mismos nuevamente. Casi nada cambia en ese código: solo la función aplicada y la llamada recursiva son diferentes. El núcleo de una llamada recursiva en una lista como esa es siempre el mismo. Resumiremos todas las partes similares en una sola función (mapa/2) que tomará otra función como argumento:


map(_, []) -> [];

map(F, [H|T]) -> [F(H)|map(F,T)].

 

incr(X) -> X + 1.

decr(X) -> X - 1.


Que luego se puede probar en el shell:


1> c(hhfuns).

{ok, hhfuns}

2> L = [1,2,3,4,5].

[1,2,3,4,5]

3> hhfuns:increment(L).

[2,3,4,5,6]

4> hhfuns:decrement(L).

[0,1,2,3,4]

5> hhfuns:map(fun hhfuns:incr/1, L).

[2,3,4,5,6]

6> hhfuns:map(fun hhfuns:decr/1, L).

[0,1,2,3,4]


Aquí los resultados son los mismos, ¡pero acabas de crear una abstracción muy inteligente! Cada vez que quieras aplicar una función a cada elemento de una lista, solo tienes que llamar a map/2 con tu función como parámetro. Sin embargo, es un poco molesto tener que poner cada función que queremos pasar como parámetro a map/2 en un módulo, nombrarla, exportarla, luego compilarla, etc. De hecho, es claramente poco práctico. Lo que necesitamos son funciones que puedan declararse sobre la marcha...

viernes, 28 de junio de 2024

Guards en Gleam


import gleam/io


pub fn main() {

  let numbers = [1, 2, 3, 4, 5]

  io.debug(get_first_larger(numbers, 3))

  io.debug(get_first_larger(numbers, 5))

}


fn get_first_larger(numbers: List(Int), limit: Int) -> Int {

  case numbers {

    [first, ..] if first > limit -> first

    [_, ..rest] -> get_first_larger(rest, limit)

    [] -> 0

  }

}

La palabra clave if se puede utilizar con expresiones para agregar una protección a un patrón. Una guardia es una expresión que debe evaluarse como Verdadera para que el patrón coincida.

Solo se puede utilizar un conjunto limitado de operadores y no se puede invocar ninguna función.

domingo, 23 de junio de 2024

Árboles binarios en Erlang


En primer lugar, es importante definir qué es un árbol. En nuestro caso, son nodos hasta abajo. Los nodos son tuplas que contienen una clave, un valor asociado a la clave y luego otros dos nodos. De estos dos nodos, necesitamos uno que tenga una clave más pequeña y otro que tenga una clave más grande que el nodo que los contiene. ¡Así que aquí está la recursividad! Un árbol es un nodo que contiene nodos, cada uno de los cuales contiene nodos, que a su vez también contienen nodos. Esto no puede continuar para siempre (no tenemos infinitos datos para almacenar), por lo que diremos que nuestros nodos también pueden contener nodos vacíos.

Para representar nodos, las tuplas son una estructura de datos apropiada. Para nuestra implementación, podemos definir estas tuplas como {node, {Key, Value, Smaller, Larger}} , donde Smaller y Larger pueden ser otro nodo similar o un nodo vacío ({nodo, nil} ). 

Comencemos a construir un módulo para nuestra implementación de árbol muy básica. La primera función, vacía/0, devuelve un nodo vacío. El nodo vacío es el punto de partida de un nuevo árbol, también llamado raíz:

-module(tree).

-export([empty/0, insert/3, lookup/2]).

 

empty() -> {node, 'nil'}.


Al usar esa función y luego encapsular todas las representaciones de nodos de la misma manera, ocultamos la implementación del árbol para que la gente no necesite saber cómo está construido. Toda esa información puede estar contenida únicamente en el módulo. Si alguna vez decide cambiar la representación de un nodo, puede hacerlo sin romper el código externo.

Para agregar contenido a un árbol, primero debemos entender cómo navegar recursivamente a través de él. Procedamos de la misma manera que lo hicimos con todos los demás ejemplos de recursividad, intentando encontrar el caso base. Dado que un árbol vacío es un nodo vacío, nuestro caso base es lógicamente un nodo vacío. Entonces, cada vez que lleguemos a un nodo vacío, ahí es donde podemos agregar nuestra nueva clave/valor. El resto del tiempo, nuestro código tiene que recorrer el árbol intentando encontrar un nodo vacío donde poner contenido.

Para encontrar un nodo vacío comenzando desde la raíz, debemos aprovechar el hecho de que la presencia de nodos más pequeños y más grandes nos permite navegar comparando la nueva clave que tenemos que insertar con la clave del nodo actual. Si la nueva clave es más pequeña que la clave del nodo actual, intentamos encontrar el nodo vacío dentro de Smaller, y si es más grande, dentro de Larger. Sin embargo, hay un último caso: ¿qué pasa si la nueva clave es igual a la clave del nodo actual? Allí tenemos dos opciones: dejar que el programa falle o reemplazar el valor por el nuevo. Esta es la opción que tomaremos aquí. Poner en una función toda esta lógica funciona de la siguiente manera:


insert(Key, Val, {node, 'nil'}) ->

    {node, {Key, Val, {node, 'nil'}, {node, 'nil'}}};

insert(NewKey, NewVal, {node, {Key, Val, Smaller, Larger}}) when NewKey < Key ->

    {node, {Key, Val, insert(NewKey, NewVal, Smaller), Larger}};

insert(NewKey, NewVal, {node, {Key, Val, Smaller, Larger}}) when NewKey > Key ->

    {node, {Key, Val, Smaller, insert(NewKey, NewVal, Larger)}};

insert(Key, Val, {node, {Key, _, Smaller, Larger}}) ->

    {node, {Key, Val, Smaller, Larger}}.


Tenga en cuenta aquí que la función devuelve un árbol completamente nuevo. Esto es típico de los lenguajes funcionales que tienen una sola asignación. Si bien esto puede considerarse ineficiente, la mayoría de las estructuras subyacentes de dos versiones de un árbol a veces resultan ser las mismas y, por lo tanto, la VM las comparte y las copia solo cuando es necesario.

Lo que queda por hacer en esta implementación de árbol de ejemplo es crear una función de búsqueda/2 que le permitirá encontrar un valor de un árbol proporcionando su clave. La lógica necesaria es extremadamente similar a la que se usa para agregar contenido nuevo al árbol: recorremos los nodos y verificamos si la clave de búsqueda es igual, menor o mayor que la clave del nodo actual. Tenemos dos casos base: uno cuando el nodo está vacío (la clave no está en el árbol) y otro cuando se encuentra la clave. Como no queremos que nuestro programa falle cada vez que buscamos una clave que no existe, devolveremos el átomo "indefinido". De lo contrario, devolveremos {ok, Valor}. La razón de esto es que si solo devolviéramos Valor y el nodo contuviera el átomo 'indefinido', no tendríamos forma de saber si el árbol devolvió el valor correcto o no pudo encontrarlo. Al agrupar los casos exitosos en una tupla de este tipo, facilitamos la comprensión de cuál es cuál. Aquí está la función implementada:


lookup(_, {node, 'nil'}) ->

undefined;

lookup(Key, {node, {Key, Val, _, _}}) ->

{ok, Val};

lookup(Key, {node, {NodeKey, _, Smaller, _}}) when Key < NodeKey ->

lookup(Key, Smaller);

lookup(Key, {node, {_, _, _, Larger}}) ->

lookup(Key, Larger).


Y hemos terminado. Probémoslo haciendo una pequeña libreta de direcciones de correo electrónico. Compile el archivo e inicie el shell:


1> T1 = tree:insert("Jim Woodland", "jim.woodland@gmail.com", tree:empty()).

{node,{"Jim Woodland","jim.woodland@gmail.com",

{node,nil},

{node,nil}}}

2> T2 = tree:insert("Mark Anderson", "i.am.a@hotmail.com", T1).

{node,{"Jim Woodland","jim.woodland@gmail.com",

{node,nil},

{node,{"Mark Anderson","i.am.a@hotmail.com",

{node,nil},

{node,nil}}}}}

3> Addresses = tree:insert("Anita Bath", "abath@someuni.edu", tree:insert("Kevin Robert", "myfairy@yahoo.com", tree:insert("Wilson Longbrow", "longwil@gmail.com", T2))).

{node,{"Jim Woodland","jim.woodland@gmail.com",

{node,{"Anita Bath","abath@someuni.edu",

{node,nil},

{node,nil}}},

{node,{"Mark Anderson","i.am.a@hotmail.com",

{node,{"Kevin Robert","myfairy@yahoo.com",

{node,nil},

{node,nil}}},

{node,{"Wilson Longbrow","longwil@gmail.com",

{node,nil},

{node,nil}}}}}}}


Y ahora puedes buscar direcciones de correo electrónico con él:

4> tree:lookup("Anita Bath", Addresses).

{ok, "abath@someuni.edu"}

5> tree:lookup("Jacques Requin", Addresses).

undefined


¡Esto concluye nuestro ejemplo de libreta de direcciones funcional construida a partir de una estructura de datos recursiva distinta de una lista!


viernes, 21 de junio de 2024

Hola mundo en Vaadin


Vamos a hacer un hola mundo en Vaadin. Yo voy a dar por sobreentendido que sabemos crear un proyecto con spring. Si no lo sabes es tan fácil como ir a https://start.spring.io/ y de ahi vas configurando las cosas y agregamos el framework vaadin. 

Luego creamos un nuevo paquete como por ejemplo : com.example.vaadin.ui. Dentro del paquete, creamos una nueva clase MainView.java


package com.example.vaadin.ui;


import com.vaadin.flow.component.button.Button;

import com.vaadin.flow.component.notification.Notification;

import com.vaadin.flow.component.orderedlayout.VerticalLayout;

import com.vaadin.flow.router.Route;


@Route("")

public class MainView extends VerticalLayout {


    public MainView() {

        Button button = new Button("Haz clic aquí",

                event -> Notification.show("¡Hola, Mundo!"));

        add(button);

    }

}


Y listo!! Vamos a localhost:8080 para chequear nuestro "Hola mundo". 

martes, 18 de junio de 2024

Inyectar condicionalmente un bean con profiles


Tengo 2 beans que implementan una interfaz, por ejemplo un repositorio jpa con una base de datos y otro con archivos o en memoria, ponele... Y quiero algunas veces utilizar un bean y otras otro dependiendo de una configuración, esto se puede resolver de 2 maneras. Se puedes utilizar la anotación @Conditional o @Profile.

Veamos ejemplo utilizando profile: 


public interface MyService {

    void performService();

}


@Service

@Profile("serviceA")

public class ServiceA implements MyService {

    @Override

    public void performService() {

        System.out.println("Service A implementation");

    }

}


@Service

@Profile("serviceB")

public class ServiceB implements MyService {

    @Override

    public void performService() {

        System.out.println("Service B implementation");

    }

}


Tenemos que configurar el perfil activo en application.properties:


# Para usar ServiceA
spring.profiles.active=serviceA

# Para usar ServiceB
#spring.profiles.active=serviceB


Y inyectamos el bean:


@RestController
public class MyController {

    private final MyService myService;

    @Autowired
    public MyController(MyService myService) {
        this.myService = myService;
    }

    @GetMapping("/perform")
    public String perform() {
        myService.performService();
        return "Service performed";
    }
}

Y listo!!

lunes, 17 de junio de 2024

Alias ​​en patrones en Gleam


import gleam/io


pub fn main() {

  io.debug(get_first_non_empty([[], [1, 2, 3], [4, 5]]))

  io.debug(get_first_non_empty([[1, 2], [3, 4, 5], []]))

  io.debug(get_first_non_empty([[], [], []]))

}


fn get_first_non_empty(lists: List(List(t))) -> List(t) {

  case lists {

    [[_, ..] as first, ..] -> first

    [_, ..rest] -> get_first_non_empty(rest)

    [] -> []

  }

}

El operador as se puede utilizar para asignar subpatrones a variables.

El patrón [_, ..] como primero coincidirá con cualquier lista que no esté vacía y asignará esa lista a la variable first.

domingo, 16 de junio de 2024

Quicksort en Erlang


Una implementación ingenua de Quicksort funciona tomando el primer elemento de una lista, el pivote, y luego colocando todos los elementos más pequeños o iguales al pivote en una nueva lista, y todos los más grandes en otra lista. Luego tomamos cada una de estas listas y hacemos lo mismo con ellas hasta que cada lista se hace cada vez más pequeña. Esto continúa hasta que no tenga nada más que una lista vacía para ordenar, que será nuestro caso base. Se dice que esta implementación es ingenua porque las versiones más inteligentes de Quicksort intentarán elegir pivotes óptimos para ser más rápidos. Sin embargo, eso realmente no nos importa para nuestro ejemplo.

Necesitaremos dos funciones para esto: una primera función para dividir la lista en partes más pequeñas y más grandes y una segunda función para aplicar la función de partición en cada una de las nuevas listas y unirlas. En primer lugar, escribiremos la función de pegamento:

quicksort([]) -> [];

quicksort([Pivot|Rest]) ->

{Smaller, Larger} = partition(Pivot,Rest,[],[]),

quicksort(Smaller) ++ [Pivot] ++ quicksort(Larger).


Esto muestra el caso base, una lista ya dividida en partes más grandes y más pequeñas por otra función, el uso de un pivote con ambas listas ordenadas rápidamente añadidas antes y después. Entonces esto debería encargarse de armar listas. Ahora la función de partición:


partition(_,[], Smaller, Larger) -> {Smaller, Larger};

partition(Pivot, [H|T], Smaller, Larger) ->

if H =< Pivot -> partition(Pivot, T, [H|Smaller], Larger);

H >  Pivot -> partition(Pivot, T, Smaller, [H|Larger])

end.


Y ahora puede ejecutar su función de clasificación rápida. Si ha buscado ejemplos de Erlang en Internet antes, es posible que haya visto otra implementación de clasificación rápida, una que es más simple y fácil de leer, pero que utiliza listas por comprensión. Las piezas fáciles de reemplazar son las que crean nuevas listas, la función partición/4:


lc_quicksort([]) -> [];

lc_quicksort([Pivot|Rest]) ->

lc_quicksort([Smaller || Smaller <- Rest, Smaller =< Pivot])

++ [Pivot] ++

lc_quicksort([Larger || Larger <- Rest, Larger > Pivot]).


Las principales diferencias son que esta versión es mucho más fácil de leer, pero a cambio, tiene que recorrer la lista dos veces para dividirla en dos partes. Esta es una lucha entre la claridad y el rendimiento, pero el verdadero perdedor aquí eres tú, porque ya existe una función listas:ordenar/1. Usa ese en su lugar.

Toda esta concisión es buena para fines educativos, pero no para el rendimiento. ¡Muchos tutoriales de programación funcional nunca mencionan esto! En primer lugar, aquí ambas implementaciones deben procesar valores que sean iguales al pivote más de una vez. En su lugar, podríamos haber decidido devolver 3 listas: elementos más pequeños, más grandes e iguales al pivote para hacerlo más eficiente.

viernes, 14 de junio de 2024

Patrones alternativos en Gleam


import gleam/int

import gleam/io


pub fn main() {

  let number = int.random(10)

  io.debug(number)


  let result = case number {

    2 | 4 | 6 | 8 -> "This is an even number"

    1 | 3 | 5 | 7 -> "This is an odd number"

    _ -> "I'm not sure"

  }

  io.debug(result)

}


Se puede realizar una acción con varias cohicidencias de patrones con el operador | . Si alguno de los patrones coincide, entonces la cláusula coincide.

Si un patrón define una variable, entonces todos los patrones alternativos para esa cláusula también deben definir una variable con el mismo nombre y el mismo tipo.

Actualmente no es posible tener patrones alternativos anidados, por lo que el patrón [1 | 2 | 3] no es válido.

martes, 11 de junio de 2024

Patrones con múltiples valores en Gleam


import gleam/int

import gleam/io


pub fn main() {

  let x = int.random(2)

  let y = int.random(2)

  io.debug(x)

  io.debug(y)


  let result = case x, y {

    0, 0 -> "Both are zero"

    0, _ -> "First is zero"

    _, 0 -> "Second is zero"

    _, _ -> "Neither are zero"

  }

  io.debug(result)

}

A veces necesitaamos patrones en múltiples valores.  Para hacer esto, puedes dar múltiples temas y múltiples patrones, separados por comas.

Al hacer coincidir varios temas, debe haber la misma cantidad de patrones que temas.


Más Funciones Recursivas en Erlang parte 4


Para llevar las cosas un poco más allá, escribiremos una función de compresión. Una función de compresión tomará dos listas de la misma longitud como parámetros y las unirá como una lista de tuplas que contienen dos términos. Nuestra propia función zip/2 se comportará de esta manera:


1> recursive:zip([a,b,c],[1,2,3]).

[{a,1},{b,2},{c,3}]


Dado que queremos que nuestros parámetros tengan la misma longitud, el caso base comprimirá dos listas vacías:


zip([],[]) -> [];

zip([X|Xs],[Y|Ys]) -> [{X,Y}|zip(Xs,Ys)].


Sin embargo, si desea una función zip más indulgente, puede decidir que finalice cada vez que finalice una de las dos listas. Por lo tanto, en este escenario, tiene dos casos base:


lenient_zip([],_) -> [];

lenient_zip(_,[]) -> [];

lenient_zip([X|Xs],[Y|Ys]) -> [{X,Y}|lenient_zip(Xs,Ys)].


Observe que no importa cuáles sean nuestros casos base, la parte recursiva de la función sigue siendo la misma. 

La recursividad de cola como se ve aquí no hace que la memoria crezca porque cuando la máquina virtual ve una función que se llama a sí misma en una posición de cola (la última expresión que se evaluará en una función), elimina el marco de pila actual. Esto se denomina optimización de llamada final (TCO) y es un caso especial de una optimización más general denominada optimización de última llamada (LCO).

LCO se realiza siempre que la última expresión que se evaluará en el cuerpo de una función es otra llamada de función. Cuando eso sucede, al igual que con el TCO, Erlang VM evita almacenar el marco de la pila. Como tal, la recursividad de cola también es posible entre múltiples funciones. Como ejemplo, la cadena de funciones a() -> b(). b()->c(). c()->a(). creará efectivamente un bucle infinito que no se quedará sin memoria ya que LCO evita el desbordamiento de la pila. Este principio, combinado con nuestro uso de acumuladores, es lo que hace que la recursividad de cola sea útil.

lunes, 10 de junio de 2024

Más Funciones Recursivas en Erlang parte 3


Otra función a implementar podría ser sublist/2, que toma una lista L y un número entero N, y devuelve los N primeros elementos de la lista. Como ejemplo, sublista([1,2,3,4,5,6],3) devolvería [1,2,3]. Nuevamente, el caso base intenta obtener 0 elementos de una lista. Sin embargo, tenga cuidado, porque sublist/2 es un poco diferente. ¡Tienes un segundo caso base cuando la lista aprobada está vacía! Si no verificamos si hay listas vacías, se generará un error al llamar a recursive:sublist([1],2). mientras que queremos [1] en su lugar. Una vez definido esto, la parte recursiva de la función solo tiene que recorrer la lista, manteniendo los elementos a medida que avanza, hasta llegar a uno de los casos base:

sublist(_,0) -> [];

sublist([],_) -> [];

sublist([H|T],N) when N > 0 -> [H|sublist(T,N-1)].


Que luego se puede transformar a una forma recursiva de cola de la misma manera que antes:


tail_sublist(L, N) -> tail_sublist(L, N, []).

 

tail_sublist(_, 0, SubList) -> SubList;

tail_sublist([], _, SubList) -> SubList;

tail_sublist([H|T], N, SubList) when N > 0 -> tail_sublist(T, N-1, [H|SubList]).


Hay un defecto en esta función. ¡Un defecto fatal! Usamos una lista como acumulador exactamente de la misma manera que lo hicimos para invertir nuestra lista. Si compila esta función tal como está, sublist([1,2,3,4,5,6],3) no devolverá [1,2,3], sino [3,2,1]. Lo único que podemos hacer es tomar el resultado final y revertirlo nosotros mismos. Simplemente cambie la llamada tail_sublist/2 y deje intacta toda nuestra lógica recursiva:


tail_sublist(L, N) -> reverse(tail_sublist(L, N, [])).


El resultado final estará ordenado correctamente. Podría parecer que revertir nuestra lista después de una llamada recursiva de cola es una pérdida de tiempo y estaría en parte en lo cierto (todavía ahorramos memoria al hacer esto). En listas más cortas, es posible que descubra que su código se ejecuta más rápido con llamadas recursivas normales que con llamadas recursivas de cola por este motivo, pero a medida que sus conjuntos de datos crezcan, invertir la lista será comparativamente más ligero.

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 ...