Santiago Quiñones Cuenca
Software Developer and Educator, Master in Software Engineering, Research UTPL {Loja, Ecuador} Repositories: http://github.com/lsantiago
Programación funcional y reactiva - Computación
El problema de los efectos colaterales
Entendiendo a los efectos colaterales
Programación funcional se basa en que las funciones no deberían tener efectos colaterales.
Un ejemplo:
Entendiendo a los efectos colaterales
En muchos lenguajes de programación imperativos, la llamada a la función print("hola") imprimirá algo y no devolverá nada.
Otro ejemplo:
En lenguajes funcionales puros, una función de impresión toma un objeto que representa el estado del mundo exterior y devuelve un nuevo objeto que representa al estado después de haber realizado la salida.
Efectos colaterales
Se considera que una función tiene efectos colaterales si
Solución a los efectos colaterales
La solución es dejar de usar efectos colaterales y codificarlos en el valor de retorno.
Efectos colaterales - solución - Ejemplo
Representa al error como una estructura de datos.
Fenómenos representados como datos.
def division(n1: Double, n2: Double) =
if (n2 == 0) throw new RuntimeException("División por 0")
else n1 / n2import scala.util.Try
def pureDivision(n1: Double, n2: Double): Try[Double] =
Try { division(n1, n2) }Efectos colaterales - solución - Ejemplo
Option / Some / None
Option / Some / None
Presencia vs ausencia
Representa un valor opcional. No hay información de error, solo "existe" (Some) o "no existe" (None). Se utiliza para representar la presencia o ausencia de un valor, reemplazando al infame null.
Option
// La forma canónica y recomendada
def dividir(a: Int, b: Int): Option[Int] =
if b == 0 then None
else Some(a / b)Ejemplo
Option
def getMiddleName(personId: Int): Option[String] = {
if (personId == 1) Some("Luis") // Tiene segundo nombre
else None // No tiene segundo nombre
}
// Uso
val middleName = getMiddleName(1)
middleName match {
case Some(name) => println(s"El segundo nombre es $name")
case None => println("No tiene segundo nombre")
}
Ejemplo
Option
val inventory = List(
("Laptop", 1200.50),
("Mouse", 25.75),
("Keyboard", 45.00)
)
def findPrice(productName: String): Option[Double] = {
inventory.find(_._1 == productName).map(_._2)
}
println(findPrice("Laptop")) // Some(1200.5)
println(findPrice("Mouse")) // Some(25.75)
println(findPrice("Monitor")) // None
Ejemplo
Option
def getText(msg: String): Option[String] = {
if (msg != null) Some(msg.toUpperCase) else None
}
// Uso
val length = getText(null).map(_.length).getOrElse(0)
// o
val length = getText(null).map(_.length) // Option[Int]
Ejemplo
Option / Some / None
Manejo de nulos en tú código
Analice el siguiente código
toInt?toInt?toInt("1")?toInt("Uno")?txtNumbers?txtNumbers.map(toInt)?flatten?def toInt(s: String) : Option[Int] = {
try {
Some(Integer.parseInt(s))
} catch {
case e: Exception => None
}
}
Scala Option
Manejo de nulos
Alternativa Option
Si tiene la tentación de usar un valor nulo piense en Option
Option: representación de valores opcionales
Scala Option
Manejo de nulos
def toInt(s: String) : Option[Int] = {
try {
Some(Integer.parseInt(s))
} catch {
case e: Exception => None
}
}
import scala.util.control.Exception._
def toInt(s: String): Option[Int] = allCatch.opt(s.toInt)
Option
Obtener valores
val x = toInt("1").getOrElse(0)
toInt("1").foreach { i => printf("Obtener un Int:%d", i) }
toInt("1") match {
case Some(i) => println(i)
case None => println("That didn't work.")
}
Usar:
getOrElse
match
foreach
Option en Listas
Ejemplo
def toInt(s: String): Option[Int] = {
try {
Some(Integer.parseInt(s))
} catch {
case e: Exception => None
}
}
val txtNumbers = List("1", "2", "foo", "3", "bar")
txtNumbers.map(toInt)Option en Listas
Ejemplo
def toInt(s: String): Option[Int] = {
try {
Some(Integer.parseInt(s))
} catch {
case e: Exception => None
}
}
val txtNumbers = List("1", "2", "foo", "3", "bar")
txtNumbers.map(toInt)
txtNumbers.map(toInt).flatten
Ejemplo de Option
Un cine tiene asientos numerados del 1 al 100. Escribe una función selectSeat que reciba un número de asiento (Int) y devuelva un Option[String]:
Some("Asiento seleccionado: [número]").None.Ejemplo de Option
// Definición de la función selectSeat
def selectSeat(seatNumber: Int): Option[String] = {
if (seatNumber >= 1 && seatNumber <= 100) {
Some(s"Asiento seleccionado: $seatNumber")
} else {
None
}
}
// Función para mostrar el resultado al usuario
def displaySeatSelection(seatOption: Option[String]): String =
seatOption.getOrElse("El asiento seleccionado no es válido.")
// Ejemplo de uso
val seats = List(50, 150, 1, -10, 100) // Lista de asientos a probar
// Uso funcional para procesar y mostrar todos los asientos
seats.map(selectSeat).map(displaySeatSelection).foreach(println)Option
Utiliza option si necesitas representar la presencia o ausencia de un valor. Cuando no estás seguro de que exista un valor
Ejemplo: Buscar datos dentro de una base de datos, donde no siempre existe un resultado. Para evitar null. Cuando no importa por qué no hay dato (ej. buscar en un Mapa).
Either / Left / Right
Either / Left / Right
Disyunción lógica
Un resultado correcto o un error informativo. Se utiliza cuando una operación puede fallar y necesitamos saber por qué.
Either / Left / Right
Ejemplo
def dividir(a: Int, b: Int): Either[String, Int] = {
if (b == 0) Left("Error: División por cero")
else Right(a / b)
}
val calculo = dividir(10, 0)
calculo match {
case Right(v) => println(s"Total: $v")
case Left(e) => println(s"Falló: $e")
}Either / Left / Right
Ejemplo
def validarEdad(edad: Int): Either[String, Int] =
if edad < 18 then Left("Error: Menor de edad") // Fallo explicativo
else Right(edad) // Éxito
Either / Left / Right
Ejemplo
Either / Left / Right
Invocaciones
divideXByY(1, 0) match {
case Left(s) => println("Answer: " + s)
case Right(i) => println("Answer: " + i)
}
Either / Left / Right
Invocaciones
val x = divideXByY(1, 0)
// x: Either[String, Int] = Left(No se puede dividir por 0)
x.isLeft
// res0: Boolean = true
x.left
// res1: LeftProjection[String, Int] = LeftProjection(Left(No se puede dividir por 0))
Either
Utiliza either si necesitas manejar errores explícitos con información adicional.
Cuando usarlo:
Try
Try / Failure / Success
Protección
Un cálculo que tuvo éxito o "explotó" (crasheó). Diseñado específicamente para envolver código inseguro que podría lanzar excepciones (como convertir Strings a números o leer archivos).
Actúa como una red de seguridad funcional alrededor de código imperativo.
Try ejemplos - ingreso de datos
Una forma de ingreso de datos en Scala es a través del método readLine que pertenece al objeto
El método toInt permite transformar un String a Int.
¿Qué sucedería en el código anterior si en lugar de un número ingresa un texto o un valor real?
Try ejemplos - Parseo de datos
import scala.util.{Try, Success, Failure}
def parsearNumero(s: String): Try[Int] = {
Try(s.toInt) // Código inseguro envuelto
}
val input = "123a" // Esto fallaría normalmente
parsearNumero(input) match {
case Success(num) => println(s"Número: $num")
case Failure(ex) => println(s"Error: ${ex.getMessage}") }Try ejemplos
¿Qué sucedería en el código anterior si en lugar de un número ingresa un texto o un valor real?
Try ejemplos
¿Cómo solucionaría esos problemas?
Try ejemplos
Un pequeño programa de ingreso de datos
import scala.io.StdIn
object Exa0 {
def main(args: Array[String]) = {
val name = StdIn.readLine("Nombre: ")
val age = StdIn.readLine("Edad: ")
val weight = StdIn.readLine("Peso: ")
printf("Hola %s, tienes %s años y pesas %skg\n", name, age, weight)
}
}Pero y ¿si el usuario se equivoca en el ingreso de los datos?
Try ejemplos
La solución final
import scala.io.StdIn
import scala.util.{Try, Success, Failure}
object Ex4 {
def main(args: Array[String]): Unit = {
print(inData())
}
def inData(): String = {
val name = StdIn.readLine("Nombre: ")
val age = Try(StdIn.readLine("Edad: ").toInt)
val weight = Try(StdIn.readLine("Peso: ").toDouble)
"Hola %s, tienes %d años y pesas %.2fkg\n".format(
name,
age match {
case Success(v) => v
case Failure(e) => {
println("Error en la edad")
inData()
}
},
weight match {
case Success(v) => v
case Failure(e) => {
println("Error en el peso")
inData()
}
}
)
}
}
Lectura de un archivo
La lectura de un archivo puede lanzar diferentes excepciones
import scala.io.Source
Source.fromFile("myData.txt")
import scala.util.Try
import scala.io.Source
def readDataFromFile(filepath: String): Try[List[String]] =
Try{
var source = Source.fromFile(filepath)
var data = source.getLines.toList
source.close()
data
}
var data = readDataFromFile("C:/scala/files/numeros.txt").get
Try ejemplos - Procesamiento de valores
Suponga que tiene una lista de valores que le fueron entregados y que supuestamente representan números que necesita para trabajar.
val values = List("1", "3", "5", "9", "2", "2 0")
import scala.util.Try
val lista_valores = values.map(v => Try { v.toInt })
val lista_valores: List[scala.util.Try[Int]] = List(Success(1), Success(3), Success(5), Success(9), Success(2), Failure(java.lang.NumberFormatException: For input string: "2 0"))
Try ejemplos - Procesamiento de valores
¿Cómo procesarlos?
Try ejemplos - Procesamiento de valores leídos desde un archivo
Suponga que tiene un lista de valores que fueron obtenidos desde un archivo de texto y que supuestamente representan números que necesita para trabajar
import scala.util.{Try, Success, Failure}
import scala.io.Source
def readNumbersFromFile(filename: String): Try[List[Int]] = {
Try {
val source = Source.fromFile(filename)
val numbers = source.getLines().map(_.toInt).toList
source.close()
numbers
}
}
// Pruebas
println(readNumbersFromFile("numbers.txt"))
// Si el archivo contiene:
// 1
// 2
// 3
// Resultado: Success(List(1, 2, 3))
println(readNumbersFromFile("missing.txt"))
// Resultado: Failure(java.io.FileNotFoundException)
println(readNumbersFromFile("invalid.txt"))
// Si el archivo contiene:
// 1
// a
// Resultado: Failure(java.lang.NumberFormatException: For input string: "a")
Try ejemplos - Procesamiento de valores leídos desde un archivo
Obtener una lista de números enteros
Try ejemplos - Procesamiento de valores leídos desde un archivo
Verificar si existen errores
Try
Utiliza Try si estás trabajando con operaciones que pueden lanzar excepciones.
Ejemplo:
Resumen
Ejemplos con Option
Ejemplos con Either
Ejemplos con Try
Prácticas y Experimentación
Ejercicio 1
Trate de escribir la función toInt para que trabaje con Try/Succes/Failure
Use su nueva función para determinar si es posible utilizar:
getOrElse
foreach
match
flatten
allCatch
Ejercicio 2
Utilice las siguientes expresiones para leer cada una de las líneas de un archivo de texto y representarlo como una lista String
El archivo que debe leer está disponible en el EVA
Considerando que el archivo contiene un número entero en cada línea, intente sumarlos y devolver un resultado.
En el caso de existir excepciones use Try / Succes / Failure
By Santiago Quiñones Cuenca
Control de efectos colaterales
Software Developer and Educator, Master in Software Engineering, Research UTPL {Loja, Ecuador} Repositories: http://github.com/lsantiago