Translate

lunes, 25 de septiembre de 2023

gofmt de Go


Las cuestiones de formato son las más polémicas pero las menos trascendentes. Las personas pueden adaptarse a diferentes estilos de formato, pero es mejor si no es necesario y se dedica menos tiempo al tema si todos siguen el mismo estilo. El problema es cómo abordar esta utopía sin una larga guía de estilo prescriptiva.

Con Go adoptaron un enfoque inusual y dejaron que la máquina se encargue de la mayoría de los problemas de formato. El programa gofmt (también disponible como go fmt, que opera a nivel de paquete en lugar de a nivel de archivo fuente) lee un programa Go y emite el código fuente en un estilo estándar de sangría y alineación vertical, reteniendo y, si es necesario, reformateando los comentarios. Si desea saber cómo manejar alguna situación de diseño nueva, ejecute gofmt; Si la respuesta no parece correcta, reorganice su programa (o registre un error sobre gofmt), no lo solucione.

Por ejemplo, no es necesario perder tiempo alineando los comentarios en los campos de una estructura. Gofmt lo hará. Dada la declaración


type T struct {

    name string // name of the object

    value int // its value

}


gofmt alineará las columnas:


type T struct {

    name    string // name of the object

    value   int    // its value

}


Todo el código Go en los paquetes estándar ha sido formateado con gofmt.


Quedan algunos detalles de formato:


Sangría: Se usa tabs para la sangría y gofmt las emite de forma predeterminada. Utilice espacios sólo si es necesario.

Longitud de la línea: Go no tiene límite de longitud de línea. Si una línea parece demasiado larga, envuélvala y sangra con una tab adicional.

Paréntesis: Go necesita menos paréntesis que C y Java: las estructuras de control (if, for, switch) no tienen paréntesis en su sintaxis. Además, la jerarquía de precedencia de operadores es más corta y clara, por lo que

x<<8 + y<<16

significa lo que implica el espacio, a diferencia de los otros lenguajes.

Juego de Serie en Clojure


Cuando quiero aprender un nuevo lenguaje desarrollo un juego de series, es decir aparece una serie con un valor faltante y el jugador debe completarlo.

Uno de los requerimientos no funcionales es que se pueda agregar una serie nueva fácilmente.

Vamos a desarrollar este juego en Clojure:

Empecemos desarrollo de la serie, la serie tiene como responsabilidad generarse y si lo hacemos de esta manera podemos utilizar la potencia del polimorfismo, para que no quede acoplado la generación de la serie con el desarrollo del juego:

(ns secuencia-clojure.core

  (:gen-class))


