Como todos los años les deseo una feliz navidad y un buen 2024.
Gracias por leerme!
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:
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.
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.
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]
Características Principales:
Ventajas:
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;
}
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]
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
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
count_elements([], 0).
count_elements([_ | Tail], Count) :-
count_elements(Tail, TailCount),
Count is TailCount + 1.
(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
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.
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:
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
Me llego el siguiente mail y queria compartirlo :
|
Hola Emanuel, |
Cuatro de cada cinco responsables de toma de decisiones empresariales creen que la IA generativa transformará su industria en los próximos 12 meses.1 La IA generativa no tardará mucho en influir en casi todos los sectores de todos los negocios, ya sea con respecto a la creación de contenido, la programación colaborativa, la búsqueda empresarial o la creación de resúmenes. |
Ningún líder empresarial puede permitirse quedarse atrás. Para ayudarte a mantenerte al día con la información más reciente, nos complace compartir contigo “La guía para ejecutivos sobre la IA generativa”. |
En esta guía completa, encontrarás lo siguiente: |
|
1. Google Cloud Gen AI Benchmarking Study, julio de 2023 |
|
Nos vemos en la nube, El equipo de Google Cloud |
|
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");
}
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:]
}
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");
}
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.
(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)
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.
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
(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)
#[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 }
(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í.
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)))
)
)
)
)
)
#[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());
}
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
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.
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"
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(())
}
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.
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)