¿Qué es la programación funcional? (Como la de Elixir)

Otro vídeo introductorio destinado a explicar a personas que tengan experiencia con programación imperativa pero no con programación funcional las diferencias entre ambos paradigmas y qué debemos hacer para adaptar nuestra mente a un paradigma de programación distinto. 00:00 Introducción 01:13 ¿Cómo se estructura un programa imperativo? 03:37 ¿Cómo se estructura un programa funcional? 05:18 Ejemplo factorial: imperativo vs funcional 09:29 Ejemplo TAD Lista y operación Filtro 14:45 Funciones como elementos de primera clase 17:43 Resumen: ¿qué caracteriza a un lenguaje funcional?

La programación funcional representa un cambio radical en la forma en que concebimos la escritura de código, especialmente si venimos de un paradigma imperativo. En la programación imperativa, estamos acostumbrados a pensar en términos de algoritmos que manipulan un estado mutable. Por ejemplo, en lenguajes como C, Java o C++, nuestro programa se compone de variables que almacenan datos y de instrucciones que modifican esos datos a lo largo del tiempo. Así, el programa es una serie de transformaciones sobre un estado inicial que, tras varias modificaciones, nos lleva a un estado final del que extraemos el resultado deseado.

Este enfoque implica que constantemente estamos actualizando variables, usando asignaciones y bucles para controlar el flujo y el estado del programa. Por ejemplo, para calcular el factorial de un número en un lenguaje imperativo, normalmente usaríamos un bucle que actualiza un acumulador en cada iteración, multiplicándolo por el siguiente número en la secuencia. Este método es muy explícito en cuanto al cómo se realiza el cálculo, detallando paso a paso las operaciones necesarias.

Sin embargo, la programación funcional nos invita a cambiar la perspectiva. En lugar de centrarnos en el cómo hacer las cosas, nos enfocamos en el qué queremos obtener. No manipulamos estados ni variables mutables, sino que definimos funciones y expresiones que describen las reglas del sistema. El programa se convierte en un conjunto de definiciones matemáticas o computacionales que, dadas unas entradas, producen un resultado sin alterar un estado interno.

Un ejemplo clásico para ilustrar esta diferencia es la definición del factorial. Matemáticamente, el factorial de un número n se define recursivamente: es 1 si n es 0, y para valores mayores, es n multiplicado por el factorial de n-1. Esta definición recursiva es natural en programación funcional y refleja directamente la fórmula matemática, sin necesidad de bucles ni variables mutables.

def factorial(0), do: 1
def factorial(n), do: n * factorial(n - 1)

Aquí, la función factorial está definida en términos de sí misma, y el lenguaje se encarga de gestionar la recursión y el cálculo. No hay asignaciones ni estados que modificar, solo definiciones claras y concisas.

Otro ejemplo que muestra la diferencia entre paradigmas es la función de filtrado. En programación imperativa, para filtrar una lista y obtener solo los elementos pares, normalmente escribimos un bucle que recorre la lista, verifica cada elemento y, si cumple la condición, lo añade a una lista auxiliar. Este proceso implica modificar el estado de la lista auxiliar en cada iteración.

En cambio, en programación funcional, pensamos en la lista como una estructura recursiva compuesta por una cabeza (el primer elemento) y una cola (el resto de la lista). Filtrar una lista se define recursivamente: si la lista está vacía, el resultado es una lista vacía; si la cabeza cumple la condición, la incluimos en el resultado y filtramos recursivamente la cola; si no, simplemente filtramos la cola sin incluir la cabeza.

def filter([], _func), do: []
def filter([head | tail], func) do
  if func.(head) do
    [head | filter(tail, func)]
  else
    filter(tail, func)
  end
end

En este código, filter es una función que recibe una lista y una función func que determina si un elemento debe incluirse. La función se aplica recursivamente sin modificar ningún estado, construyendo una nueva lista resultado.

Además, la programación funcional destaca por tratar las funciones como elementos de primera clase. Esto significa que podemos pasar funciones como argumentos a otras funciones, devolverlas como resultados o asignarlas a variables, igual que haríamos con cualquier otro dato. Este enfoque facilita la creación de programas más modulares y expresivos. Por ejemplo, en Elixir, podemos usar funciones estándar como Enum.filter para filtrar listas sin tener que implementar el algoritmo nosotros mismos, simplemente pasando la función que define el criterio de filtrado.

