Do you want to pick up from where you left of?
Take me there

Estruturas de Controle

Nesta lição iremos conhecer algumas estruturas de controle disponíveis em Elixir.

Table of Contents

if e unless

Existem chances de que você já tenha encontrado if/2 antes, e caso você tenha utilizado Ruby você é familiarizado com unless/2. Em Elixir eles trabalham praticamente da mesma forma porém são definidos como macros, não construtores da linguagem; Você pode encontrar a implementação deles em Kernel module.

Pode-se notar que em Elixir, os únicos valores falsos são nil e o booleano false.

iex> if String.valid?("Hello") do
...>   "Valid string!"
...> else
...>   "Invalid string."
...> end
"Valid string!"

iex> if "a string value" do
...>   "Truthy"
...> end
"Truthy"

Usar unless/2 é bem parecido com o uso do if/2 porém trabalhando de forma negativa:

iex> unless is_integer("hello") do
...>   "Not an Int"
...> end
"Not an Int"

case

Caso seja necessário combinar múltiplos padrões nós poderemos utilizar case/2:

iex> case {:ok, "Hello World"} do
...>   {:ok, result} -> result
...>   {:error} -> "Uh oh!"
...>   _ -> "Catch all"
...> end
"Hello World"

A variável _ é uma importante inclusão na declaração do case/2. Sem isso a falha em procura de combinação iria causar um erro:

iex> case :even do
...>   :odd -> "Odd"
...> end
** (CaseClauseError) no case clause matching: :even

iex> case :even do
...>   :odd -> "Odd"
...>   _ -> "Not Odd"
...> end
"Not Odd"

Considere _ como o else que irá igualar com “todo o resto”. Já que case/2 depende de combinação de padrões, todas as mesmas regras e restrições são aplicadas. Se você pretende procurar padrões em variáveis que já existem, você irá precisar utilizar o operador pin ^/1:

iex> pie = 3.14
3.14
iex> case "cherry pie" do
...>   ^pie -> "Not so tasty"
...>   pie -> "I bet #{pie} is tasty"
...> end
"I bet cherry pie is tasty"

Outra característica interessante do case/2 é o seu suporte para cláusulas de guarda:

Este exemplo vem diretamente do Guia Introdutório oficial do Elixir.

iex> case {1, 2, 3} do
...>   {1, x, 3} when x > 0 ->
...>     "Will match"
...>   _ ->
...>     "Won't match"
...> end
"Will match"

Verifique a documentação oficial sobre Expressões permitidas em cláusulas guard.

cond

Quando necessitamos associar condições, e não valores, nós podemos recorrer ao cond/1; Isso é semelhante ao else if ou elsif de outras linguagens:

Este exemplo vem diretamente do Guia Introdutório oficial do Elixir.

iex> cond do
...>   2 + 2 == 5 ->
...>     "This will not be true"
...>   2 * 2 == 3 ->
...>     "Nor this"
...>   1 + 1 == 2 ->
...>     "But this will"
...> end
"But this will"

Como case/2, cond/1 irá gerar um erro caso não seja achado associação. Para lidar com isso, nós podemos definir a condição para true:

iex> cond do
...>   7 + 1 == 0 -> "Incorrect"
...>   true -> "Catch all"
...> end
"Catch all"

with

A forma especial with/1 é útil quando tentamos usar case/2 de maneira aninhada ou em situações que não é possível encadear funções. A expressão with/1 é composta de palavras-chaves, generators e finalmente uma expressão.

Iremos discutir generators na lição sobre list comprehensions para comparar o lado direito do operador <- com o lado esquerdo.

Vamos começar com um exemplo simples de with/1 e então vamos olhar em algo mais:

iex> user = %{first: "Sean", last: "Callan"}
%{first: "Sean", last: "Callan"}
iex> with {:ok, first} <- Map.fetch(user, :first),
...>      {:ok, last} <- Map.fetch(user, :last),
...>      do: last <> ", " <> first
"Callan, Sean"

Quando uma expressão falha em achar um padrão, o valor da expressão que falhou será retornado:

iex> user = %{first: "doomspork"}
%{first: "doomspork"}
iex> with {:ok, first} <- Map.fetch(user, :first),
...>      {:ok, last} <- Map.fetch(user, :last),
...>      do: last <> ", " <> first
:error

Agora vamos olhar um exemplo maior sem with/1 e então ver como nós podemos refatorar:

case Repo.insert(changeset) do
  {:ok, user} ->
    case Guardian.encode_and_sign(user, :token, claims) do
      {:ok, token, full_claims} ->
        important_stuff(token, full_claims)

      error ->
        error
    end

  error ->
    error
end

Quando utilizamos with/1 acabamos com um código que é facilmente entendido e possui menos linhas:

with {:ok, user} <- Repo.insert(changeset),
     {:ok, token, full_claims} <- Guardian.encode_and_sign(user, :token, claims) do
  important_stuff(token, full_claims)
end

A partir do Elixir 1.3, with/1 suporta else:

import Integer

m = %{a: 1, c: 3}

a =
  with {:ok, number} <- Map.fetch(m, :a),
    true <- is_even(number) do
      IO.puts "#{number} divided by 2 is #{div(number, 2)}"
      :even
  else
    :error ->
      IO.puts("We don't have this item in map")
      :error

    _ ->
      IO.puts("It is odd")
      :odd
  end

Isso ajuda a lidar com erros provendo padrões parecido com o case. O valor passado para o else é o primeiro que não foi correspondido com o padrão em uma expressão.

Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!