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 / n2
import 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
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
Posibles valores:
Some - Se genera cuando el valor existe
None - Se genera cuando el valor no existe
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
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")
}
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
Either / Left / Right
Either / Left / Right
Ejemplo
def divideXByY(x: Int, y: Int): Either[String, Int] =
if (y == 0) Left("No se puede dividir por 0")
else Right(x / y)
Either / Left / Right
Detalles
Utilizado en lugar de Try (antes de Scala 2.10)
Análogo a Try / Failure / Success
Try -> Either
Failure -> Left
Success -> Right
Método devuelve Either
Right: procesamiento correcto existe un valor
Left: fallas
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.
Ejemplo: Validación donde quieras devolver un mensaje de error detallado.
Recomendación usar:
Try / Succes / Failure
Try
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
¿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")
values.map(toInt)
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: Parseo de datos de texto a números.
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