Translate

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");

}


sábado, 2 de diciembre de 2023

Casting en Rust


Rust no tiene conversiones de tipos implícitas, pero admite conversiones explícitas con as. 

fn main() {

    let value: i64 = 1000;

    println!("as u16: {}", value as u16);

    println!("as i16: {}", value as i16);

    println!("as u8: {}", value as u8);

}


Los resultados de as siempre están definidos en Rust y son consistentes en todas las plataformas. Es posible que esto no coincida con su intuición para cambiar el signo o transformar a un tipo más pequeño; debemos consultar la documentación.

Generalmente se desaconseja el uso de as en casos en los que se puedan perder datos, o al menos merece un comentario explicativo.

Función para filtrar valores de una lista en lisp


Vamos a hacer rápidamente una función que permita filtrar valores de una lista según una función. Veamos el código : 

(defun filtrar (lista fx) 

  (cond 

     ((null lista) lista) 

     ((funcall fx (first lista)) 

         (cons (first lista) (filtrar (rest lista) fx)))

     (T (filtrar (rest lista) fx))

  )

Si la lista esta vacía la retorna, sino se fija si ese elemento cumple el criterio y si lo cumple construye una nueva lista con este elemento y el resto filtrado. Y si no retorna el resto de la lista filtrado. 


Veamos si funciona: 


> (filtrar '(1 2 3 4 5) (lambda (a) (> a 5)))

NIL


> (filtrar '(1 2 3 4 5) (lambda (a) (> a 2)))

(3 4 5)


> (filtrar '(1 2 3 4 5) (lambda (a) (< a 2)))

(1)



viernes, 1 de diciembre de 2023

Traits From y Into de Rust


Los tipos que implementan From y Into facilitan las conversiones de tipos:

fn main() {

    let s = String::from("hello");

    let addr = std::net::Ipv4Addr::from([127, 0, 0, 1]);

    let one = i16::from(true);

    let bigger = i32::from(123i16);

    println!("{s}, {addr}, {one}, {bigger}");

}

Into se implementa automáticamente cuando se implementa From:

fn main() {

    let s: String = "hello".into();

    let addr: std::net::Ipv4Addr = [127, 0, 0, 1].into();

    let one: i16 = true.into();

    let bigger: i32 = 123i16.into();

    println!("{s}, {addr}, {one}, {bigger}");

}

Es por eso que es común implementar solo From, ya que su tipo también entrará en la implementación de Into.

Al declarar un tipo de entrada de argumento de función como "cualquier cosa que pueda convertirse en una cadena", la regla es la opuesta, debes usar Into. Su función aceptará tipos que implementen From y aquellos que solo implementen Into.


jueves, 30 de noviembre de 2023

Testear endpoints en go con echo

Supongamos que tenemos unos endpoints hechos con echo:

package handler


import (

    "net/http"

    "github.com/labstack/echo/v4"

)


type (

    User struct {

        Name  string `json:"name" form:"name"`

        Email string `json:"email" form:"email"`

    }

    handler struct {

        db map[string]*User

    }

)


func (h *handler) createUser(c echo.Context) error {

    u := new(User)

    if err := c.Bind(u); err != nil {

        return err

    }

    return c.JSON(http.StatusCreated, u)

}


func (h *handler) getUser(c echo.Context) error {

    email := c.Param("email")

    user := h.db[email]

    if user == nil {

        return echo.NewHTTPError(http.StatusNotFound, "user not found")

    }

    return c.JSON(http.StatusOK, user)

}


Lo que queremos hacer es un test de unidad que testee este comportamiento, y podemos hacerlo así: 


package handler


import (

    "net/http"

    "net/http/httptest"

    "strings"

    "testing"


    "github.com/labstack/echo/v4"

    "github.com/stretchr/testify/assert"

)


var (

    mockDB = map[string]*User{

        "jon@labstack.com": &User{"Jon Snow", "jon@labstack.com"},

    }

    userJSON = `{"name":"Jon Snow","email":"jon@labstack.com"}`

)


func TestCreateUser(t *testing.T) {

    // Setup

    e := echo.New()

    req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON))

    req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)

    rec := httptest.NewRecorder()

    c := e.NewContext(req, rec)

    h := &handler{mockDB}


    // Assertions

    if assert.NoError(t, h.createUser(c)) {

        assert.Equal(t, http.StatusCreated, rec.Code)

        assert.Equal(t, userJSON, rec.Body.String())

    }

}


func TestGetUser(t *testing.T) {

    // Setup

    e := echo.New()

    req := httptest.NewRequest(http.MethodGet, "/", nil)

    rec := httptest.NewRecorder()

    c := e.NewContext(req, rec)

    c.SetPath("/users/:email")

    c.SetParamNames("email")

    c.SetParamValues("jon@labstack.com")

    h := &handler{mockDB}


    // Assertions

    if assert.NoError(t, h.getUser(c)) {

        assert.Equal(t, http.StatusOK, rec.Code)

        assert.Equal(t, userJSON, rec.Body.String())

    }

}


Y listo!! 


Dejo link: https://echo.labstack.com/docs/testing

lunes, 27 de noviembre de 2023

Unir dos listas en lisp


Vamos a unir dos listas en Lisp. Si la primera lista es vacia, retornamos la otra lista y si no lo es, costruimos una nueva lista con el primer elemento de la primera lista y la union del resto de la primera lista (porque al primero ya lo sacamos) con la otra lista. 

(defun unir (lista1 lista2) 

  (cond 

    ((null lista1) lista2)

    (T (cons (first lista1) (unir (rest lista1) lista2)))

  )

)


Si probamos : 

> (unir '(1 2 3) '(4 5 6))

(1 2 3 4 5 6)


> (unir '(1 2 3) '(4))

(1 2 3 4)


Sobrecarga de operadores por medio de traits en Rust


 La sobrecarga del operador se implementa mediante rasgos en std::ops:

#[derive(Debug, Copy, Clone)]

struct Point { x: i32, y: i32 }


impl std::ops::Add for Point {

    type Output = Self;


    fn add(self, other: Self) -> Self {

        Self {x: self.x + other.x, y: self.y + other.y}

    }

}


fn main() {

    let p1 = Point { x: 10, y: 20 };

    let p2 = Point { x: 100, y: 200 };

    println!("{:?} + {:?} = {:?}", p1, p2, p1 + p2);

}

Si lo ejecutamos : 

cargo run main.rs

   Compiling hello_cargo v0.1.0 

    Finished dev [unoptimized + debuginfo] target(s) in 0.58s

     Running `target/debug/hello_cargo main.rs`

Point { x: 10, y: 20 } + Point { x: 100, y: 200 } = Point { x: 110, y: 220 }


Podrías implementar Add para &Point. Si el tipo T para el cual está sobrecargando el operador no implementa Copy, debería considerar sobrecargar también el operador para &T. Esto evita clonaciones innecesarias.

Se podría implementar Add para dos tipos diferentes, p. impl Add<(i32, i32)> for Point agregaría una tupla a un Point.

Sería así :


impl std::ops::Add<(i32, i32)>  for Point {

    type Output = Self;


    fn add(self, other: (i32, i32)) -> Self {

        Self {x: self.x + other.0, y: self.y + other.1}

    }

}


fn main() {

    let p1 = Point { x: 10, y: 20 };

    let tuple= (100, 200);

    println!("{:?} + {:?} = {:?}", p1, tuple, p1 + tuple);

}


Y la salida va a ser : 

Point { x: 10, y: 20 } + (100, 200) = Point { x: 110, y: 220 }


sábado, 25 de noviembre de 2023

Buscando el mayor y el menor en lisp


Ahora lo que vamos a hacer es buscar el menor o el mayor de una lista. El algoritmo es similar, por lo tanto vamos a utilizar una función general que busque según una función y luego escribimos el mayor o menor pasando un lambda que busque eso (con eso me refiero al menor o al mayor) : 


(defun buscar (lista fx)

  (cond

    ((null (rest lista)) (first lista))

    ((funcall fx (first lista) (buscar (rest lista) fx)) (first lista))

    (T (buscar (rest lista) fx))

  )

)


(defun menor (lista)

  (buscar lista (lambda (a b) (< a b)))

)


(defun mayor (lista)

  (buscar lista (lambda (a b) (> a b)))

)

El algoritmo buscar lo que hace es si la lista tiene un solo elemento, ya esta ese es el menor o el mayor. Si no compara el primero con el buscar del resto, por ejemplo para el menor, compara el primero con el menor del resto, si es verdadero ese es el menor y si no el menor es el menor del resto. 

Y listo! 

Comenten si quieren más algoritmos así. 

miércoles, 22 de noviembre de 2023

Quicksort in lisp


Un Algoritmo que me gusta mucho es el quicksort, porque es un algoritmo por demás claro. Ya he escrito lo fácil que es implementarlo en haskell 

Ahora le toca a lisp. Básicamente el algoritmo toma un pivot y agrupa los menores del pivot al principio y los mayores al final y aplica quicksort a estos 2 grupos. Y si la lista es vacia, ya esta ordenada. 

Vamos al código: 


(defun qso (l) 

   (cond 

     ((null l) l)

     (T (append 

         (qso (remove-if (lambda (a) (> a (first l))) (rest l)))

         (cons 

           (first l)

           (qso (remove-if (lambda (a) (<= a (first l))) (rest l)))

         )

        )

     )

   )

)


El Trait Default

 


El Trait Default produce un valor predeterminado para un tipo.

#[derive(Debug, Default)]

struct Derived {

    x: u32,

    y: String,

    z: Implemented,

}


#[derive(Debug)]

struct Implemented(String);


impl Default for Implemented {

    fn default() -> Self {

        Self("John Smith".into())

    }

}


fn main() {

    let default_struct = Derived::default();

    println!("{default_struct:#?}");


    let almost_default_struct = Derived {

        y: "Y is set!".into(),

        ..Derived::default()

    };

    println!("{almost_default_struct:#?}");


    let nothing: Option<Derived> = None;

    println!("{:#?}", nothing.unwrap_or_default());

}

  • Se puede implementar directamente o se puede derivar mediante #[derive(Default)].
  • Una implementación derive producirá un valor en el que todos los campos se establecerán en sus valores por defecto.
  • Esto significa que todos los tipos de la estructura también deben implementar Default.
  • Los tipos estándar de Rust a menudo implementan el valor predeterminado con valores razonables (por ejemplo, 0, "", etc.).
  • La copia parcial de la estructura funciona bien de forma predeterminada.
  • La biblioteca estándar de Rust es consciente de que los tipos pueden implementar el valor predeterminado y proporciona métodos convenientes para usarlo.
  • la .. sintaxis se llama sintaxis de actualización de estructura

sábado, 18 de noviembre de 2023

El Trait Drop en Rust


Los valores que implementan Drop pueden especificar el código que se ejecutará cuando salgan del alcance:

struct Droppable {

    name: &'static str,

}


impl Drop for Droppable {

    fn drop(&mut self) {

        println!("Dropping {}", self.name);

    }

}


fn main() {

    let a = Droppable { name: "a" };

    {

        let b = Droppable { name: "b" };

        {

            let c = Droppable { name: "c" };

            let d = Droppable { name: "d" };

            println!("Exiting block B");

        }

        println!("Exiting block A");

    }

    drop(a);

    println!("Exiting main");

}


Si ejecutamos esto tenemos: 

Exiting block B

Dropping d

Dropping c

Exiting block A

Dropping b

Dropping a

Exiting main


  • Tenga en cuenta que std::mem::drop no es lo mismo que std::ops::Drop::drop.
  • Los valores se eliminan automáticamente cuando salen del alcance.
  • Cuando se elimina un valor, si implementa std::ops::Drop, se llamará a su implementación Drop::drop.
  • Todos sus campos también se eliminarán, ya sea que implemente Drop o no.

std::mem::drop es solo una función vacía que toma cualquier valor. La importancia es que se apropia del valor, por lo que al final de su alcance se elimina. Esto lo convierte en una forma conveniente de eliminar valores explícitamente antes de que, de otro modo, saldrían del alcance.

Esto puede ser útil para objetos que realizan algún trabajo al soltarlos: liberar bloqueos, cerrar archivos, etc.

lunes, 13 de noviembre de 2023

La función reduce en lisp


Vamos a hacer una función reduce o reducir en lisp. La función reduce nos permite acumular una lista de números por ejemplo o concatenar una lista de string. 

Es decir toma un valor inicial y va acumulando los valores que tiene una lista con una función que se pasa por parámetros. Veamos esto en lisp : 


(defun reducir(inicial lista fx) 

    (cond 

        ((Null lista) inicial)

        (T (reducir (Funcall fx inicial (first lista)) (rest lista) fx))

    )


Si la lista esta vacía, retornamos el acumulador. Si no volvemos a llamar a la función con el valor de la acumulación del primer elemento como valor inicial y el resto del la lista. 


Y podemos llamarlo de la siguiente manera: 

 > (reducir 0 '(1 2 3 4 5) (LAMBDA (a b) (+ a b))) 

15

> (reducir "" '("hola " "Mundo") (LAMBDA (a b) (concatenate 'string a b)))

"hola Mundo"

 > (reducir "" '("uno" "dos" "tres" "super tranquilo") (LAMBDA (a b) (concatenate 'string a " " b)))

