Translate

viernes, 29 de diciembre de 2023

IntoIterator en Rust


El trait Iterator le indica cómo iterar una vez que haya creado un iterador. El trait relacionado IntoIterator define cómo crear un iterador para un tipo. Es utilizado automáticamente por el bucle for.


struct Grid {

    x_coords: Vec<u32>,

    y_coords: Vec<u32>,

}


impl IntoIterator for Grid {

    type Item = (u32, u32);

    type IntoIter = GridIter;

    fn into_iter(self) -> GridIter {

        GridIter { grid: self, i: 0, j: 0 }

    }

}


struct GridIter {

    grid: Grid,

    i: usize,

    j: usize,

}


impl Iterator for GridIter {

    type Item = (u32, u32);


    fn next(&mut self) -> Option<(u32, u32)> {

        if self.i >= self.grid.x_coords.len() {

            self.i = 0;

            self.j += 1;

            if self.j >= self.grid.y_coords.len() {

                return None;

            }

        }

        let res = Some((self.grid.x_coords[self.i], self.grid.y_coords[self.j]));

        self.i += 1;

        res

    }

}


fn main() {

    let grid = Grid {

        x_coords: vec![3, 5, 7, 9],

        y_coords: vec![10, 20, 30, 40],

    };

    for (x, y) in grid {

        println!("point = {x}, {y}");

    }

}


Cada implementación de IntoIterator debe declarar dos tipos:

  • Item: el tipo sobre el que se va a iterar, como i8,
  • IntoIter: el tipo de Iterator devuelto por el método into_iter.

Tenga en cuenta que IntoIter y Item están vinculados: el iterador debe tener el mismo tipo de elemento, lo que significa que devuelve Option<Item>

El ejemplo itera sobre todas las combinaciones de coordenadas x e y.


domingo, 24 de diciembre de 2023

Iterator en Rust


El trait Iterator admite la iteración sobre valores en una colección. Requiere un método next y proporciona muchos métodos. Muchos tipos de bibliotecas estándar implementan Iterator y usted también puede implementarlo usted mismo:


struct Fibonacci {

    curr: u32,

    next: u32,

}


impl Iterator for Fibonacci {

    type Item = u32;


    fn next(&mut self) -> Option<Self::Item> {

        let new_next = self.curr + self.next;

        self.curr = self.next;

        self.next = new_next;

        Some(self.curr)

    }

}


fn main() {

    let fib = Fibonacci { curr: 0, next: 1 };

    for (i, n) in fib.enumerate().take(5) {

        println!("fib({i}): {n}");

    }

}


Iterator implementa muchas operaciones de programación funcional comunes sobre colecciones (por ejemplo, mapear, filtrar, reducir, etc.). Este es el rasgo donde puedes encontrar toda la documentación sobre ellos. En Rust, estas funciones deberían producir un código tan eficiente como las implementaciones imperativas equivalentes.

sábado, 23 de diciembre de 2023

Invertir una lista en prolog


Si queremos invertir una lista en prolog lo que podemos hacer es utilizar el predicado union del post anterior y hacer lo siguiente : 


inversa([], []).

inversa([H|T], L) :-

    inversa(T, IT),

    union(IT, [H], L).


Lo probamos : 


inversa([1,2,3,4], X)

X = [4, 3, 2, 1]

Introducción a gRPC


gRPC es un sistema de llamadas a procedimientos remotos (RPC) de código abierto desarrollado por Google. Se basa en el protocolo HTTP/2 para la comunicación entre servicios, lo que ofrece una comunicación eficiente y de bajo consumo de recursos.

Características Principales:

  • Protocolo basado en HTTP/2: Utiliza HTTP/2 como su protocolo subyacente, lo que proporciona una comunicación más rápida, eficiente y multiplexada.
  • IDL y generación de código: Utiliza Protocol Buffers (protobuf) para definir la estructura del servicio y los mensajes intercambiados entre los clientes y servidores. Esto permite la generación automática de código en varios lenguajes.
  • Soporte para múltiples lenguajes: Ofrece soporte para una variedad de lenguajes de programación, lo que permite a los desarrolladores construir servicios heterogéneos y sistemas distribuidos.
  • Tipado fuerte y serialización eficiente: Utiliza Protocol Buffers para la serialización de datos, lo que resulta en una comunicación más rápida y eficiente que otros métodos de serialización.