(import '(java.util Scanner))

(def scan (Scanner. *in*))


   

(defn generateSecEven []

  (def seed (rand-int 30))

  (for [x (range seed (+ seed 4))] (* x 2))

)

   

(defn generateSecOdd []

  (def seed (rand-int 30))

  (for [x (range seed (+ seed 4))] (+ (* x 2) 1))

)


(defn generateSecFibo []

  (def seed (rand-int 30))

  (def seed2 (* seed 2))

  (def seed3 (* seed 3))

  (list seed seed seed2 seed3)

)

Y listo!! ahora en el main tenemos que llamar a esta función y ver si esta bien el resultado: 



(defn generateSec []
  (def seed (rand-int 3))
  (cond 
    (= seed 0) (generateSecEven)
    (= seed 1) (generateSecOdd)
    (= seed 2) (generateSecFibo)   
  )
)

(defn printSec [sec]
  (print (nth sec 0))
  (print " ")
  (print (nth sec 1))
  (print " ")
  (print " __ ")
  (print " ")
  (println (nth sec 3))
)

(defn game
  [points]
  (def sec (generateSec))
  (def sol (nth sec 2))
  
  (printSec sec)
  
  (let [data (read-line)]
  (if (= data (str sol))
    (do 
      (println "Correct!! the point is " (str (+ points 1)))
      (game (+ points 1))
    )
    (do 
      (println "Wrong!! the point is " (str (- points 1)))
      (game (- points 1))
    )))
)


(defn -main
  [& args]
  (game 0))

Y eso es todo a jugar se a dicho!!

Dejo el repositorio git: 

viernes, 22 de septiembre de 2023

Módulos en Rust


Hemos visto cómo los bloques impl nos permiten asignar funciones de espacio de nombres a un tipo.

De manera similar, mod nos permite tipos de espacios de nombres y funciones:

mod foo {

    pub fn do_something() {

        println!("In the foo module");

    }

}


mod bar {

    pub fn do_something() {

        println!("In the bar module");

    }

}


fn main() {

    foo::do_something();

    bar::do_something();

}

Los paquetes brindan funcionalidad e incluyen un archivo Cargo.toml que describe cómo crear un paquete de más de 1 caja.

Las cajas son un árbol de módulos, donde una caja binaria crea un ejecutable y una caja de biblioteca se compila en una biblioteca.

Los módulos definen la organización y alcance.


martes, 19 de septiembre de 2023

Cell y RefCell en Rust


Cell y RefCell implementan lo que Rust llama mutabilidad interior: mutación de valores en un contexto inmutable.

La celda se usa normalmente para tipos simples, ya que requiere copiar o mover valores. Los tipos de interiores más complejos suelen utilizar RefCell, que rastrea referencias compartidas y exclusivas en tiempo de ejecución y entra en pánico si se utilizan incorrectamente.


use std::cell::RefCell;

use std::rc::Rc;


#[derive(Debug, Default)]

struct Node {

    value: i64,

    children: Vec<Rc<RefCell<Node>>>,

}


impl Node {

    fn new(value: i64) -> Rc<RefCell<Node>> {

        Rc::new(RefCell::new(Node { value, ..Node::default() }))

    }


    fn sum(&self) -> i64 {

        self.value + self.children.iter().map(|c| c.borrow().sum()).sum::<i64>()

    }

}


fn main() {

    let root = Node::new(1);

    root.borrow_mut().children.push(Node::new(5));

    let subtree = Node::new(10);

    subtree.borrow_mut().children.push(Node::new(11));

    subtree.borrow_mut().children.push(Node::new(12));

    root.borrow_mut().children.push(subtree);


    println!("graph: {root:#?}");

    println!("graph sum: {}", root.borrow().sum());

}


Si estuviéramos usando Cell en lugar de RefCell en este ejemplo, tendríamos que sacar el Nodo de Rc para empujar a los elementos secundarios y luego volver a colocarlo. Esto es seguro porque siempre hay un valor sin referencia en la celda, pero no es ergonómico.

Para hacer cualquier cosa con un Nodo, debe llamar a un método RefCell, generalmente borrow o borrow_mut.

sábado, 16 de septiembre de 2023

Rc en Rust


Rc es un puntero compartido, Rc es de contado por referencias, es decir que va contando las referencias. Se usa cuando se necesita hacer referencia a los mismos datos de varios lugares:

use std::rc::Rc;


fn main() {

    let mut a = Rc::new(10);

    let mut b = Rc::clone(&a);


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

    println!("b: {b}");

}


Rc garantiza que el valor contenido sea válido mientras haya referencias.

Rc en Rust es como std::shared_ptr en C++.

Rc::clone es económico: crea un puntero a la misma asignación y aumenta el recuento de referencias. No realiza una clonación profunda y generalmente se puede ignorar cuando se buscan problemas de rendimiento en el código.

make_mut en realidad clona el valor interno si es necesario (“clonar en escritura”) y devuelve una referencia mutable.

Utilice Rc::strong_count para comprobar el recuento de referencia.

Rc::downgrade le brinda un objeto con recuento débil de referencias para crear ciclos que se eliminarán correctamente (probablemente en combinación con RefCell, en la siguiente post).

viernes, 15 de septiembre de 2023

Y es Erlang tan bueno?


Actualmente, Erlang está ganando mucha popularidad debido a conversaciones entusiastas que pueden llevar a la gente a creer que es más de lo que realmente es. El primer caso de esto está relacionado con las enormes capacidades de escalamiento de Erlang debido a sus procesos livianos. Es cierto que los procesos de Erlang son muy ligeros: puedes tener cientos de miles de ellos existentes al mismo tiempo, pero esto no significa que tengas que usarlos de esa manera sólo porque puedas. Por ejemplo, crear un juego de disparos en el que todo, incluidas las balas, sea su propio actor, es una locura. Lo único que dispararás en un juego como este será tu propio pie. Todavía hay un pequeño costo al enviar un mensaje de actor a actor, y si divides demasiado las tareas, ¡harás las cosas más lentas!

También se dice que Erlang puede escalar de manera directamente proporcional a la cantidad de núcleos que tiene su computadora, pero esto generalmente no es cierto. Es posible, pero la mayoría de los problemas no se comportan. de una manera que te permita ejecutar todo al mismo tiempo.

Hay algo más a tener en cuenta: si bien Erlang hace algunas cosas muy bien, técnicamente todavía es posible obtener los mismos resultados en otros lenguajes. Lo opuesto también es cierto; evaluar cada problema como debe ser y elegir la herramienta adecuada según el problema que se aborda. Erlang no es una solución mágica y será particularmente malo en cosas como procesamiento de imágenes y señales, controladores de dispositivos de sistemas operativos, etc. y brillará en cosas como software grande para uso de servidor (es decir, colas, reducción de mapas), haciendo algo de levantamiento junto con otros lenguajes, implementación de protocolos de nivel superior, etc. Las áreas intermedias dependerán de usted. No necesariamente debes encerrarte en un software de servidor con Erlang: ha habido casos de personas que hacen cosas inesperadas y sorprendentes. Un ejemplo es IANO, un robot creado por el equipo UNICT, que utiliza Erlang por su inteligencia artificial y ganó la medalla de plata en el concurso eurobot de 2009. Otro ejemplo es Wings 3D, un modelador 3D de código abierto (pero no un renderizador) escrito en Erlang y, por lo tanto, multiplataforma.

Dejo link: https://learnyousomeerlang.com/introduction

Transformar el mundo de las aplicaciones de mensajería

 Me llego este mail y se los quiero compartir: 

¿Qué es Erlang?


Erlang es un lenguaje de programación funcional. Si alguna vez ha trabajado con lenguajes imperativos, declaraciones como i++ pueden resultarle normales; en programación funcional no están permitidos. De hecho, ¡está estrictamente prohibido cambiar el valor de cualquier variable! Esto puede sonar extraño al principio, pero si recuerdas tus clases de matemáticas, en realidad así es como las aprendiste:

y = 2

x = y + 3

x = 2 + 3

x = 5

Si hubiera agregado lo siguiente:

x = 5 + 1

x = x

∴ 5 = 6

Habrías estado muy confundido. La programación funcional reconoce esto: si digo que x es 5, entonces lógicamente no puedo afirmar que también sea 6. Esto sería deshonesto. Esta es también la razón por la que una función con el mismo parámetro siempre debería devolver el mismo resultado:

x = add_two_to(3) = 5

∴ x = 5

Las funciones que siempre devuelven el mismo resultado para el mismo parámetro se denominan transparencia referencial. Es lo que nos permite reemplazar add_two_to(3) con 5, ya que el resultado de 3+2 siempre será 5. Eso significa que luego podemos unir docenas de funciones para resolver problemas más complejos mientras nos aseguramos de que nada se rompa. Lógico y limpio ¿no? Sin embargo, hay un problema:


x = today() = 2009/10/22

-- wait a day --

x = today() = 2009/10/23

x = x

∴ 2009/10/22 = 2009/10/23


¡Oh, no! ¡Mis hermosas ecuaciones! ¡De repente todos se equivocaron! ¿Cómo es que mi función arroja un resultado diferente cada día?

Evidentemente, hay algunos casos en los que resulta útil romper la transparencia referencial. Erlang tiene este enfoque muy pragmático con la programación funcional: obedece sus principios más puros (transparencia referencial, evitar datos mutables, etc.), pero aléjate de ellos cuando surgen problemas del mundo real.

Ahora, definimos Erlang como un lenguaje de programación funcional, pero también hay un gran énfasis en la concurrencia y la alta confiabilidad. Para poder realizar docenas de tareas al mismo tiempo, Erlang utiliza el modelo de actor, y cada actor es un proceso separado en la máquina virtual. En pocas palabras, si fueras un actor en el mundo de Erlang, serías una persona solitaria, sentada en una habitación oscura sin ventanas, esperando junto a tu buzón para recibir un mensaje. Una vez que recibes un mensaje, reaccionas de una manera específica: pagas las facturas al recibirlas, respondes a las tarjetas de cumpleaños con una carta de agradecimiento e ignoras las cartas que no puedes entender.

El modelo de actor de Erlang puede imaginarse como un mundo en el que todos están sentados solos en su propia habitación y pueden realizar algunas tareas distintas. Todos se comunican estrictamente escribiendo cartas y listo. Si bien suena como una vida aburrida (y una nueva era para el servicio postal), significa que puedes pedirle a muchas personas que realicen tareas muy específicas por ti, y ninguna de ellas hará algo mal o cometerá errores que tendrán repercusiones en tu vida. el trabajo de otros; es posible que ni siquiera conozcan la existencia de otras personas además de ti (y eso es genial).

Para escapar de esta analogía, Erlang te obliga a escribir actores (procesos) que no compartirán información con otros bits de código a menos que se pasen mensajes entre sí. Cada comunicación es explícita, rastreable y segura.

Cuando definimos Erlang, lo hicimos a nivel de lenguaje, pero en un sentido más amplio, esto no es todo: Erlang es también un entorno de desarrollo en su conjunto. El código se compila en código de bytes y se ejecuta dentro de una máquina virtual. Entonces Erlang, al igual que Java, puede ejecutarse en cualquier lugar. La distribución estándar incluye (entre otras) herramientas de desarrollo (compilador, depurador, generador de perfiles, framework de prueba), el framework Open Telecom Platform (OTP), un servidor web, un generador de analizadores y la base de datos mnesia, un sistema de almacenamiento de valores clave capaz de replicarse en muchos servidores, admitiendo transacciones anidadas y permitiéndole almacenar cualquier tipo de datos de Erlang.

La VM y las librería también le permiten actualizar el código de un sistema en ejecución sin interrumpir ningún programa, distribuir su código con facilidad en muchas computadoras y administrar errores y fallas de una manera simple pero poderosa.

Una política general relacionada en Erlang: dejar que se explote. No como un avión con decenas de pasajeros muriendo, sino más bien como un equilibrista con una red de seguridad debajo. Si bien debes evitar cometer errores, en la mayoría de los casos no será necesario verificar cada tipo o condición de error.

La capacidad de Erlang para recuperarse de errores, organizar el código con actores y hacerlo escalar con la distribución y la concurrencia suena increíble!

Dejo link: https://learnyousomeerlang.com/introduction


jueves, 14 de septiembre de 2023

Prueba Erlang


Quiero recomendarles el tutorial tryerlang que encontre de casualidad. Si bien es basico, es muy interactivo y esta bueno. 

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

Box en Rust parte 2


Y siguiendo con Box, los tipos de datos recursivos o tipos de datos con tamaños dinámicos necesitan usar un Box:

#[derive(Debug)]

enum List<T> {

    Cons(T, Box<List<T>>),

    Nil,

}


fn main() {

    let list: List<i32> = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));

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

}