" uno dos tres super tranquilo"

Read y Write en Rust


Usando Read y BufRead, nos podemos abstraer a un vector de u8

use std::io::{BufRead, BufReader, Read, Result};


fn count_lines<R: Read>(reader: R) -> usize {

    let buf_reader = BufReader::new(reader);

    buf_reader.lines().count()

}


fn main() -> Result<()> {

    let slice: &[u8] = b"foo\nbar\nbaz\n";

    println!("lines in slice: {}", count_lines(slice));


    let file = std::fs::File::open(std::env::current_exe()?)?;

    println!("lines in file: {}", count_lines(file));

    Ok(())

}


De manera similar, Write nos permite abstraernos de igual forma:


use std::io::{Result, Write};


fn log<W: Write>(writer: &mut W, msg: &str) -> Result<()> {

    writer.write_all(msg.as_bytes())?;

    writer.write_all("\n".as_bytes())

}


fn main() -> Result<()> {

    let mut buffer = Vec::new();

    log(&mut buffer, "Hello")?;

    log(&mut buffer, "World")?;

    println!("Logged: {:?}", buffer);

    Ok(())

}

Slices en golang


Los Slices envuelven matrices para brindar una interfaz más general, poderosa y conveniente para secuencias de datos. Excepto por elementos con dimensiones explícitas, como matrices de transformación, la mayor parte de la programación de matrices en Go se realiza con Slices en lugar de simples arrays.