Ventajas:

  • Alta eficiencia y rendimiento en la comunicación entre microservicios.
  • Facilita la creación de servicios interoperables en diferentes lenguajes.
  • Útil en entornos distribuidos, como arquitecturas de microservicios, Internet de las cosas (IoT) y sistemas basados en la nube.


Veamos un ejemplo de un protobuf:

syntax = "proto3";


service Greeter {

  rpc SayHello (HelloRequest) returns (HelloResponse) {}

}


message HelloRequest {

  string name = 1;

}


message HelloResponse {

  string message = 1;

}



gRPC ofrece una forma eficiente y poderosa de comunicación entre servicios, facilitando el desarrollo de aplicaciones distribuidas escalables y de alto rendimiento. Su capacidad para generar código y su enfoque en la eficiencia lo convierten en una herramienta valiosa para la construcción de sistemas modernos y distribuidos.

Tal vez te preguntas si esto va a sustituir a Rest y la respuesta es no, Rest proporciona una interfaz clara tanto para aplicaciones como para humanos para aplicaciones en cambio gRPC sacrifica la claridad de las apis en post de una mayor eficiencia. 

Si tenes que hacer una API que no interesa tanto la performance y es necesario que sea super clara para que todo el mundo la use, Rest es la mejor opción. Si es necesaria una comunicación rápida entre sistemas que tengo el control, gRPC viene bien. 

Dejo link: https://grpc.io/



viernes, 22 de diciembre de 2023

Unir dos listas en prolog


Vamos a hacer un predicado que una dos listas en prolog: 


unir([], Lista, Lista).

unir([X | Resto], Lista, [X | Resultado]) :-

   unir(Resto, Lista, Resultado).


Y lo vamos a probar : 

unir([5,6,7],[1,2,3,4], X)

X = [5, 6, 7, 1, 2, 3, 4]

jueves, 21 de diciembre de 2023

Maps en Go


Los maps son una estructura de datos que asocia valores de un tipo (la clave) con valores de otro tipo (el elemento o valor). La clave puede ser de cualquier tipo para el que esté definido el operador de igualdad, como números enteros, números de punto flotante y complejos, cadenas, punteros, interfaces (siempre que el tipo dinámico admita la igualdad), estructuras y matrices. Los slices no se pueden utilizar como claves de map porque la igualdad no está definida en ellos. Al igual que los slices, los map contienen referencias a una estructura de datos subyacente. Si pasa un map a una función que cambia el contenido del map, los cambios serán visibles en la persona que llama.

Los map se pueden construir utilizando la sintaxis literal compuesta habitual con pares clave-valor separados por dos puntos, por lo que es fácil construirlos durante la inicialización.0


var timeZone = map[string]int{

    "UTC":  0*60*60,

    "EST": -5*60*60,

    "CST": -6*60*60,

    "MST": -7*60*60,

    "PST": -8*60*60,

}


Asignar y recuperar valores de maps parece sintácticamente igual que hacer lo mismo con matrices y slices, excepto que no es necesario que el índice sea un número entero.


offset := timeZone["EST"]


Un intento de recuperar un valor de mapa con una clave que no está presente en el mapa devolverá el valor cero para el tipo de entradas en el mapa. Por ejemplo, si el mapa contiene números enteros, buscar una clave inexistente devolverá 0. Un conjunto se puede implementar como un mapa con valor de tipo bool. 

attended := map[string]bool{

    "Ann": true,

    "Joe": true,

    ...

}


if attended[person] { // will be false if person is not in the map

    fmt.Println(person, "was at the meeting")

}


A veces es necesario distinguir una entrada faltante de un valor cero. ¿Hay una entrada para "UTC" o es 0 porque no está en el mapa? Se puede discriminar con una forma de asignación múltiple.