Si no se usaba Box e intentábamos incrustar una Lista directamente en la Lista, el compilador no calcularía un tamaño fijo de la estructura en la memoria (la Lista sería de tamaño infinito).

Box resuelve este problema ya que tiene el mismo tamaño que un puntero normal y solo apunta al siguiente elemento de la Lista.

Veamos que pasa si lo eliminamos: 

enum List<T> {

    Cons(T, List<T>),

    Nil,

}


fn main() {

    let list: List<i32> = List::Cons(1, List::Cons(2, List::Nil));

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

}

Y si ejecutamos esto: 

cargo run

   Compiling hello_cargo v0.1.0 

error[E0072]: recursive type `List` has infinite size

 --> src/main.rs:2:1

  |

2 | enum List<T> {

  | ^^^^^^^^^^^^

3 |     Cons(T, List<T>),

  |             ------- recursive without indirection

  |

help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle

  |

3 |     Cons(T, Box<List<T>>),

  |             ++++       +


For more information about this error, try `rustc --explain E0072`.

error: could not compile `hello_cargo` due to previous error


Un Box no puede estar vacío, por lo que el puntero siempre es válido y no nulo. Esto permite al compilador optimizar el diseño de la memoria.

sábado, 9 de septiembre de 2023

Como crear un nuevo proyecto con Clojure y Leiningen?



Bueno, todo tiene un principio y vamos a empezar a crear un proyectito de consola con Clojure y Leiningen

$ lein new app my-project

Fijate vos que si queres utilizar nombre de proyecto con mayuscula, no te deja, ojo ahi. 

Ahora vamos a ejecutarlo :

$ lein run

podemos generar un jar con : 

$ lein jar

Si estamos trabajando en un proyecto de aplicación, Leiningen nos brinda la posibilidad de construir lo que se llama un uberjar. Este es un archivo JAR que contiene el proyecto en sí y todas las dependencias y está configurado para permitir que se ejecute tal cual.

$ lein uberjar

Compiling secuencia-clojure.core

$ java -jar target/uberjar/el-jar-standalone.jar 

Y ya podemos empezar!!

viernes, 8 de septiembre de 2023

Box en Rust


Box es un puntero a los datos del monticulo:

fn main() {

    let five = Box::new(5);

    println!("five: {}", *five);

}

Box<T> implementa Deref<Target = T>, lo que significa que puedes llamar métodos desde T directamente en un Box<T>.

Box es como std::unique_ptr en C++, excepto que se garantiza que el elemento no será nulo.

En el ejemplo anterior, incluso podemos omitir el * en println! gracias a Deref.

Un Box puede resultar útil cuando:

  • tiene un tipo cuyo tamaño no se puede conocer en el momento de la compilación, pero el compilador de Rust quiere saber un tamaño exacto.
  • desea transferir una propiedad de una gran cantidad de datos. Para evitar copiar grandes cantidades de datos en la pila, almacene los datos en el monticulo en un Box de modo que solo se mueva el puntero.

lunes, 4 de septiembre de 2023

HashMap en Rust


Veamos un ejemplo: 


use std::collections::HashMap;

fn main() {

    let mut page_counts = HashMap::new();

    page_counts.insert("Adventures of Huckleberry Finn".to_string(), 207);

    page_counts.insert("Grimms' Fairy Tales".to_string(), 751);

    page_counts.insert("Pride and Prejudice".to_string(), 303);


    if !page_counts.contains_key("Les Misérables") {

        println!("We know about {} books, but not Les Misérables.",

                 page_counts.len());

    }


    for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {

        match page_counts.get(book) {

            Some(count) => println!("{book}: {count} pages"),

            None => println!("{book} is unknown.")

        }

    }


    // Use the .entry() method to insert a value if nothing is found.

    for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {

        let page_count: &mut i32 = page_counts.entry(book.to_string()).or_insert(0);

        *page_count += 1;

    }


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

}


Si ejecutamos este programa: 

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

     Running `target/debug/hello_cargo`

We know about 3 books, but not Les Misérables.

Pride and Prejudice: 303 pages

Alice's Adventure in Wonderland is unknown.

{

    "Pride and Prejudice": 304,

    "Grimms' Fairy Tales": 751,

    "Adventures of Huckleberry Finn": 207,

    "Alice's Adventure in Wonderland": 1,

}


HashMap no está definido por defecto y es necesario incluirlo : use std::collections::HashMap;

Desde Rust 1.56, HashMap implementa From<[(K, V); N]>, que nos permite inicializar fácilmente un mapa hash a partir de una matriz literal:

  let page_counts = HashMap::from([

    ("Harry Potter and the Sorcerer's Stone".to_string(), 336),

    ("The Hunger Games".to_string(), 374),

  ]);

Alternativamente, HashMap se puede construir a partir de cualquier iterador que produzca tuplas de valores clave.

Mostramos HashMap<String, i32> y evitamos usar &str como clave para facilitar los ejemplos. Por supuesto, se puede utilizar referencias en colecciones, pero puede generar complicaciones con el verificador de préstamos.

Podemos agregar estas lineas : 

  let pc1 = page_counts

      .get("Harry Potter and the Sorcerer's Stone ")

      .unwrap_or(&336);

  let pc2 = page_counts

      .entry("The Hunger Games".to_string())

      .or_insert(374);

La primera línea verá si un libro está en el HashMap y, en caso contrario, devolverá un valor alternativo. La segunda línea insertará el valor alternativo en el HashMap si no se encuentra el libro.

 


Vec en Rust


Vec es el búfer estándar de tamaño variable de Rust, veamos un ejemplo:


fn main() {

    let mut v1 = Vec::new();

    v1.push(42);

    println!("v1: len = {}, capacity = {}", v1.len(), v1.capacity());


    let mut v2 = Vec::with_capacity(v1.len() + 1);

    v2.extend(v1.iter());

    v2.push(9999);

    println!("v2: len = {}, capacity = {}", v2.len(), v2.capacity());


    // Canonical macro to initialize a vector with elements.

    let mut v3 = vec![0, 0, 1, 2, 3, 4];


    // Retain only the even elements.

    v3.retain(|x| x % 2 == 0);

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


    // Remove consecutive duplicates.

    v3.dedup();

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

}


Vec implementa Deref<Target = [T]>, lo que significa que puedes llamar a métodos de slice.

  • Vec es un tipo de colección, junto con String y HashMap. Los datos que contiene se almacenan en el monticulo. Esto significa que no es necesario conocer la cantidad de datos en el momento de la compilación. Puede crecer o reducirse en tiempo de ejecución.
  • Observe que Vec<T> también es un tipo genérico, pero no es necesario especificar T explícitamente. Como siempre con la inferencia de tipo Rust, la T se estableció durante la primera llamada push.
  • vec![...] es una macro canónica para usar en lugar de Vec::new() y admite agregar elementos iniciales al vector.
  • Para indexar el vector se puede utilizar [], pero entrarán en pánico si están fuera de los límites. Alternativamente, usar get devolverá un Option. La función pop eliminará el último elemento.

domingo, 3 de septiembre de 2023

String en Rust


 String es el búfer de cadena UTF-8 estándar:


fn main() {

    let mut s1 = String::new();

    s1.push_str("Hello");

    println!("s1: len = {}, capacity = {}", s1.len(), s1.capacity());


    let mut s2 = String::with_capacity(s1.len() + 1);

    s2.push_str(&s1);

    s2.push('!');

    println!("s2: len = {}, capacity = {}", s2.len(), s2.capacity());


    let s3 = String::from("🇨🇭");

    println!("s3: len = {}, number of chars = {}", s3.len(),

             s3.chars().count());

}


Si ejecutamos este código: 


Compiling hello_cargo v0.1.0 

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

     Running `target/debug/hello_cargo`

s1: len = 5, capacity = 8

s2: len = 6, capacity = 6

s3: len = 8, number of chars = 2

String implementa Deref<Target = str>, lo que significa que puedes llamar a todos los métodos str en un String.

  • String::new devuelve una nueva cadena vacía, se usa String::with_capacity cuando se sabe cuántos caracteres tiene la cadena.
  • String::len devuelve el tamaño de la cadena en bytes (que puede ser diferente de su longitud en caracteres).
  • String::chars devuelve un iterador sobre los caracteres reales. Un carácter puede ser diferente de lo que un humano considerará un "carácter" debido a los grupos de grafemas.
  • Cuando las personas se refieren a cadenas, podrían estar hablando de &str o String.
  • Cuando un tipo implementa Deref<Target = T>, el compilador le permitirá llamar de forma transparente a métodos desde T.
  • String implementa Deref<Target = str> que le da acceso de forma transparente a los métodos de str.
  • String se implementa como un contenedor alrededor de un vector de bytes; muchas de las operaciones que ve admitidas en vectores también se admiten en String, pero con algunas garantías adicionales.