Los Slices contienen referencias a una matriz subyacente y, si asigna un Slice a otro, ambos se refieren a la misma matriz. Si una función toma un argumento de tipo Slice, los cambios que realice en los elementos del segmento serán visibles para quien llama, de forma análoga a pasar un puntero a la matriz subyacente. Por lo tanto, una función de lectura puede aceptar un argumento de tipo Slice en lugar de un puntero y una dimensión; la longitud dentro del segmento establece un límite superior de la cantidad de datos que se leerán. Aquí está la firma del método de lectura del tipo de archivo en el paquete os:


func (f *File) Read(buf []byte) (n int, err error)


El método devuelve el número de bytes leídos y un valor de error, si lo hubiera. Para leer los primeros 32 bytes hacemos: 


  n, err := f.Read(buf[0:32])


Este Slice es común y eficiente. De hecho, dejando de lado la eficiencia por el momento, el siguiente fragmento también leería los primeros 32 bytes del búfer.


    var n int

    var err error

    for i := 0; i < 32; i++ {

        nbytes, e := f.Read(buf[i:i+1])  // Read one byte.

        n += nbytes

        if nbytes == 0 || e != nil {

            err = e

            break

        }

    }


La longitud de un segmento se puede cambiar siempre que todavía se ajuste dentro de los límites de la matriz subyacente; simplemente asígnalo a una Slice de sí mismo. La capacidad de un Slice es accesible mediante una función incorporada, informa la longitud máxima que puede asumir el segmento. Aquí hay una función para agregar datos a un slice. Si los datos exceden la capacidad, se reasigna el slice y se devuelve el slice resultante. La función utiliza el hecho de que len y cap son legales cuando se aplican a segmentos nulos y devuelven 0.


