2. Modelo de programación Funcional
Instituto Tecnológico de Tehuacán
Ingeniería en Sistemas Computacionales | Programación Lógica y Funcional
Tema: El Modelo de Programación Funcional
El Modelo de Programación Funcional es un paradigma declarativo basado en el concepto matemático de función y en las reglas del Cálculo Lambda (λ) desarrollado por Alonzo Church en la década de 1930. A diferencia de la programación imperativa, no maneja estados mutables ni secuencias de instrucciones de control de flujo explícitas (como bucles for o while).
Transparencia Referencial
Una expresión o función siempre produce el mismo resultado dado el mismo argumento. Puede ser reemplazada por su valor sin alterar el programa.
Inmutabilidad
Las variables son constantes. Un valor asignado nunca cambia; para «modificar» un dato se genera una nueva estructura en memoria.
Ausencia de Efectos Secundarios
Las funciones puras no alteran el estado global, no modifican variables de entrada ni interactúan con I/O fuera de su ámbito delimitado.
En lenguajes como Haskell, el sistema de tipos es estático y fuertemente tipado con un potente motor de Inferencia de Tipos (Hindley-Milner).
| Categoría | Tipo en Haskell | Descripción y Ejemplos |
|---|---|---|
| Tipos Primitivos | Int, Integer, Float, Double, Bool, Char |
Valores atómicos. Integer ofrece precisión infinita (limitada solo por la RAM). |
| Estructuras Compuestas | Listas ([a]), Tuplas ((a, b)) |
Las listas son homogéneas; las tuplas son heterogéneas y de tamaño fijo. |
| Tipos Algebraicos (ADT) | data / type |
Permite crear tipos personalizados sum/product (Ej. data Bool = False | True). |
Las funciones en programación funcional son ciudadanos de primera clase (First-class citizens) y soportan abstracciones avanzadas:
Toda función en Haskell toma técnicamente un solo argumento. Funciones con múltiples parámetros son evaluadas como una cadena de funciones anidadas de un argumento.
-- Suma expresada normalmente (Currificada por defecto en Haskell)
suma :: Int -> Int -> Int
suma x y = x + y
-- Ocurre equivalencia con: suma x = \y -> x + y
Son aquellas que reciben funciones como parámetros o devuelven funciones como resultado.
-- Uso de map (Alto orden) para duplicar una lista
duplicarLista :: [Int] -> [Int]
duplicarLista xs = map (\x -> x * 2) xs
Combina dos o más funciones mediante el operador punto (.), equivalente al operador matemático (f o g)(x) = f(g(x)).
cuadrado :: Int -> Int
cuadrado x = x * x
incrementar :: Int -> Int
incrementar x = x + 1
-- Composición: primero incrementa, luego calcula cuadrado
operacionCompuesta :: Int -> Int
operacionCompuesta = cuadrado . incrementar -- operacionCompuesta 4 = 25
Los Intervalos permiten generar secuencias aritméticas de forma concisa empleando la notación [inicio..fin] o secuencias infinitas [inicio..].
-- Ejemplos de Intervalos
numeros = [1..10] -- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pares = [2, 4..20] -- [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
abecedario = ['a'..'z'] -- Lista de caracteres
infinito = [1..] -- Lista infinita de enteros
| Operador / Función | Sintaxis | Descripción | Ejemplo y Resultado |
|---|---|---|---|
| Cons (Construcción) | : |
Agrega un elemento al inicio de una lista. | 1 : [2, 3] → [1, 2, 3] |
| Concatenación | ++ |
Une dos listas homogéneas. | [1, 2] ++ [3, 4] → [1, 2, 3, 4] |
| Indexación | !! |
Obtiene el n-ésimo elemento (base 0). | [10, 20, 30] !! 1 → 20 |
| Cabeza / Cola | head / tail |
head obtiene el 1er elemento; tail el resto. |
head [1,2,3] → 1 |
Las listas son la estructura de datos recursiva fundamental en programación funcional. Una herramienta de alta potencia son las Listas por Comprensión (basadas en la notación matemática de conjuntos).
Sintaxis: [ expresión | generador, filtro1, filtro2 ]
Lee: «Construir la lista con la expresión para cada valor del generador que cumpla las condiciones de los filtros«.
-- Ejemplo 1: Cuadrados de números pares entre 1 y 20
cuadradosPares = [x^2 | x <- [1..20], x `mod` 2 == 0]
-- Ejemplo 2: Algoritmo Quicksort expresado concisamente con List Comprehension
quicksort :: Ord a => [a] -> [a]
quicksort [] = []
quicksort (pivote:resto) =
quicksort [x | x <- resto, x <= pivote]
++ [pivote]
++ quicksort [x | x <- resto, x > pivote]
En el paradigma funcional, estructuras avanzadas como los Árboles Binarios se modelan mediante Tipos de Datos Algebraicos Recursivos.
-- Definición de un Árbol Binario de Búsqueda (BST)
data Arbol a = Vacio
| Nodo a (Arbol a) (Arbol a)
deriving (Show, Eq)
-- Función para insertar un elemento en el árbol
insertar :: Ord a => a -> Arbol a -> Arbol a
insertar x Vacio = Nodo x Vacio Vacio
insertar x (Nodo val izq der)
| x == val = Nodo val izq der
| x < val = Nodo val (insertar x izq) der
| otherwise = Nodo val izq (insertar x der)
La Evaluación Perezosa (Lazy Evaluation) o reducción mediante Call-by-Need retrasa el cálculo de una expresión hasta que su resultado es estrictamente indispensable para el progreso del programa.
Supongamos la función doble x = x + x y la expresión take 2 (map doble [1..]):
Paso 1: Petición Externa
take 2 requiere únicamente los primeros 2 elementos de la lista infinita.
Paso 2: Generación Demanda
El intervalo [1..] genera solo el valor 1. Se aplica doble 1 → 2.
Paso 3: Segundo Elemento
El intervalo genera 2. Se aplica doble 2 → 4.
Paso 4: Detención
take 2 se satisface con [2, 4]. El resto de la lista infinita NUNCA se evalúa en RAM.
sumaCuadradosImpares :: Int -> Int
sumaCuadradosImpares n = sum [x^2 | x <- [1..n], odd x]
-- Ejemplo de uso: sumaCuadradosImpares 5 -> 1^2 + 3^2 + 5^2 = 35
sumarArbol que calcule la suma total de los valores de un árbol de enteros.
sumarArbol :: Arbol Int -> Int
sumarArbol Vacio = 0
sumarArbol (Nodo v izq der) = v + sumarArbol izq + sumarArbol der
zipWith.
fibs :: [Integer]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
-- Para obtener los primeros 10: take 10 fibs
Responde el siguiente cuestionario para evaluar tu comprensión sobre la materia: