Y ya vamos por el quinto asalto, el quinto de la serie sobre el aprendizaje del lenguaje Elixir. En este asalto aprenderemos estructuras de control de flujo, esenciales en cualquier lenguaje de programación. No sé en otros lenguajes funcionales, pero en Elixir, estas estructuras no son muy bien acogidas. De todas formas, son parte del lenguaje, son sencillas y nos servirán para establecer conexiones con lenguajes que ya conozcamos.
Para seguir con el aprendizaje, sigo con el método seguido en otros asaltos partiendo del post aprendiendo Elixir:
- Aprender lo suficiente para comenzar
- Experimentar, jugar, buscar puntos desconocidos, hacerse preguntas
- Aprender lo suficiente para hacer algo de utilidad
- Enseñar lo aprendido
Aprender lo suficiente para comenzar
En Elixir no se usan mucho, se suelen escribir métodos pequeños, que junto con claúsulas de guarda y pattern matching nos alejan bastante de lo que aquí estudiaremos. Normalmente, se suelen favorecer esos mecanismos frente a estructuras de control. Al principio cuesta acostumbrarse, pero luego uno se va dando cuenta de que los cuerpos de los métodos quedan más pequeños y más enfocados a hacer una sola cosa, aunque despista un poco que una misma función tenga varios cuerpos.
if
y unless
# Ambos toman dos parámetros, una condición y una *keyword list*, cuyas claves posibles son `do:` y `else:`.
if 1 == 2, do: "truthy", else: "falsy"
unless 2 == 1, do: "do not execute", else: "execute this"
# igual que las funciones, se puede acortar un poco
if 1 == 2 do
"truthy"
else
"falsy"
end
cond
En realidad es una macro, como muchas otras construciones del lenguaje, y
acepta una serie de condiciones. Se ejecutará el código de la primera condición
que se evalúe a true
.
# Resolver la kata FizzBuzz
cond do
rem(current, 3) == 0 and rem(current, 5) == 0 -> "FizzBuzz"
rem(current, 5) == 0 -> "Buzz"
rem(current, 3) == 0 -> "Fizz"
true -> current
end
En muchos casos, una mejor alternativa puede ser utilizar múltiples funciones,
pattern matching y claúsulas de guarda en lugar del bloque cond
.
case
case
permite evaluar una serie de patrones, y ejecuta el código asociado a
dicho patrón. También se pueden usar claúsulas de guarda.
# para controlar errores al abrir un fichero
case File.open("some file.txt") do
{ :ok, file } -> IO.puts "First line: #{IO.read(file, :line)}"
{ :error, reason } -> IO.puts "Failed to open file: #{reason}"
end
# con claúsulas de guarda
dave = %{name: "Dave", age: 27}
case dave do
person = %{age: age} when is_number(age) and age >= 21 -> IO.puts "You are allowed #{person.name}"
_ -> IO.puts "You are not allowed"
end
Excepciones
Las excepciones en Elixir se usan para casos excepcionales. Por ejemplo, si hay un fallo al leer un fichero de configuración, con un nombre fijo. Pero no si hay un error al leer un fichero que el usuario ha introducido el nombre, podemos controlar eso, y no sería un error excepcional.
# lanzando una RuntimeError
raise "Giving up"
# o con algunos argumentos
raise RuntimeError, message: "Stack overflow"
# por convención, se suele escribir `!` al final de una llamada que puede
# devolver una excepción bien conocida, por ejemplo
{ ok: file } = File.open!("foo.bar")
Aprender lo suficiente para hacer algo de utilidad
-
implementación, que termina con
!
, la cual, si el resultado no coincide con{ :ok, data }
lanza una excepción. Implementa una funciónok!
que haga exactamente esto
Enseñar lo aprendido, y repetir desde el paso 7
Aquí está, este post, mis notas, mis pensamientos, mis dudas y mi código. Hasta el siguiente asalto.