func Append(slice, data []byte) []byte {

    l := len(slice)

    if l + len(data) > cap(slice) {  // reallocate

        // Allocate double what's needed, for future growth.

        newSlice := make([]byte, (l+len(data))*2)

        // The copy function is predeclared and works for any slice type.

        copy(newSlice, slice)

        slice = newSlice

    }

    slice = slice[0:l+len(data)]

    copy(slice[l:], data)

    return slice

}


Debemos devolver el slice después porque, aunque Append puede modificar los elementos del slice, el slice en sí (la estructura de datos en tiempo de ejecución que contiene el puntero, la longitud y la capacidad) se pasa por valor.

La idea de agregar un elemento a un slice es tan util que tenemos una función para hacerlo:  append. Sin embargo, para comprender el diseño de esa función necesitamos un poco más de información, por lo que volveremos a ello más adelante.

viernes, 10 de noviembre de 2023

Función map en lisp


Vamos a hacer una función transformar o map en lisp que lo que haga es tome una lista y una función y aplique esa función a cada elemento de la lista. 

Para esto vamos a analizar los casos si la lista esta vacía, ya esta retornamos la lista vacía. Si no esta vacía, creamos una nueva lista aplicando esa función al primer elemento y llamando de forma recursiva la función transformar para el resto. 

