Fork me on GitHub

Kontrollstrukturen

In dieser Lektion werden wir schauen welche Kontrollstrukturen in Elixir vorhanden sind.

Inhaltsverzeichnis

if und unless

Die Chancen stehen gut, dass du bereits über if/2 gestolpert bist und falls du mit Ruby vertraut bist, kennst du auch schon unless/2. In Elixir arbeiten diese beiden Konstrukte ähnlich, sind aber als Makros definiert und nicht wie in Ruby als Sprachkonstrukte. Du kannst ihre Implementierung in der Dokumentation des Kernel Moduls nachschlagen.

Es sollte darauf geachtet werden, dass in Elixir die einzigen “falsey” Werte nil und der Boolesche Wert false sind.

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

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

Die Benutzung von unless/2 ist wie if/2, arbeitet jedoch mit negativen Werten:

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

case

Falls es notwendig ist mehrere patterns zu matchen können wir case benutzen:

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

Die _ Variable ist ein wichtiger Teil in case statements. Wird es im match nicht gefunden, so tritt ein Fehler auf:

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

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

Betrachte _ wie else, dass auf “alles andere” zutrifft.

Da case auf Pattern Matching basiert, gelten auch dieselben Regeln und Beschränkungen. Falls du vorhast gegen mehrere bereits existierende Variablen zu matchen, so musst du den Pin-Operator ^ benutzen:

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"

Ein weiteres nettes Feature von case ist die Unterstützung von guard clauses:

Dieses Beispiel kommt direkt aus dem offiziellen Elixir Getting Started Guide.

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

Schau in die offizielle Dokumentation für Expressions allowed in guard clauses, um mehr über guard clauses zu erfahren.

cond

Wenn man statt Werten conditions matchen muss benutzt man cond; es ist ähnlich else if oder elsif, bekannt aus anderen Sprachen:

Dieses Beispiel kommt direkt aus dem offiziellen Elixir Getting Started Guide.

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"

Wie case wirft cond einen Fehler sollte ein match nicht gefunden werden. Um das zu verhindern, können wir eine Bedingung auf true definieren:

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

with

Die Spezialform with ist nützlich wenn du ein genestetes case statement benutzen würdest oder in Situationen, die nicht so einfach gepiped werden können. Der Ausdruck with setzt sich aus keyword, generators und einem Ausdruck zusammen.

Wir werden generators später in der Lektion “List Comprehension” besprechen, aber momentan müssen wir nur wissen, dass sie pattern matching benutzen, um die rechte Seite von <- mit der linken zu vergleichen.

Wir starten mit einem einfachen Beispiel von with und werden dann einen spezielleren Fall anschauen:

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"

Im Fall, dass ein Ausdruck keinen match hat, wird der nicht matchende Wert zurück gegeben:

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

Lass uns nun ein längeres Beispiel ohne with anschauen und dann sehen, wie wir es refactoren können:

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

Wenn wir with einführen, kommen wir zu Code, der einfach zu verstehen ist und aus weniger Zeilen Code besteht:

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

Share This Page