Funciones Totales y Parciales en Programación Funcional
Resumen
¿Qué son las funciones totales y parciales?
En el desarrollo de software, especialmente en programación funcional, es crucial entender el concepto de funciones totales y parciales. Las funciones totales son aquellas que pueden ofrecer una respuesta satisfactoria para todos los datos dentro de su dominio. Por otro lado, las funciones parciales pueden fallar al intentar brindar una respuesta a ciertos valores. Esto es particularmente relevante en Scala, un lenguaje flexible en el que, a pesar de permitir programación funcional, ciertas funciones podrían no manejar adecuadamente todos los insumos que reciben.
Ejemplos de funciones en listas
¿Cómo funcionan las funciones head, tail, last y get?
En Scala, hay varias funciones interesantes dentro de la clase Lista, como head, tail, y last. Para analizarlas, primero declaramos una lista de ejemplo:
val list = List(1,2,3)
head: Esta función devuelve el primer elemento de la lista. Por ejemplo, list.head devolvería 1.
tail: Retorna todos los elementos de la lista excepto el primero. Con list.tail, obtendremos [2, 3].
Aunque útiles, tanto head como tail son funciones parciales, lo que significa que pueden fallar si no se estructuran correctamente los datos de entrada.
¿Qué sucede con listas vacías?
Supongamos que tenemos una lista vacía, definida así:
val b = Nil // o alternativamente, List()
Intentar usar head o tail con esta lista vacía resultaría en un error, ya que no pueden operar correctamente con la ausencia de elementos. Esto nos indica que la función es parcial, pues no puede manejar todos los casos posibles sin fallar.
Alternativas más seguras: take y drop
¿Cómo hacer segura la manipulación de listas?
Afortunadamente, Scala ofrece funciones seguras para algunos de los mismos propósitos:
take: Esta función es una alternativa segura a head. Si se pasa a una lista vacía, take simplemente devuelve otra lista vacía en lugar de fallar.
list.take(1)// devuelve List(1)
drop: Equivalente seguro a tail, elimina un número específico de elementos desde el principio de la lista. Al igual que take, se comportará de manera segura con listas vacías.
list.drop(1)// devuelve List(2, 3)
Estas funciones permiten manejar listas sin riesgo de errores inesperados y son más seguras para utilizar en lugares donde no se puede garantizar que las listas tendrán elementos.
Importancia de preferir funciones totales
La elección correcta de funciones puede prevenir problemas en el código. Aunque es posible utilizar funciones parciales, es vital asegurarse de no llamarlas en circunstancias donde puedan fallar. Siempre es recomendable optar por funciones totales siempre que sea posible para asegurar que el código sea robusto y confiable, especialmente en producción.
Recordemos que la programación segura y efectiva no solo es más económica a largo plazo sino que también reduce significativamente el riesgo de errores y fallos en el software. Al dominar estos conceptos y emplear prácticas sólidas, se contribuye de manera significativa a un código más eficiente y mantenible.
Consideran que primero debería hacer el curso de Java? Es que me estoy perdiendo un montón en la sintaxis y lo resumidos que resultan los programas con Scala.
No creo que sea indispensable, pero depende.
Si conoces Python o Java, muchos conceptos van a ser más obvios de comprender que si vienes de solo JavaScript, por poner un ejemplo.
Pero a nivel de sintaxis, Java no creo que aporte mucho, por el contrario, la sintaxis de Java tiene más cosas a tener en cuenta que para aprender Scala no te van a facilitar o a complicar en nada.
También está que con Scala tendrías que desapreder muchas cosas que se aprenden en Java, igual, creo que deberías aprenderlo todo.
No lo haga compa, aparte de lo que menciona @EruJazz si le entras a Java tendrías que aprender como usan OOP allá y justo en este curso estamos para olvidarnos de ese paradigma. Saludos.
Me surgió la pregunta de: Si existen las funciones totales que nunca fallan ¿Cual es la razón por la que existen funciones parciales que pueden generar errores?
Navegando por internet encontré que las funciones parciales suelen tener nombres más explícitos como en los casos del manejo de listas en este video, por ejemplo, es más fácil identificar el primer elemento de la lista como el head o la cabeza de la lista. Pero esto de los nombres no me parece suficiente razón.
Entonces seguí buscando y también encontré que las funciones parciales sirve para representar funciones matemáticas que no son inyectivas (entradas diferentes pueden generar una misma salida) o el ejemplo más claro es para las funciones matemáticas que no son continuas. Por ejemplo, si quisieramos definir o modelar la función de arcoseno deberiamos usar una funcion parcial porque el arcoseno no esta definido en valores fuera de [-1, 1] y no tendría sentido dar una respuesta fuera de ese dominio.
Imagino que si por ejemplo estuvieramos programando una calculadora y modelaramos el arcoseno como una funcion total, al hacer una cuenta con el arcoseno de 2 y retornar un valor cualquiera haría que al final de todas las operaciones retornaramos un valor erroneo, mi conclusión es que algunas veces es mejor que el programa no de respuesta en vez de dar respuestas incorrectas xd. Es más, como las funciones parciales retornan errores y excepciones podríamos capturarlas y ser más precisos controlando en que contextos, para que datos de entrada funciona nuestro programa y que debería hacer si recibe un dato de entrada no válido para por ejemplo retroalimentar al usuario.
En el codigo en un momento colocaste a. head ( con espacio) y las demás veces colocaste por ejm b.tail ó b.take(1), probé con el espacio y funciona, suena algo raro de scala no?
Función total
Es una función que puede dar respuesta a todos los elementos de su dominio. Es decir, que sin importar la entrada, la función será capaz de responder apropiadamente.
Función parcial
Una función que puede dar respuesta a sólo algunos elementos de su dominio y a otros no. Hay que tener cuidado con funciones que no están bien definidas, o que son inseguras de usar por no ser totales
.get()
.head()
.tail()
.last()
val a = List(1,2,3)//a: List[Int] = List(1,2,3)a.head
//res: Int = 1a.tail
//res: List[Int] = List(2,3)val b = Nil
b.head //error, es parcial porque no responde satisfactoriamente a todos los datosb.tail //mismo errorb.take(1)//res: List[Nothing] = List()b.drop(1)//res: List[Int] = List(2,3)
Para saber si una funcion es parcial, podemos llamar el .head y si nos da una exception y esto quiere decir que es una funcion parcial ya que nos da una respuesta no satisfactoria
Funcion total, da respuesta a todo los elementos de su dominio o alcance, es diferente a una funcion parcial