var seconds int

var ok bool

seconds, ok = timeZone[tz]


Por razones obvias, esto se denomina modismo "coma ok". En este ejemplo, si tz está presente, los segundos se configurarán apropiadamente y ok será verdadero; de lo contrario, los segundos se establecerán en cero y ok será falso. Aquí hay una función que lo combina con un bonito informe de errores:


func offset(tz string) int {

    if seconds, ok := timeZone[tz]; ok {

        return seconds

    }

    log.Println("unknown time zone:", tz)

    return 0

}


Para probar la presencia en el mapa sin preocuparse por el valor real, puede utilizar el identificador en blanco (_) en lugar de la variable habitual para el valor.


_, present := timeZone[tz]


Para eliminar una entrada de mapa, utilice la función incorporada de eliminación, cuyos argumentos son el mapa y la clave que se va a eliminar. Es seguro hacer esto incluso si la clave ya no está en el mapa.


delete(timeZone, "PDT")  // Now on Standard Time


martes, 19 de diciembre de 2023

Predicado que nos indique si un elemento existe en una lista en prolog


Vamos a hacer un predicado que nos indique si un elemento existe en una lista en prolog: 


existe(Elemento, [Elemento | _]).

existe(Elemento, [_ | Tail]) :-

    existe(Elemento, Tail).


Si la probamos : 


existe(5,[1,2,3,4])

false

existe(4,[1,2,3,4])

true

lunes, 18 de diciembre de 2023

Contar elementos de una lista en prolog


Vamos a hacer un predicado que cuenten los elementos de una lista en prolog: 

count_elements([], 0).

count_elements([_ | Tail], Count) :-

    count_elements(Tail, TailCount),

    Count is TailCount + 1.

Contar valores que cumplan un criterio en Lisp

 


Función para contar valores de una lista en lisp según un criterio (que va a ser una función) : 

(defun contarSegun (lista fx) 

 (cond

     ((null lista) 0)

     ((funcall fx (first lista)) 

           (+ (contarSegun (rest lista) fx) 1))

     (T (contarSegun (rest lista) fx))

 )

)


Y vamos a probarla : 


