Las tuplas :ok, :error

Al hilo de lo del pattern matching y el destructuring de tuplas, en las funciones no puras de Elixir que pueden provocar errores, un patrón muy común es envolver los retornos en algún tipo de tupla que permita devolver, no sólo el retorno de la función, sino también si ha salido bien o mal.

El módulo File en Elixir nos abre la puerta para trabajar con archivos de manera directa, permitiéndonos abrir, leer, escribir, borrar, copiar y crear directorios. Sin embargo, estas operaciones no son funciones puras en el sentido estricto de la programación funcional, porque pueden fallar dependiendo del estado del sistema de archivos o del entorno en el que se ejecuten.

En la programación funcional, estamos acostumbrados a que las funciones sean puras: para las mismas entradas, siempre obtenemos las mismas salidas. Por ejemplo, si llamamos muchas veces a 3 + 4, siempre obtendremos 7. Lo mismo ocurre con funciones como String.upcase("hola"), que siempre devolverá "HOLA". Pero cuando interactuamos con el mundo real, como leer archivos o acceder a recursos externos, esta pureza se rompe. Un archivo puede existir o no, una conexión a internet puede fallar, y por tanto, nuestras funciones pueden devolver resultados diferentes o incluso errores.

Para ilustrar esto, podemos usar la función File.exists?, que nos devuelve true si el archivo existe y false si no. Por ejemplo, si tenemos un archivo llamado existo.txt con contenido, File.exists?("existo.txt") devolverá true. Pero si consultamos por un archivo inexistente, devolverá false. Aunque esta función devuelve un booleano, no es pura porque su resultado depende del estado externo del sistema de archivos.

Cuando queremos leer un archivo, la función File.read es especialmente interesante porque no devuelve simplemente el contenido o un error, sino que devuelve una tupla con dos elementos. El primer elemento es un átomo que indica si la operación fue exitosa (:ok) o si hubo un error (:error). El segundo elemento es el contenido del archivo en caso de éxito, o un átomo que describe el error en caso contrario, como :enoent para indicar que el archivo no existe.

Esto nos permite usar pattern matching para manejar ambos casos de forma clara. Por ejemplo, si hacemos:

{:ok, contenido} = File.read("existo.txt")

estamos diciendo que esperamos que la función devuelva una tupla cuyo primer elemento sea :ok y cuyo segundo elemento lo asignamos a la variable contenido. Si el archivo no existe y la función devuelve {:error, :enoent}, este pattern matching fallará y se lanzará un MatchError.

Por eso, antes de hacer un pattern matching directo con :ok, es recomendable comprobar que el archivo existe con File.exists?. De lo contrario, podemos manejar la respuesta con un condicional o un case para tratar ambos escenarios:

case File.read("existo.txt") do
  {:ok, contenido} ->
    IO.puts("Contenido del archivo: #{contenido}")
  {:error, :enoent} ->
    IO.puts("El archivo no existe")
  {:error, otro_error} ->
    IO.puts("Error al leer el archivo: #{otro_error}")
end

Este enfoque nos permite capturar los posibles errores y actuar en consecuencia, evitando que el programa falle inesperadamente.

Además, es importante entender cuándo puede fallar un pattern matching. Por ejemplo, si intentamos hacer:

{:ok, texto} = File.read("archivo_inexistente.txt")

y la función devuelve {:error, :enoent}, el pattern matching no se cumplirá porque :ok no coincide con :error, y se lanzará un error. Esto sucede porque estamos usando una constante (:ok) a la izquierda que no coincide con el valor real a la derecha.

En resumen, al trabajar con funciones impuras como las del módulo File, debemos estar preparados para manejar tanto los casos de éxito como los de error. El pattern matching es una herramienta poderosa para esto, pero requiere que sepamos cuándo usarlo directamente y cuándo manejar los posibles fallos con estructuras de control como case. Así, podemos escribir código más robusto y claro al interactuar con el sistema de archivos en Elixir.

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