Enum.filter([1, 2, 3, 4], fn x -> rem(x, 2) == 0 end)
# Resultado: [2, 4]

Aquí, Enum.filter recibe la lista y una función anónima que devuelve true para los números pares, filtrando la lista de forma declarativa y sin mutaciones.

En resumen, la programación funcional nos invita a pensar en términos de definiciones y expresiones, usando recursión para manejar estructuras de datos y aprovechando la capacidad de tratar funciones como ciudadanos de primera clase. Aunque al principio puede resultar un cambio de mentalidad desafiante para quienes vienen de la programación imperativa, con práctica y paciencia se convierte en una forma elegante y poderosa de resolver problemas, especialmente en lenguajes como Elixir que están diseñados para aprovechar estas características.

Lista de reproducción
  1. 1
    ¿Qué es Elixir?
    10 minutos
  2. 2
    Instalación de Elixir
    9 minutos
  3. 3
    ¿Qué es la programación funcional? (Como la de Elixir)
    20 minutos
  4. 4
    ¿Cómo funciona la REPL de Elixir?
    7 minutos
  5. 5
    ¿Cómo hacer asignaciones en Elixir?
    7 minutos
  6. 6
    Operadores aritméticos básicos
    6 minutos
  7. 7
    ¿Qué son los tipos de datos de Elixir?
    5 minutos
  8. 8
    Átomos en Elixir
    4 minutos
  9. 9
    Las palabras clave nil, true y false
    6 minutos
  10. 10
    Operadores lógicos de comparación
    8 minutos
  11. 11
    Comparación estricta con ===
    3 minutos
  12. 12
    Operadores lógicos y proposicionales
    8 minutos
  13. 13
    Invocación de funciones
    10 minutos
  14. 14
    Fundamentos de funciones
    9 minutos
  15. 15
    Cadenas de caracteres
    8 minutos
  16. 16
    Entrada y salida estandar de la mano de gets y puts
    9 minutos
  17. 17
    Concatenar e interpolar strings
    9 minutos
  18. 18
    Código fuente en archivos
    9 minutos
  19. 19
    Condicional IF y bloques DO-END
    11 minutos
  20. 20
    IFs anidados, UNLESS y COND
    12 minutos
  21. 21
    Definición de funciones
    11 minutos
  22. 22
    Fundamentos de compilación de módulos
    6 minutos
  23. 23
    Guardas
    8 minutos
  24. 24
    Funciones anónimas
    7 minutos
  25. 25
    Capturar funciones
    4 minutos
  26. 26
    Invocación de funciones dentro del mismo módulo
    7 minutos
  27. 27
    Tuplas
    8 minutos
  28. 28
    Introducción al pattern matching
    8 minutos
  29. 29
    Pattern matching en funciones
    11 minutos
  30. 30
    Las tuplas :ok, :error
    7 minutos
  31. 31
    case
    10 minutos
  32. 32
    Operador pin
    7 minutos
  33. 33
    Pattern matchings y recursividad
    5 minutos
  34. 34
    Listas
    9 minutos
  35. 35
    Operadores y funciones de lista
    10 minutos
  36. 36
    Keyword lists: listas de palabras clave
    8 minutos
  37. 37
    Mapas
    7 minutos
  38. 38
    Pattern matching de mapas y keyword lists
    6 minutos
  39. 39
    Operadores y funciones para mapas y keyword lists
    5 minutos
  40. 40
    Estructuras con defstruct
    11 minutos
  41. 41
    Bitstrings
    11 minutos
  42. 42
    Charlists
    10 minutos
  43. 43
    Funciones de alto orden en Elixir
    5 minutos
  44. 44
    Uso de la función filter
    10 minutos
  45. 45
    Uso de la función map
    7 minutos
  46. 46
    Uso de la función reduce
    9 minutos
  47. 47
    Pipelines
    11 minutos
  48. 48
    Rangos y Streams
    11 minutos
  49. 49
    Funciones recursivas con listas
    14 minutos