Sería así : 

(defun transformar (l fx)

   (cond 

      ((Null l) Nil)

      (T (cons (Funcall fx (first l)) 

           (transformar (rest l) fx)))

    )


Y la podemos llamar de esta manera : 

> (transformar '(1 2 3) (LAMBDA (a) (* a 2)))

(2 4 6)

o

 > (transformar '(1 2 3) (LAMBDA (a) (+ a 1)))

(2 3 4)



miércoles, 8 de noviembre de 2023

From y Into de Rust


 Los tipos implementan From y Into facilitan las conversiones de tipos:

fn main() {

    let s = String::from("hello");

    let addr = std::net::Ipv4Addr::from([127, 0, 0, 1]);

    let one = i16::from(true);

    let bigger = i32::from(123i16);

    println!("{s}, {addr}, {one}, {bigger}");

}

Into se implementa automáticamente cuando se implementa From:

fn main() {

    let s: String = "hello".into();

    let addr: std::net::Ipv4Addr = [127, 0, 0, 1].into();

    let one: i16 = true.into();

    let bigger: i32 = 123i16.into();

    println!("{s}, {addr}, {one}, {bigger}");

}

Es por eso que es común implementar solo From.

Al declarar un tipo de entrada de argumento de función como "cualquier cosa que pueda convertirse en una cadena", la regla es la opuesta, debes usar Into. Su función aceptará tipos que implementen From y aquellos que solo implementen Into.


sábado, 4 de noviembre de 2023

FromIterator de Rust

 


FromIterator te permite crear una colección a partir de un Iterador.

fn main() {

    let primes = vec![2, 3, 5, 7];

    let prime_squares = primes

        .into_iter()

        .map(|prime| prime * prime)

        .collect::<Vec<_>>();

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

}

Iterator implementa fn collect<B>(self) -> B where B: FromIterator<Self::Item>, Self: Sized


También hay implementaciones que te permiten hacer cosas interesantes como convertir un Iterador<Item = Result<V, E>> en un Result<Vec<V>, E>.

viernes, 3 de noviembre de 2023

Asignación de memoria con make en Go


La función make(T, args) tiene un propósito diferente al de new(T). Crea únicamente slices, maps, y channels y devuelve un valor inicializado (no puesto a cero) de tipo T (no *T). El motivo de la distinción es que estos tres tipos representan, encubiertamente, referencias a estructuras de datos que deben inicializarse antes de su uso. Un slice, por ejemplo, es un descriptor de tres elementos que contiene un puntero a los datos (dentro de un arreglo o vector), la longitud y la capacidad, y hasta que esos elementos se inicialicen, el slice es nulo. Para slices, map y chanels, make inicializa la estructura de datos interna y prepara el valor para su uso. Por ejemplo,


make([]int, 10, 100)


asigna una arreglo de entero de 100 elementos y luego crea una estructura slice con una longitud de 10 y una capacidad de 100 que apunta a los primeros 10 elementos de la matriz. (Al crear un slice, se puede omitir la capacidad). Por el contrario, new([]int) devuelve un puntero a una estructura de slice puesta a cero recién asignada, es decir, un puntero a un slice nulo.

Estos ejemplos ilustran la diferencia entre nuevo y fabricado.


var p *[]int = new([]int)       // allocates slice structure; *p == nil; rarely useful

var v  []int = make([]int, 100) // the slice v now refers to a new array of 100 ints


// Unnecessarily complex:

var p *[]int = new([]int)

*p = make([]int, 100, 100)


// Idiomatic:

v := make([]int, 100)


Recuerde que make se aplica solo a maps, slices y channels y no devuelve un puntero. Para obtener un puntero explícito, asigne nuevo o tome la dirección de una variable explícitamente.

Iterators en Rust


Puede implementar el trait Iterador en nuestros tipos :

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}");

    }

}


El trait Iterador implementa muchas operaciones de programación funcional comunes sobre colecciones (por ejemplo, map, filter, reduce, etc). En Rust, estas funciones deberían producir un código tan eficiente como las implementaciones imperativas equivalentes.

IntoIterator es el trait que hace que los bucles funcionen. Se implementa mediante tipos de colección como Vec<T> y referencias a ellos como &Vec<T> y &[T]. Ranges también lo implementan. Es por eso que puedes iterar sobre un vector con for i in some_vec { .. } pero some_vec.next() no existe.


miércoles, 1 de noviembre de 2023

Test en Go


Go ofrece soporte integrado para pruebas unitarias que hace que sea más fácil realizar pruebas sobre la marcha. Específicamente, utilizando las convenciones de nomenclatura, el paquete de pruebas de Go y el comando go test, puede escribir y ejecutar pruebas rápidamente.

Veamos un ejemplo, vamos a hacer un archivo greetings con el siguiente contenido : 

package greetings


import "fmt"


// Hello returns a greeting for the named person.

func Hello(name string) (message string) {

// Return a greeting that embeds the name in a message.

if name == "" {

message = "Hi!"

} else {

message = fmt.Sprintf("Hi, %v. Welcome!", name)

}

return message

}


En el directorio greetings, crearemos un archivo llamado greetings_test.go, que son las pruebas. Terminar el nombre de un archivo con _test.go le indica al comando go test que este archivo contiene test.

En Greetings_test.go, vamos a pegar el siguiente código y guardar el archivo.

package greetings


import (

"regexp"

"testing"

)


// TestHelloName calls greetings.Hello with a name, checking

// for a valid return value.

func TestHelloName(t *testing.T) {

name := "Gladys"

want := regexp.MustCompile(`\b` + name + `\b`)

msg := Hello("Gladys")

if !want.MatchString(msg) {

t.Fatalf(`Hello("Gladys") = %q, %v`, msg, want)

}

}


// TestHelloEmpty calls greetings.Hello with an empty string.

func TestHelloEmpty(t *testing.T) {

msg := Hello("")

if msg != "Hi!" {

t.Fatalf(`Hello("") = %q, want "Hi!"`, msg)

}

}


En este código, se puede ver que utilizamos el paquete "test" para hacer uso de función "Fatalf"  que indica que la función no pasa el test. 

Si ejecutamos go test en el directorio obtendremos : 

$ go test

PASS

ok      example.com/greetings   0.364s


$ go test -v

=== RUN   TestHelloName

--- PASS: TestHelloName (0.00s)

=== RUN   TestHelloEmpty

--- PASS: TestHelloEmpty (0.00s)

PASS

ok      example.com/greetings   0.372s


Y eso es todo! 

Dejo link : https://go.dev/doc/tutorial/add-a-test




Arrays en Go


Los arrays son útiles al planificar el diseño detallado de la memoria y, a veces, pueden ayudar a evitar la asignación, pero principalmente son un bloque de construcción para los slice.

Existen grandes diferencias entre la forma en que funcionan los arrys en Go y C. En Go,

  • Los arrys son valores. Asignar un array a otra copia todos los elementos.
  • En particular, si pasa una matriz a una función, recibirá una copia de la matriz, no un puntero a ella.
  • El tamaño de una matriz es parte de su tipo. Los tipos [10]int y [20]int son distintos.

Estas propiedades puede ser útiles pero también costosa; Si desea un comportamiento y eficiencia similares a los de C, puede pasar un puntero a la matriz.


func Sum(a *[3]float64) (sum float64) {

    for _, v := range *a {

        sum += v

    }

    return

}


array := [...]float64{7.0, 8.5, 9.1}

x := Sum(&array)  // Note the explicit address-of operator


Pero este código no sigue el estilo Go, podemos hacer algo similar con Slice que veremos más adelante. 

martes, 31 de octubre de 2023

Constructores y literales compuestos


A veces el valor cero no es suficiente y es necesario un constructor de inicialización, como en este ejemplo derivado del paquete os.


func NewFile(fd int, name string) *File {

    if fd < 0 {

        return nil

    }

    f := new(File)

    f.fd = fd

    f.name = name

    f.dirinfo = nil

    f.nepipe = 0

    return f

}


Hay mucho texto en este codigo que podemos simplificarlo usando un literal compuesto, que es una expresión que crea una nueva instancia:


func NewFile(fd int, name string) *File {

    if fd < 0 {

        return nil

    }

    f := File{fd, name, nil, 0}

    return &f

}


