Implementa operaciones básicas de arrays.
En los lenguajes funcionales, las operaciones de arrays como length, map y reduce son muy comunes.
Implementa una serie de operaciones básicas de arrays, sin usar funciones existentes.
El número exacto y los nombres de las operaciones que debes implementar dependerán del track, para evitar conflictos con nombres existentes, pero las operaciones generales que implementarás incluyen:
append (dados dos arrays, agrega todos los elementos del segundo array al final del primero);concatenate (dada una serie de arrays, combina todos los elementos de todos los arrays en un solo array aplanado);filter (dado un predicado y un array, devuelve el array de todos los elementos para los que predicate(item) es True);length (dado un array, devuelve el número total de elementos que contiene);map (dada una función y un array, devuelve el array de los resultados de aplicar function(item) a todos los elementos);foldl (dadas una función, un array y un acumulador inicial, fold (reduce) cada elemento en el acumulador desde la izquierda);foldr (dadas una función, un array y un acumulador inicial, fold (reduce) cada elemento en el acumulador desde la derecha);reverse (dado un array, devuelve un array con todos los elementos originales, pero en orden inverso).Ten en cuenta que el orden en el que se pasan los argumentos a las funciones fold (foldl, foldr) es importante.
Para este ejercicio vas a necesitar usar el polimorfismo de parámetros de Odin (más comúnmente llamado genéricos). Si no conoces esta característica, aquí tienes un resumen rápido para empezar.
El polimorfismo de parámetros es una característica de los lenguajes de programación que permite a quien programa ser menos específico (más genérico, de ahí el nombre) sobre los tipos que usa en su código, sin dejar de lado la seguridad de tipos. Obviamente, esto solo tiene sentido en lenguajes de tipado fuerte como Odin.
Empecemos con un ejemplo. Supongamos que quieres incrementar todos los elementos de un array en un valor fijo, un problema bastante sencillo.
incr_array_int :: proc(a: []int, by: int) -> []int {
new_array := make([]int, len(a))
for i := 0; i < len(a); i+= 1 {
new_array[i] = a[i] + by
}
return new_array
}
¿Y si ahora también necesitas la misma funcionalidad para números de punto flotante?
incr_array_f64 :: proc(a: []f64, by: f64) -> []f64 {
new_array := make([]f64, len(a))
for i := 0; i < len(a); i+= 1 {
new_array[i] = a[i] + by
}
return new_array
}
¿Y luego para enteros sin signo, números de punto flotante de 32 bits, y así sucesivamente?
En poco tiempo terminas con un montón de procedimientos que hacen exactamente lo mismo pero con tipos distintos. Si alguna vez necesitas actualizar la lógica, tienes que asegurarte de hacerlo en todas las variantes, lo que puede convertirse en mucho trabajo de mantenimiento. La otra molestia es que tienes que darle un nombre distinto a cada procedimiento, porque Odin no admite la sobrecarga implícita de procedimientos (todavía puedes usar sobrecarga explícita, pero esa es una historia para otro ejercicio).
Odin, al ser un lenguaje práctico, ofrece una solución con el polimorfismo de parámetros. Siempre que el compilador pueda deducir el tipo de un parámetro en tiempo de compilación, puedes darle un nombre genérico, como T. Reescribamos nuestro procedimiento anterior:
incr_array :: proc(a: []$T, by: T) -> []T {
new_array := make([]T, len(a))
for i := 0; i < len(a); i+= 1 {
new_array[i] = a[i] + by
}
return new_array
}
Fíjate que reemplazamos todas las anotaciones de tipo (int o f64) por T y que la primera aparición de T va precedida por un signo de dólar ($T). El tipo $T le indica al compilador de Odin que el nombre T es un nombre genérico para el tipo, que se reemplazará por el nombre real durante la compilación. Y, como el compilador ahora conoce el tipo genérico T, las apariciones siguientes de ese mismo tipo solo necesitan marcarse con el nombre de tipo elegido (T).
Ahora puedes escribir código como:
a_int := incr_array([]int{1, 2, 3}, 10)
a_f64 := incr_array([]f64{1.0, 2.0, 3.0}, 10.0)
En la primera sentencia, el compilador de Odin identificará el tipo del primer parámetro ([]int) con el tipo del parámetro genérico ([]$T), deducirá que T = int y procederá a compilar una versión con todas las apariciones siguientes de T reemplazadas por int (equivalente a la versión especializada incr_array_int() de arriba). Si omitieras el signo de dólar en la definición del primer parámetro, el compilador habría buscado en el paquete actual y en la lista de importaciones un tipo llamado T, y lo más probable es que devolviera un error de compilación: Error: Undeclared name: T.
La segunda sentencia funciona exactamente igual que la primera, pero con el compilador identificando T como f64.
Es costumbre dar a los tipos genéricos nombres de una sola letra (T y E son los más usados).
Ahora ya deberías saber lo suficiente sobre el polimorfismo de parámetros, también conocido como tipos genéricos, para resolver el ejercicio List Operations.
Regístrate en Exercism para aprender y dominar Odin con 73 ejercicios y mentoría humana real, todo gratis.
Disfruta de una introducción práctica a la recursión, explora las alternativas imperativas y funcionales a Operaciones con arrays, y profundiza en la recursión de cola y las funciones acumuladoras.