Funciones

Programación funcional y reactiva - Computación

Profesor: Ing. Santiago Quiñones

Docente Investigador

Departamento de Ingeniería Civil

Contenidos

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.

f(x) = x^2 + 1

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

f(x) = x^2 + 1

Scala

 

 

El tipo Int expresa el dominio

def f(x: Int): Int =
  x * x + 1

La 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

  • Clases y objetos con datos

Funcional

  • Funciones como valores
  • Prefiere val: datos que no cambian

Plataforma

  • Corre en la JVM
  • Convive con Java

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 * x

Sin nombre (lambda)

(x: Int) => x * x

Se 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 false

Má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

  • Da 5 hoy, mañana y siempre
  • No guarda nada entre usos

Impura

Alcancía

depositar(10) → ¿?

  • El total depende de lo que ya había
  • Cada uso la cambia

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 + b
  • suma(3, 4) siempre es 7
  • No lee ni modifica nada externo
def promedio(nums: List[Int]): Double =
  nums.sum.toDouble / nums.length
  • Misma lista, mismo resultado
  • List es inmutable: no la modifica

Funciones puras

Estas funciones no son puras

Depende de estado externo

var contador = 0
def incrementarContador(x: Int): Int = {
  contador = contador + x // ¡cambia!
  contador
}
  • incrementarContador(5) da 5, luego 10, luego 15…
  • Cambia una variable de afuera

Hace I/O

def leerArchivo(ruta: String): String =
  scala.io.Source
    .fromFile(ruta)
    .mkString
  • El archivo puede cambiar
  • Puede no existir
  • Depende del disco

Funciones 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, siempre

Cada 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 + 5

Cada 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

  • Una función no tiene "estado". Su única misión es transformar los valores de entrada en una salida. No existen efectos secundarios.
  • En los lenguajes de programación, un método podría modificar el estado interno de un objeto o tener efectos como imprimir en la consola o alterar el contenido de una variable externa.

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):

  1. Pide que evalúe si cada método de Counter es una función pura.
  2. Pide las principales características de las funciones puras.
  3. Pide una versión en Scala con funciones puras.
  4. Contrasta con lo visto en clase: ¿la IA acertó?

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 5

El 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

f(x) = 2x + 3
g(x) = x^2
h(x) = \sqrt{x}
(g \circ f)(x) = g(f(x))

Funciones matemáticas a código

Una fórmula es una expresión

\prod_{k=1}^{10} k
= 1 \times 2 \times 3 \times \cdots \times 10
(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).product

Se 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: incorrecto

Con BigInt: correcto

def factorial(n: Int): BigInt =
  (1 to n).map(BigInt(_)).product
factorial(13) // 6227020800

El 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

\sum_{k=1}^{n} k^2
= 1^2 + 2^2 + \cdots + n^2
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

\sum_{\substack{k=1 \\ k \text{ par}}}^{10} k^2

Suma los cuadrados de los pares, del 1 al 10.

(1 to 10).filter(k => k % 2 == 0).map(k => k * k).sum // 220

1 … 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

  • Odersky, M., Spoon, L., Venners, B. y Sommers, F. Programming in Scala (5.ª ed.). Artima.
  • Chiusano, P. y Bjarnason, R. (2014). Functional Programming in Scala. Manning.
  • Documentación oficial de Scala: docs.scala-lang.org
  • [Fuente de la tabla «Notación matemática → Scala» — completar]

B1S2 Funciones V2

By Santiago Quiñones Cuenca

B1S2 Funciones V2

  • 56