Tenemos que tener en cuenta que, a diferencia de C, está perfectamente bien devolver la dirección de una variable local; el almacenamiento asociado con la variable sobrevive después de que regresa la función. De hecho, al tomar la dirección de un literal compuesto se asigna una instancia nueva cada vez que se evalúa, por lo que podemos combinar estas dos últimas líneas: 


return &File{fd, name, nil, 0}


Los campos de un literal compuesto están ordenados y todos deben estar presentes. Sin embargo, al etiquetar los elementos explícitamente como pares campo:valor, los inicializadores pueden aparecer en cualquier orden, dejando los que faltan con sus respectivos valores cero. Así podríamos decir


return &File{fd: fd, name: name}


Como caso límite, si un literal compuesto no contiene ningún campo, crea un valor cero para el tipo. Las expresiones new(File) y &File{} son equivalentes.

También se pueden crear literales compuestos para matrices, slices y mapas, siendo las etiquetas de campo índices o claves de mapa, según corresponda. En estos ejemplos, las inicializaciones funcionan independientemente de los valores de Enone, Eio y Einval, siempre que sean distintos.


a := [...]string   {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}

s := []string      {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}

m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"}




lunes, 23 de octubre de 2023

Asignación con new en Go


Go tiene dos primitivas de asignación de memoria, las funciones integradas new y make. Hacen cosas diferentes y se aplican a diferentes tipos, lo que puede resultar confuso, pero las reglas son simples. Hablemos primero de new. Es una función incorporada que asigna memoria, pero a diferencia de sus homónimos en otros lenguajes, no inicializa la memoria, solo la pone a cero. Es decir, new(T) asigna almacenamiento puesto a cero para un nuevo elemento de tipo T y devuelve su dirección, un valor de tipo *T. En la terminología de Go, devuelve un puntero a un valor cero recién asignado de tipo T.

Dado que la memoria devuelta por new se pone a cero, es útil disponer al diseñar las estructuras de datos que el valor cero de cada tipo se pueda usar sin inicialización adicional. Esto significa que un usuario de la estructura de datos puede crear una nueva y ponerse manos a la obra. Por ejemplo, la documentación de bytes.Buffer indica que "el valor cero de Buffer es un búfer vacío listo para usar". De manera similar, sync.Mutex no tiene un constructor explícito ni un método Init. En cambio, el valor cero para sync.Mutex se define como un mutex desbloqueado.

La propiedad del valor cero es útil funciona de forma transitiva. Considere este tipo de declaración.


type SyncedBuffer struct {

    lock    sync.Mutex

    buffer  bytes.Buffer

}


Los valores de tipo SyncedBuffer también están listos para usar inmediatamente después de la asignación o simplemente de la declaración. En el siguiente fragmento, tanto p como v funcionarán correctamente sin más arreglos.


p := new(SyncedBuffer)  // type *SyncedBuffer

var v SyncedBuffer      // type  SyncedBuffer


domingo, 22 de octubre de 2023

impl Trait en rust


De manera similar a trait bounds, se puede usar una sintaxis de trait implícita en argumentos de funciones y valores de retorno:

use std::fmt::Display;

fn get_x(name: impl Display) -> impl Display {

    format!("Hello {name}")

}


fn main() {

    let x = get_x("foo");

    println!("{x}");

}


El significado de impl Trait es un poco diferente en las diferentes posiciones.

Para un parámetro, impl Trait es como un parámetro genérico anónimo con un trait vinculado.

Para un tipo de retorno, significa que el tipo de retorno es algún tipo concreto que implementa el trait, sin nombrar el tipo. Esto puede resultar útil cuando no desea exponer el tipo concreto en una API pública.

La inferencia es difícil en la posición de retorno. Una función que devuelve impl Foo elige el tipo concreto que devuelve, sin escribirlo en el código fuente. Una función que devuelve un tipo genérico como recopilar<B>() -> B puede devolver cualquier tipo que satisfaga B, y es posible que la persona que llama deba elegir uno, como con let x: Vec<_> = foo.collect() o  foo.collect::<Vec<_>>().

Este ejemplo es fantástico porque utiliza impl Display dos veces. Es útil explicar que nada aquí exige que sea el mismo tipo de visualización implícito. Si usáramos un especificación de tipo T:, se impondría la restricción de que el tipo de entrada T y el de retorno T sean del mismo tipo. ¡No funcionaría para esta función en particular, ya que el tipo que esperamos como entrada probablemente no sea del mismo tipo  que format!. Si quisiéramos hacer lo mismo mediante la especificación del tipo, necesitaríamos dos parámetros de tipo genéricos independientes.

