Implementa operaciones básicas con listas.
En los lenguajes funcionales, las operaciones con listas como length, map y reduce son muy habituales. Implementa una serie de operaciones básicas con listas, sin usar las funciones que ya existen.
El número y los nombres exactos de las operaciones que hay que implementar dependerán de cada track, para evitar conflictos con nombres ya existentes, pero las operaciones generales que vas a implementar son:
append (dadas dos listas, añade todos los elementos de la segunda lista al final de la primera);concatenate (dada una serie de listas, combina todos los elementos de todas las listas en una sola lista aplanada);filter (dados un predicado y una lista, devuelve la lista de todos los elementos para los que predicate(item) es True);length (dada una lista, devuelve el número total de elementos que contiene);map (dadas una función y una lista, devuelve la lista de los resultados de aplicar function(item) a todos los elementos);foldl (dadas una función, una lista y un acumulador inicial, pliega (reduce) cada elemento dentro del acumulador desde la izquierda);foldr (dadas una función, una lista y un acumulador inicial, pliega (reduce) cada elemento dentro del acumulador desde la derecha);reverse (dada una lista, devuelve una lista 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 de plegado (foldl, foldr) es significativo.
Para este ejercicio tendrás que usar el polimorfismo de parámetros de Odin (más conocido como genéricos). Si no has visto antes esta funcionalidad, aquí tienes un repaso rápido para empezar.
El polimorfismo de parámetros es una funcionalidad de los lenguajes de programación que permite a los programadores ser menos específicos (más genéricos, de ahí el nombre) sobre los tipos que usan en su código sin dejar de mantener la seguridad de tipos. Obviamente, esto solo tiene sentido en lenguajes fuertemente tipados 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 necesitas la misma funcionalidad para números de coma 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 coma flotante de 32 bits, y así sucesivamente?
En poco tiempo acabas con un montón de procedimientos que hacen exactamente lo mismo pero con tipos diferentes. Si alguna vez necesitas actualizar la lógica, tendrás que asegurarte de hacerlo en todas las variantes, lo que puede convertirse en una gran carga de mantenimiento. La otra molestia es que tienes que dar un nombre distinto a cada procedimiento, ya que Odin no admite la sobrecarga implícita de procedimientos (todavía podrías usar la 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 el 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 en que hemos sustituido todas las anotaciones de tipo (int o f64) por T y en que la primera aparición de T va precedida de un signo de dólar ($T).
El tipo $T le dice al compilador de Odin que el nombre T es un nombre genérico para el tipo, que se sustituirá por el nombre real durante la compilación.
Y, como el compilador ya conoce el tipo genérico T, las apariciones siguientes de ese mismo tipo solo tienen que ir marcadas 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 instrucción, 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 compilará una versión con todas las apariciones siguientes de T sustituidas 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 instrucción funciona exactamente igual que la primera, pero con el compilador identificando T como f64.
Se suele dar a los tipos genéricos nombres de una sola letra (T y E son los más habituales).
Ahora ya deberías saber lo suficiente sobre el polimorfismo de parámetros, también conocido como tipos genéricos, para enfrentarte al 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 sumérgete en la recursión de cola y las funciones acumuladoras.