Programación funcional y reactiva - Computación
Objetivos de la clase
Al terminar, podrás…
01
Explicar qué hace pura a una función: determinismo, transparencia referencial y ausencia de efectos secundarios.
02
Distinguir funciones puras de métodos con estado, y decidir cuándo conviene cada enfoque.
03
Traducir fórmulas matemáticas (Σ, Π, ∀, ∃) a expresiones en Scala.
De la matemática a la programación
Funciones matemáticas
Algo para recordar
Una relación que asigna a cada elemento de entrada exactamente un elemento de salida.
Notación: f(x) = y. El conjunto de entradas válidas es el dominio.
Funciones matemáticas
Una función es como una máquina
Entrada
3
La máquina
f(x) = x² + 1
Salida
10
Regla de oro: si ingresas lo mismo, sale lo mismo.
Ingresar 3 da 10 hoy, mañana y siempre. La máquina no «recuerda» lo que le diste antes.
Funciones matemáticas
Tres ideas que vamos a llevar al código
Determinismo
Siempre responde igual
Misma entrada, misma salida. Sin sorpresas.
f(3) → 10, siempre
Transparencia referencial
Se puede cambiar por su resultado
Donde diga f(3) puedo escribir 10, y nada cambia.
f(3) ≡ 10
Sin efectos secundarios
No toca nada de afuera
Solo calcula y devuelve. No cambia variables, no imprime, no guarda archivos.
La pregunta clave
¿Podemos programar igual que definimos funciones matemáticas?
Sí. Ese es el paradigma funcional: programas construidos con funciones puras.
Funciones matemáticas
De la fórmula a la función en Scala
Matemáticas
Dominio: los números enteros
Scala
El tipo Int expresa el dominio
def f(x: Int): Int =
x * x + 1La misma regla en los dos lados: f(3) = 10 en matemáticas y en Scala.
Scala: lo esencial
Lenguaje Scala
Un lenguaje que une dos paradigmas
Orientado a objetos
Funcional
Plataforma
Creado por Martin Odersky (EPFL, Suiza); primera versión pública en 2004.
Lenguaje Scala
Los tipos básicos
| Tipo | Qué guarda | Ejemplo |
|---|---|---|
| Int | Números enteros | 42 |
| Double | Números con decimales | 3.14 |
| Boolean | Verdadero o falso | true |
| Char | Un solo carácter | 'A' |
| String | Texto | "Hola" |
| Unit | «Nada útil», parecido a void | () |
val y var
val edad: Int = 20
val es una caja sellada: su valor no cambia.
var es una caja abierta: se puede cambiar.
En esta materia usamos val.
Lenguaje Scala
Las partes de una función
def suma(a: Int, b: Int): Int = a + b
def
Define la función
suma
El nombre
(a: Int, b: Int)
Lo que recibe, con su tipo
: Int
Lo que devuelve
a + b
La regla (el cuerpo)
No hace falta return: la función devuelve el valor de su última expresión, aquí a + b.
Lenguaje Scala
Funciones sin nombre: lambdas
Con nombre
def cuadrado(x: Int): Int = x * xSin nombre (lambda)
(x: Int) => x * xSe lee: «dado un x, devuelve x por x».
List(1, 2, 3).map(x => x * x)
// List(1, 4, 9)¿Para qué sirven? Para pasar una regla corta a otra función sin tener que ponerle nombre.
Lenguaje Scala
Escribir menos, decir lo mismo
Funciona, pero sobra
def esMayor(edad: Int): Boolean =
if (edad >= 18) true
else falseMás claro
def esMayor(edad: Int): Boolean =
edad >= 18¿Por qué?
edad >= 18 ya es una pregunta cuya respuesta es true o false. No hace falta preguntar dos veces.
Lenguaje Scala
Encadenar operaciones, como en una línea de producción
// Cuenta las letras que no son a, e ni o
def contar(palabra: String): Int =
palabra.toLowerCase.filterNot(c => "aeo".contains(c)).length"Casa"
toLowerCase
"casa"
quita a, e, o
"cs"
length
2
Funciones puras
Funciones puras
Una función pura cumple dos condiciones
1 Con los mismos argumentos, siempre retorna el mismo valor.
2 No produce efectos secundarios.
¿Te suena?
Son las mismas ideas de la función matemática: siempre responde igual y no toca nada de afuera. Ahora, en código.
Funciones puras
Calculadora vs. alcancía
Pura
Calculadora
sumar(2, 3) → 5
Impura
Alcancía
depositar(10) → ¿?
La pregunta clave: ¿el resultado depende solo de lo que le paso?
Funciones puras
Estas funciones son puras
def suma(a: Int, b: Int): Int =
a + bdef promedio(nums: List[Int]): Double =
nums.sum.toDouble / nums.lengthFunciones puras
Estas funciones no son puras
Depende de estado externo
var contador = 0
def incrementarContador(x: Int): Int = {
contador = contador + x // ¡cambia!
contador
}Hace I/O
def leerArchivo(ruta: String): String =
scala.io.Source
.fromFile(ruta)
.mkStringFunciones puras
Cambiar la llamada por su resultado
Pura: se puede sustituir
def incrementar(n: Int): Int = n + 1
val a = incrementar(5) +
incrementar(5)
// = 6 + 6 = 12, siempreCada llamada vale 6. Puedo escribir 6 en su lugar.
Impura: no se puede
// incrementarContador, la de antes
val b = incrementarContador(5) +
incrementarContador(5)
// = 5 + 10 = 15, no 5 + 5Cada llamada da algo distinto: depende de cuándo se llame.
Si se puede cambiar por su resultado, es transparente.
Funciones puras
¿Por qué nos conviene que sean puras?
Fáciles de probar
Solo compruebas: con esta entrada, ¿sale este resultado?
Fáciles de entender
Todo lo que necesitas saber está en sus parámetros.
Fáciles de combinar
Encajan como piezas: la salida de una es la entrada de otra.
Seguras en paralelo
Varias pueden ejecutarse a la vez sin pisarse.
Funciones vs. métodos
Funciones vs. métodos
Diferencias conceptuales
Funciones vs. métodos
Actividad: analizar una clase con estado
public class Counter {
private int counter = 0;
public int incrementCounter() {
return ++counter;
}
public int multiplyCounter(int num) {
counter *= num;
return counter;
}
public int getCounter() {
return counter;
}
}Con un asistente de IA (ChatGPT, Claude u otro):
Funciones vs. métodos
Counter, en versión pura
object Counter {
def incrementar(c: Int): Int = c + 1
def multiplicar(c: Int, n: Int): Int = c * n
}
// Cada paso produce un valor nuevo
val c0 = 5
val c1 = Counter.incrementar(c0) // 6
val c2 = Counter.multiplicar(c1, 2) // 12
// c0 sigue valiendo 5El estado viaja explícito
Cada función recibe el valor y devuelve uno nuevo.
¿Y getCounter?
Desaparece: el valor ya está en c0, c1 y c2.
Funciones vs. métodos
¿Y si necesito imprimir o leer archivos? Como en un restaurante
El mesero recibe pedidos y entrega platos: habla con el mundo exterior.
El cocinero
Solo transforma ingredientes en platos. Esa es la parte pura.
// Mesero: leer (impuro)
val texto = leerArchivo("notas.txt")
// Cocinero: calcular (puro)
val notas = parsear(texto)
val media = promedio(notas)
// Mesero: mostrar (impuro)
println(s"Promedio: $media")La lógica queda pura; lo impuro queda pequeño y en los bordes.
De la fórmula al código
Funciones matemáticas a código
Para empezar: implementa estas funciones en Scala
Funciones matemáticas a código
Una fórmula es una expresión
(1 to 10).product // 3628800
100 + (1 to 10).product
(1 to 10).product == 3628800 // true
// Darle un nombre al valor
val fac10 = (1 to 10).productSe evalúa a un valor, se combina con otras expresiones y se nombra con val.
Funciones matemáticas a código
Factorial: n! = 1 × 2 × … × n
Con Int: se desborda
def factorial(n: Int): Int = (1 to n).product
factorial(12) // 479001600: correcto
factorial(13) // 1932053504: incorrectoCon BigInt: correcto
def factorial(n: Int): BigInt =
(1 to n).map(BigInt(_)).product
factorial(13) // 6227020800El tipo tiene límites
Int llega hasta ~2 100 millones. 13! no cabe y Scala no avisa.
Funciones matemáticas a código
Sumatoria: se lee igual que la fórmula
def g(n: Int): Int =
(1 to n).map(k => k * k).sum
g(3) // 1 + 4 + 9 = 14| En la fórmula | En el código |
|---|---|
| «k de 1 a n» | (1 to n) |
| «k al cuadrado» | .map(k => k * k) |
| «súmalos» | .sum |
Funciones matemáticas a código
Filtrar, transformar, sumar
Suma los cuadrados de los pares, del 1 al 10.
(1 to 10).filter(k => k % 2 == 0).map(k => k * k).sum // 2201 … 10
filter
2, 4, … 10
map
4, 16, … 100
sum
220
Funciones matemáticas a código
Para repasar: notación matemática → Scala
Funciones matemáticas a código
Para repasar: notación matemática → Scala
| Matemáticas | Scala | Se lee |
|---|---|---|
| [1, 2, …, n] | (1 to n) | los números de 1 a n |
| Σ k², k = 1…n | (1 to n).map(k => k * k).sum | suma los cuadrados |
| Π k, k = 1…n | (1 to n).product | multiplícalos todos |
| ∀ k : p(k) | (1 to n).forall(p) | ¿todos cumplen p? |
| ∃ k : p(k) | (1 to n).exists(p) | ¿alguno cumple p? |
| x ↦ √(x² + 1) | x => math.sqrt(x * x + 1) | función sin nombre |
Para llevar
Cuatro ideas de esta clase
Una función pura depende solo de sus argumentos y no tiene efectos secundarios.
Transparencia referencial: una llamada pura se puede reemplazar por su valor.
Pura o impura describe el comportamiento; método o función, la forma de declarar.
Σ, Π, ∀ y ∃ se traducen a sum, product, forall y exists.
Práctica, consulta y cierre
Referencias