Funktionen
In Elixir und vielen anderen funktionalen Sprachen sind Funktionen “Bürger erster Klasse”. Wir werden mehr über die Typen von Funktionen in Elixir lernen, was sie unterscheidet und wie man sie benutzt.
Anonyme Funktionen
Wie der Name impliziert hat eine anonyme Funktion keinen Namen. Wie wir bereits in der Enum
-Lektion gelernt haben, werden diese häufig an andere Funktionen übergeben. Um eine anonyme Funktion in Elixir zu definieren werden wir die keywords fn
und end
verwenden. Innerhalb dieser 2 keywords können wir eine beliebige Anzahl an Parametern und Funktionskörper getrennt durch ->
definieren.
Lass uns ein einfaches Beispiel anschauen:
iex> sum = fn (a, b) -> a + b end
iex> sum.(2, 3)
5
Die Abkürzung &
Anonyme Funktionen in Elixir zu benutzen ist so üblich, dass es dafür eine Abkürzung gibt:
iex> sum = &(&1 + &2)
iex> sum.(2, 3)
5
Wie du bereits wahrscheinlich erraten hast, sind in der verkürzten Version unsere Parameter mit &1
, &2
, &3
und so weiter verfügbar.
Pattern Matching
Pattern matching ist nicht nur auf Variablen in Elixir beschränkt, es kann genauso gut auf Funktionssignaturen angewandt werden, wie wir in dieser Sektion sehen können.
Elixir nutzt pattern matching, um den Satz an passenden Parametern zu finden und den entsprechenden Funktionskörper aufzurufen:
iex> handle_result = fn
...> {:ok, result} -> IO.puts "Handling result..."
...> {:error} -> IO.puts "An error has occurred!"
...> end
iex> some_result = 1
1
iex> handle_result.({:ok, some_result})
Handling result...
:ok
iex> handle_result.({:error})
An error has occurred!
Benannte Funktionen
Wir können Funktionen mit Namen definieren, so dass wir später einfacher auf sie zugreifen können. Benannte Funktionen werden innerhalb eines Moduls mit dem keyword def
definiert. Wir werden mehr über Module in der nächsten Lektion lernen, momentan reicht es, wenn wir uns auf benannte Funktionen allein konzentrieren.
Funktionen, die innerhalb eines Modules definiert wurden, sind auch für andere Module nutzbar. Das ist ein besonders nützlicher Baustein in Elixir:
defmodule Greeter do
def hello(name) do
"Hello, " <> name
end
end
iex > Greeter.hello("Sean")
"Hello, Sean"
Falls unsere Funktion nur eine Zeile lang ist, können wir sie mit do:
verkürzen:
defmodule Greeter do
def hello(name), do: "Hello, " <> name
end
Mit unserem Wissen über pattern matching bewaffnet, lass uns mit Hilfe von benannten Funktionen Rekursion weiter erkunden:
defmodule Length do
def of([]), do: 0
def of([_ | tail]), do: 1 + of(tail)
end
iex> Length.of []
0
iex> Length.of [1, 2, 3]
3
Benennung von Funktionen und Arity
Wir haben früher erwähnt, dass Funktionen benannt werden mit der Kombination aus vergebenem Namen und der arity (Anzahl Argumente). Das bedeutet du kannst Folgendes tun:
defmodule Greeter2 do
def hello(), do: "Hello, anonymous person!" # hello/0
def hello(name), do: "Hello, " <> name # hello/1
def hello(name1, name2), do: "Hello, #{name1} and #{name2}"
# hello/2
end
iex> Greeter2.hello()
"Hello, anonymous person!"
iex> Greeter2.hello("Fred")
"Hello, Fred"
iex> Greeter2.hello("Fred", "Jane")
"Hello, Fred and Jane"
Wir haben oben die Funktionsnamen in Kommentaren angegeben. Die erste Implementierung nimmt keine Argumente entgegen, also ist sie bekannt als hello/0
; die zweite nimmt ein Argument und ist somit hello/1
und so weiter. Anders wie überladene Funktionen aus anderen Sprachen kann man sich diese als verschiedene Funktionen vorstellen. Pattern matching, wie vorhin erklärt, trifft nur zu, wenn mehrere Definitionen für Funktionsdefinitionen gegeben sind, die alle die gleiche Anzahl Argumente besitzen.
Private Funktionen
Wenn wir nicht wollen, dass andere Module auf Funktionen zugreifen, können wir diese Funktionen als privat definieren. Private Funktionen können nur innerhalb ihres Moduls aufgerufen werden. Wir definieren sie in Elixir mit defp
:
defmodule Greeter do
def hello(name), do: phrase <> name
defp phrase, do: "Hello, "
end
iex> Greeter.hello("Sean")
"Hello, Sean"
iex> Greeter.phrase
** (UndefinedFunctionError) function Greeter.phrase/0 is undefined or private
Greeter.phrase()
Guards
Wir haben guards kurz im Kapitel Kontrollstrukturen angeschnitten, jetzt werden wir sehen wie man sie auf benannte Funktionen anwenden kann. Wenn Elixir einmal eine Funktion gematched hat, werden alle vorhandenen guards überprüft.
Im folgenden Beispiel haben wir zwei Funktionen mit der selben Signatur, aber die guards entscheiden anhand der verwendeten Argumenttypen, welche Funktion aufgerufen wird.
defmodule Greeter do
def hello(names) when is_list(names) do
names = Enum.join(names, ", ")
hello(names)
end
def hello(name) when is_binary(name) do
phrase() <> name
end
defp phrase, do: "Hello, "
end
iex> Greeter.hello(["Sean", "Steve"])
"Hello, Sean, Steve"
Defaultargumente
Falls wir Defaultargumente für Werte verwenden wollen, benutzen wir die Argument \\ Wert
-Syntax:
defmodule Greeter do
def hello(name, language_code \\ "en") do
phrase(language_code) <> name
end
defp phrase("en"), do: "Hello, "
defp phrase("es"), do: "Hola, "
end
iex> Greeter.hello("Sean", "en")
"Hello, Sean"
iex> Greeter.hello("Sean")
"Hello, Sean"
iex> Greeter.hello("Sean", "es")
"Hola, Sean"
Wenn wir unser Beispiel mit guards mit Defaultargumenten kombinieren, rennen wir in ein Problem. Lass uns sehen, wie sich das auswirken kann:
defmodule Greeter do
def hello(names, language_code \\ "en") when is_list(names) do
names = Enum.join(names, ", ")
hello(names, language_code)
end
def hello(name, language_code \\ "en") when is_binary(name) do
phrase(language_code) <> name
end
defp phrase("en"), do: "Hello, "
defp phrase("es"), do: "Hola, "
end
** (CompileError) iex:31: definitions with multiple clauses and default values require a header. Instead of:
def foo(:first_clause, b \\ :default) do ... end
def foo(:second_clause, b) do ... end
one should write:
def foo(a, b \\ :default)
def foo(:first_clause, b) do ... end
def foo(:second_clause, b) do ... end
def hello/2 has multiple clauses and defines defaults in one or more clauses
iex:31: (module)
Elixir kann Defaultargumente in mehreren matchenden Funktionen nicht auseinander halten. Um dieses Problem zu lösen können wir einen Funktionskopf mit Defaultargumenten hinzufügen:
defmodule Greeter do
def hello(names, language_code \\ "en")
def hello(names, language_code) when is_list(names) do
names = Enum.join(names, ", ")
hello(names, language_code)
end
def hello(name, language_code) when is_binary(name) do
phrase(language_code) <> name
end
defp phrase("en"), do: "Hello, "
defp phrase("es"), do: "Hola, "
end
iex> Greeter.hello ["Sean", "Steve"]
"Hello, Sean, Steve"
iex> Greeter.hello ["Sean", "Steve"], "es"
"Hola, Sean, Steve"
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!