> (contarSegun '(1 2 3 4 5 6) (lambda (a) (> a 3)))

3


> (contarSegun '(1 2 3 4 5 6) (lambda (a) (>= a 3)))

4


> (contarSegun '(1 2 3 4 5 6) (lambda (a) (= a 3)))

1


sábado, 16 de diciembre de 2023

Slices en Rust


Los Slices brindan una vista de una colección más grande, es como una porción:


fn main() {

    let mut a: [i32; 6] = [10, 20, 30, 40, 50, 60];

    println!("a: {a:?}");

    let s: &[i32] = &a[2..4];

    println!("s: {s:?}");

}


Los Slice toman prestados el tipo del arreglo.

Si el Slice comienza en el índice 0, la sintaxis de rango de Rust nos permite eliminar el índice inicial, lo que significa que &a[0..a.len()] y &a[..a.len()] son idénticos. Lo mismo ocurre con el último índice, por lo que &a[2..a.len()] y &a[2..] son idénticos. Por lo tanto, para crear fácilmente un  Slice de todo el vector, podemos usar &a[..].

El tipo de s (&[i32]) ya no menciona la longitud de la matriz. Esto nos permite realizar cálculos en sectores de diferentes tamaños.

miércoles, 13 de diciembre de 2023

¿Qué es Go-Zero?

 


En el mundo del desarrollo de software, la velocidad, la eficiencia y la simplicidad son pilares fundamentales. El framework Go-Zero emerge como una solución poderosa para aquellos que buscan crear aplicaciones escalables y de alto rendimiento utilizando el lenguaje de programación Go (Golang).


Pero ¿Qué es Go-Zero? Go-Zero es un framework moderno y de código abierto diseñado para acelerar el proceso de desarrollo de aplicaciones en Go. Ofrece una arquitectura robusta y flexible, proporcionando herramientas y patrones que permiten construir aplicaciones web, API y microservicios de manera eficiente.


Características Principales:

  • Alto rendimiento: Go-Zero se destaca por su capacidad para manejar cargas de trabajo intensivas y mantener un alto rendimiento incluso en entornos de gran escala.
  • Productividad mejorada: Proporciona abstracciones y utilidades que reducen la complejidad del código, permitiendo a los desarrolladores enfocarse en la lógica de negocio en lugar de detalles de implementación.
  • Facilidad de uso: Con una curva de aprendizaje amigable, su estructura modular y su amplia documentación, Go-Zero facilita a los desarrolladores tanto principiantes como experimentados.
  • Soporte para microservicios: Ofrece herramientas específicas para la construcción de arquitecturas de microservicios, simplificando la comunicación entre ellos y la gestión de datos distribuidos.
Veamos un ejemplo básico de cómo se podría crear un servidor HTTP simple utilizando el framework Go-Zero:

package main

import (
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/rest"
"github.com/tal-tech/go-zero/rest/httpx"
)

type Request struct {
Name string `form:"name"`
}

type Response struct {
Message string `json:"message"`
}

func helloHandler(w httpx.ResponseWriter, r *httpx.Request) {
var req Request
if err := r.ParseForm(&req); err != nil {
w.Error(httpx.BadRequest(err.Error()))
return
}

resp := Response{
Message: "Hello, " + req.Name + "!",
}
w.WriteJson(resp)
}

func main() {
var c rest.RestConf
conf.MustLoad("path/to/config.yaml", &c)

server := rest.MustNewServer(c)
server.AddRoute(rest.Route{
Method:  "GET",
Path:    "/hello",
Handler: helloHandler,
})
defer server.Stop()

server.Start()
}


Go-Zero representa una opción valiosa para aquellos que buscan desarrollar aplicaciones en Go de manera rápida, eficiente y escalable. Su enfoque en el rendimiento y la productividad lo convierten en una herramienta atractiva para proyectos de diversos tamaños y complejidades.

Dejo link: https://github.com/zeromicro/go-zero

sábado, 9 de diciembre de 2023

Comienza a usar la IA generativa

 Me llego el siguiente mail y queria compartirlo : 

Manejo de memoria en Rust



Los programas asignan memoria de dos maneras:

stack: Área continua de memoria para variables locales.

Los valores tienen tamaños fijos conocidos en el momento de la compilación.

Extremadamente rápido: basta con mover un puntero de pila.

Fácil de administrar: sigue llamadas a funciones.

Gran recuerdo de la localidad.


heap: almacenamiento de valores fuera de las llamadas a funciones.

Los valores tienen tamaños dinámicos determinados en tiempo de ejecución.

Ligeramente más lento que la pila: se necesita algo de contabilidad.

No hay garantía de localidad de memoria.


Veamos un ejemplo: La creación de una cadena coloca metadatos de tamaño fijo en el stack y datos de tamaño dinámico, la cadena real, en el heap:


fn main() {

    let s1 = String::from("Hello");

}




Una cadena está respaldada por un Vec, por lo que tiene capacidad y longitud y puede crecer si es mutable mediante reasignación en el heap.

Podemos inspeccionar el diseño de la memoria con Rust pero esto no es seguro.

fn main() {
    let mut s1 = String::from("Hello");
    s1.push(' ');
    s1.push_str("world");
    // DON'T DO THIS AT HOME! For educational purposes only.
    // String provides no guarantees about its layout, so this could lead to
    // undefined behavior.
    unsafe {
        let (ptr, capacity, len): (usize, usize, usize) = std::mem::transmute(s1);
        println!("ptr = {ptr:#x}, len = {len}, capacity = {capacity}");
    }
}


martes, 5 de diciembre de 2023

Slices bidimensionales en Go


Las matrices y Slices de Go son unidimensionales. Para crear el equivalente de una matriz o un Slice 2D, es necesario definir un conjunto de matrices o un Slice de Slices, como este:


type Transform [3][3]float64  // A 3x3 array, really an array of arrays.

type LinesOfText [][]byte     // A slice of byte slices.


Debido a que los Slices tienen una longitud variable, es posible que cada Slice interno tenga una longitud diferente. Esa puede ser una situación común, como en nuestro ejemplo de LinesOfText: cada línea tiene una longitud independiente.


text := LinesOfText{

    []byte("Now is the time"),

    []byte("for all good gophers"),

    []byte("to bring some fun to the party."),

}

A veces es necesario asignar un Slice 2D, una situación que puede surgir al procesar líneas de escaneo de píxeles, por ejemplo. Hay dos formas de lograrlo. Una es asignar cada porción de forma independiente; la otra es asignar una única matriz y apuntar los sectores individuales hacia ella. Cuál usar depende de su aplicación. Si los sectores pueden crecer o reducirse, deben asignarse de forma independiente para evitar sobrescribir la siguiente línea; de lo contrario, puede ser más eficiente construir el objeto con una única asignación. Como referencia, aquí hay bocetos de los dos métodos. Primero, una línea a la vez:


// Allocate the top-level slice.

picture := make([][]uint8, YSize) // One row per unit of y.

// Loop over the rows, allocating the slice for each row.

for i := range picture {

    picture[i] = make([]uint8, XSize)

}


Y ahora como una asignación, dividida en líneas:


// Allocate the top-level slice, the same as before.

picture := make([][]uint8, YSize) // One row per unit of y.

// Allocate one large slice to hold all the pixels.

pixels := make([]uint8, XSize*YSize) // Has type []uint8 even though picture is [][]uint8.

// Loop over the rows, slicing each row from the front of the remaining pixels slice.

for i := range picture {

    picture[i], pixels = pixels[:XSize], pixels[XSize:]

}

lunes, 4 de diciembre de 2023

Closures en Rust


Los Closures o expresiones lambda tienen tipos que no se pueden nombrar. Sin embargo, implementan características especiales de Fn, FnMut y FnOnce:


fn apply_with_log(func: impl FnOnce(i32) -> i32, input: i32) -> i32 {

    println!("Calling function on {input}");

    func(input)

}


fn main() {

    let add_3 = |x| x + 3;

    println!("add_3: {}", apply_with_log(add_3, 10));

    println!("add_3: {}", apply_with_log(add_3, 20));


    let mut v = Vec::new();

    let mut accumulate = |x: i32| {

        v.push(x);

        v.iter().sum::<i32>()

    };

    println!("accumulate: {}", apply_with_log(&mut accumulate, 4));

    println!("accumulate: {}", apply_with_log(&mut accumulate, 5));


    let multiply_sum = |x| x * v.into_iter().sum::<i32>();

    println!("multiply_sum: {}", apply_with_log(multiply_sum, 3));

}


Una Fn (por ejemplo, add_3) no consume ni muta los valores capturados, o tal vez no captura nada en absoluto. Se puede llamar varias veces al mismo tiempo.

Un FnMut (por ejemplo, acumular) podría mutar los valores capturados. Puedes llamarlo varias veces, pero no al mismo tiempo.

Si tiene un FnOnce (por ejemplo, multiplicar_sum), solo puede llamarlo una vez. Podría consumir valores capturados.

FnMut es un subtipo de FnOnce. Fn es un subtipo de FnMut y FnOnce. Es decir. puede usar un FnMut donde sea que se requiera un FnOnce, y puede usar un Fn donde sea que se requiera un FnMut o FnOnce.

El compilador también infiere Copy (por ejemplo, para add_3) y Clone (por ejemplo, multiply_sum), dependiendo de lo que capture el Closure.

De forma predeterminada, los Closures trabajan por referencia si pueden. La palabra clave move los hace capturar por valor.


fn make_greeter(prefix: String) -> impl Fn(&str) {

    return move |name| println!("{} {}", prefix, name)

}


fn main() {

    let hi = make_greeter("Hi".to_string());

    hi("there");

}