Agregación y acumuladores en programación funcional con Scala
Resumen
¿Qué es la agregación en programación funcional?
La programación funcional nos desafía a repensar cómo abordamos problemas comunes. Uno de estos enfoques es la agregación, que utiliza funciones específicas para ejecutar operaciones acumulativas sin depender de variables externas. En lugar de mantener una variable acumuladora explícita, se trabaja con listas de valores y se aplican funciones como fold left y fold right para lograr el objetivo.
¿Cómo trabajan los acumuladores en programación funcional?
En paradigmas como la programación operativa, los acumuladores suelen mantenerse en variables externas que recopilan valores mientras se itera sobre una estructura de datos. Sin embargo, en programación funcional, se utilizan funciones aplicadas a listas que resuelven esta necesidad, alejándose del uso de variables externas y haciendo el código más limpio y declarativo.
Ejemplo de funciones comunes para acumulación:
sum: utilizada en listas numéricas para calcular la suma de todos los elementos.
fold left y fold right: permiten la acumulación de datos para diferentes tipos de casos sin alterar el código con variables externas.
Implementación de acumulación con 'fold left' en Scala
Scala nos ofrece una herramienta poderosa: fold left, que requiere un valor inicial y una operación a aplicar. Veremos cómo funciona con el cálculo del factorial de un número, un ejemplo clásico para entender esta técnica.
Paso a paso para calcular el factorial:
Creación de la lista de números: Generamos una lista de números de 1 a n con la sintaxis 1 to n.
val lista =1 to 3
Utilización de 'fold left': Para aplicar fold left, necesitamos definir:
Un valor inicial que en el caso del factorial es 1.
Una operación de acumulación que será la multiplicación entre el acumulador y cada uno de los elementos de la lista.
val resultado = lista.foldLeft(1)((r, n)=> r * n)
Imprimir el resultado: Finalmente, damos salida al resultado obtenido.
println(resultado)// Imprime 6, el factorial de 3
Al usar fold left, Scala permite aplicar la lógica de acumulación en un solo paso, generando un código más claro y conciso.
Ventajas de usar 'fold left' y 'fold right'
El uso de funciones como fold left y fold right en las listas proporciona varias ventajas:
Simplicidad: Al codificar menos, se reduce la complejidad y las posibles fuentes de errores.
Modularidad: Estas funciones permiten pensar en términos de operaciones atómicas, mejorando la legibilidad del código.
Reusabilidad: Al escribir funciones puras, es más fácil reutilizarlas en distintos contextos.
Razonamiento inductivo en programación funcional
La transición a la programación funcional a menudo requiere un cambio en la forma de pensar, pasando a un razonamiento inductivo. Esto implica resolver problemas al definir operaciones que automáticamente se aplican a elementos de una estructura, como listas o secuencias, transformando la acumulación de datos en una tarea más natural y eficiente. Este paradigma es muy potente y te invita a explorar nuevas formas de resolver problemas, potenciado por el uso de herramientas funcionales como fold left y fold right.
Complementando me gustaria agregar este código donde se compara foldLeft y foldRight para ver como afectan el resultado final
List(1, 3, 8).foldLeft(100)(_ - ) == ((100 - 1) - 3) - 8 == 88
List(1, 3, 8).foldRight(100)( - _) == 1 - (3 - (8 - 100)) == -94
Importantísimo!
Según me clarifica tu ejemplo, foldLeft recorre la lista de izquierda a derecha, foldRight lo hace de derecha a izquierda.
Gracias
Agregación
Si queremos hacer un acumulador en programación imperativa debíamos tener una variable externa donde íbamos acumulando los valores. En FP tendremos una lista con todos los valores que queremos acumular y habrán unas opciones sobre esas listas que nos ayudaran a esto.
Un ejemplo de acumulador sería la función sum()que está disponible para listas de números. Pero existe una función más general.
FoldLeft y FoldRight nos ayudan a acumular sin tener variables externas.
scala>val resultado =(1 to 4).foldLeft(1L)((r,n)=>r*n)val resultado:Long=24scala> println(resultado)24
La lógica que sigue detrás es que siempre necesitamos un valor inicial y necesitaremos una función que ira recorriendo cada uno de elementos de la lista teniendo el acumulador del resultado y el elemento que nos va a llegar.
foldLeft y FoldRight nos permiten acumular sin necesidad de tener variables externas.
Crtl+/ para comentar
La forma de generar una lista en scala es muy similar a ruby, ejemplo (1 to 4). , wao que bien.
Una pregunta, el 1l en el foldleft es únicamente para determinar el valor inicial o también el tipo de valores que recibirá la función?
El 1L se usa para decirle de forma explícita al compilador que el valor 1 que estás ingresando a la función es de tipo Long, si no fuese de esta forma, la función podría retornar un entero por la forma en que está definida la función en SCALA:
def foldLeftB(op: (B, A) ⇒ B): B
Si te fijas, toda la operación retorna un valor del mismo tipo que el del Inicial y del mismo modo el rango de la función op debe estar contenido en B ( Para efectos prácticos, el rango es el mismo tipo).
Por otro lado se usa en este caso porque la operación del factorial puede llegar a retornar números realmente grandes y si son guardados en un valor de tipo Int se podría tener un OverFlow. Déjame saber si tienes alguna otra duda!
foldLeft y foldRight son funciones de orden superior en Scala que se utilizan para reducir una colección de elementos a un solo valor aplicando una operación dada de manera iterativa.
foldLeft: Comienza el plegado desde el primer elemento de la colección y avanza hacia la derecha. Toma dos parámetros: un valor inicial y una función que combina el valor acumulado con cada elemento de la colección. La función de combinación toma dos argumentos: el valor acumulado hasta ahora y el elemento actual de la colección.
foldRight: Comienza el plegado desde el último elemento de la colección y avanza hacia la izquierda. Al igual que foldLeft, toma un valor inicial y una función de combinación. La diferencia principal radica en el orden de procesamiento de los elementos en la colección.
Aquí tienes una comparación más detallada:
foldLeft:
Sintaxis: foldLeft(initial)(function)
Comienza desde el primer elemento de la colección.
Aplica la función de combinación de izquierda a derecha.
Se utiliza comúnmente para operaciones como sumas, productos, concatenaciones, etc.
Útil cuando la operación de plegado depende del orden de los elementos en la colección.
foldRight:
Sintaxis: foldRight(initial)(function)
Comienza desde el último elemento de la colección.
Aplica la función de combinación de derecha a izquierda.
A menudo se utiliza en operaciones donde el orden de los elementos en la colección no importa o es importante el orden inverso.
Útil cuando la operación de plegado es asociativa y conmutativa.
Aquí tienes un ejemplo de uso de foldLeft y foldRight para calcular la suma de una lista de números:
val numbers =List(1,2,3,4,5)val sumLeft = numbers.foldLeft(0)((acc, num)=> acc + num)// Resultado: 15 (1 + 2 + 3 + 4 + 5)val sumRight = numbers.foldRight(0)((num, acc)=> num + acc)// Resultado: 15 (1 + 2 + 3 + 4 + 5)```En este ejemplo, `foldLeft` y `foldRight` producen el mismo resultado, pero dependiendo del contexto y la operación realizada, uno puede ser más adecuado que el otro.
Acumuladores
Cuando trabajamos con Listas en lenguajes funcionales, hay casos en los que necesitaremos hacer acumulaciones de datos para entregar un resultado final.
Un ejemplo de un acumulador sería la función sum() que está disponible para listas de números. Pero existe una función más general
FoldLeft y FoldRight
Estas funciones nos permiten prescindir de “acumuladores manuales”, como los que se usan en programación imperativa
val resultado =(1 to 3).foldLeft(1L)((r, n)=> r*n)println(resultado)
Sum nos permite acumular las sumas.
Código del Ejemplo:
val resultado =(1 to 3).foldLeft(1L)((r, n)=> r*n)println(resultado)6