viernes, 20 de octubre de 2023

Trait Bounds en Rust


Cuando se trabaja con genéricos, a menudo se puede solicitar que los tipos implementen algún Trait, de modo que pueda llamar a los métodos de este Trait.

Puedes hacer esto con T: Trait o impl Trait:


fn duplicate<T: Clone>(a: T) -> (T, T) {

    (a.clone(), a.clone())

}


// Syntactic sugar for:

//   fn add_42_millions<T: Into<i32>>(x: T) -> i32 {

fn add_42_millions(x: impl Into<i32>) -> i32 {

    x.into() + 42_000_000

}


// struct NotClonable;

fn main() {

    let foo = String::from("foo");

    let pair = duplicate(foo);

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


    let many = add_42_millions(42_i8);

    println!("{many}");

    let many_more = add_42_millions(10_000_000);

    println!("{many_more}");

}


Se puede tambien usar la sentencia where


fn duplicate<T>(a: T) -> (T, T)

where

    T: Clone,

{

    (a.clone(), a.clone())

}


miércoles, 18 de octubre de 2023

Defer en Go


La declaración de Defer de Go programa una llamada a una función para que se ejecute inmediatamente antes de que regrese el flujo de control. Es una forma inusual pero efectiva de lidiar con situaciones como recursos que deben liberarse independientemente del camino que tome una función para regresar. Los ejemplos canónicos son desbloquear un mutex o cerrar un archivo.


// Contents returns the file's contents as a string.

func Contents(filename string) (string, error) {

    f, err := os.Open(filename)

    if err != nil {

        return "", err

    }

    defer f.Close()  // f.Close will run when we're finished.


    var result []byte

    buf := make([]byte, 100)

    for {

        n, err := f.Read(buf[0:])

        result = append(result, buf[0:n]...) // append is discussed later.

        if err != nil {

            if err == io.EOF {

                break

            }

            return "", err  // f will be closed if we return here.

        }

    }

    return string(result), nil // f will be closed if we return here.

}

Diferir una llamada a una función como Close tiene dos ventajas. Primero, garantiza que nunca olvidará cerrar el archivo, un error que es fácil de cometer si luego edita la función para agregar una nueva ruta de retorno. En segundo lugar, significa que el cierre se sitúa cerca de la apertura, lo cual es mucho más claro que colocarlo al final de la función.

Los argumentos de la función diferida (que incluyen al receptor si la función es un método) se evalúan cuando se ejecuta el diferimiento, no cuando se ejecuta la llamada. Además de evitar preocupaciones acerca de que las variables cambien los valores a medida que se ejecuta la función, esto significa que un único sitio de llamada diferida puede diferir múltiples ejecuciones de funciones. He aquí un ejemplo:


for i := 0; i < 5; i++ {

    defer fmt.Printf("%d ", i)

}


Las funciones diferidas se ejecutan en orden LIFO, por lo que este código hará que se imprima 4 3 2 1 0 cuando la función regrese. Un ejemplo más plausible es una forma sencilla de rastrear la ejecución de funciones a través del programa. Podríamos escribir un par de rutinas de rastreo simples como esta:


func trace(s string)   { fmt.Println("entering:", s) }

func untrace(s string) { fmt.Println("leaving:", s) }


// Use them like this:

func a() {

    trace("a")

    defer untrace("a")

    // do something....

}

Podemos hacerlo mejor aprovechando el hecho de que los argumentos de las funciones diferidas se evalúan cuando se ejecuta el diferimiento. La rutina de rastreo puede configurar el argumento para la rutina de desrastreo. Este ejemplo:


func trace(s string) string {

    fmt.Println("entering:", s)

    return s

}


func un(s string) {

    fmt.Println("leaving:", s)

}


func a() {

    defer un(trace("a"))

    fmt.Println("in a")

}


func b() {

    defer un(trace("b"))

    fmt.Println("in b")

    a()

}


func main() {

    b()

}


Esto imprime: 


entering: b

in b

entering: a

in a

leaving: a

leaving: b


Para programadores acostumbrados a la gestión de recursos a nivel de bloques de otros lenguajes, defer puede parecer peculiar, pero sus aplicaciones más interesantes y potentes provienen precisamente de que no está basado en bloques sino en funciones.