EMZETT.
Login

Kurz: kurz = &(&1 * 2) # Capture-Syntax: &1 ist das erste Argument IO.inspect(kurz.(21)) IO.inspect(Enum.map([1, 2, 3], &(&1 + 10))) IO.inspect(Enum.map([“a”, “b”], &String.upcase/1)) # benannte Funktion als Wert

Teil des Kurses Elixir

Kapitel 3 von 8 im Kurs Elixir. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Anonyme Funktionen

quadrat = fn x -> x * x end
plus = fn a, b -> a + b end
IO.inspect(quadrat.(7))          # Aufruf mit Punkt!
IO.inspect(plus.(2, 3))
 
kurz = &(&1 * 2)                  # Capture-Syntax: &1 ist das erste Argument
IO.inspect(kurz.(21))
IO.inspect(Enum.map([1, 2, 3], &(&1 + 10)))
IO.inspect(Enum.map(["a", "b"], &String.upcase/1))   # benannte Funktion als Wert
 
beschreibe = fn
  0 -> "null"
  n when n < 0 -> "negativ"
  n when rem(n, 2) == 0 -> "gerade"
  _ -> "ungerade"
end
IO.inspect(Enum.map([0, -3, 4, 7], beschreibe))
 
faktor = 3
mal = fn x -> x * faktor end         # Closure fängt Variablen ein
faktor = 100
IO.inspect(mal.(5))

Ausgabe:

49
5
42
'\v\f\r'
["A", "B"]
["null", "negativ", "gerade", "ungerade"]
15

Module und benannte Funktionen

Benannte Funktionen stehen in Modulen. Mehrere Klauseln mit Pattern Matching ersetzen if-Ketten:

defmodule Mathe do
  @pi 3.14159                      # Modulattribut (Konstante)
 
  def kreisflaeche(r), do: @pi * r * r
 
  def fakultaet(0), do: 1
  def fakultaet(n) when n > 0, do: n * fakultaet(n - 1)
 
  def fib(n), do: fib(n, 0, 1)
  defp fib(0, a, _), do: a             # defp = privat
  defp fib(n, a, b), do: fib(n - 1, b, a + b)
 
  def gruss(name \\ "Welt", anrede \\ "Hallo") do   # Standardwerte
    "#{anrede}, #{name}!"
  end
 
  def groesse(liste) when is_list(liste), do: length(liste)
  def groesse(map) when is_map(map), do: map_size(map)
  def groesse(text) when is_binary(text), do: String.length(text)
 
  def summe([]), do: 0
  def summe([kopf | rest]), do: kopf + summe(rest)
 
  def ggt(a, 0), do: a
  def ggt(a, b), do: ggt(b, rem(a, b))
end
 
IO.inspect(Mathe.kreisflaeche(2))
IO.inspect(Mathe.fakultaet(20))
IO.inspect(Mathe.fib(50))
IO.puts(Mathe.gruss())
IO.puts(Mathe.gruss("Mia"))
IO.puts(Mathe.gruss("Tom", "Hi"))
IO.inspect({Mathe.groesse([1, 2]), Mathe.groesse(%{a: 1}), Mathe.groesse("Hä")})
IO.inspect(Mathe.summe([1, 2, 3, 4]))
IO.inspect(Mathe.ggt(48, 18))
IO.inspect(Mathe.__info__(:functions) |> Enum.sort() |> Enum.take(3))

Ausgabe:

12.56636
2432902008176640000
12586269025
Hallo, Welt!
Hallo, Mia!
Hi, Tom!
{2, 1, 2}
10
6
[fakultaet: 1, fib: 1, ggt: 2]

Funktionen werden durch Name und Stelligkeit bestimmt (fakultaet/1). Elixir hat Endrekursion-Optimierung, daher laufen auch rekursive Schleifen mit Millionen Durchläufen ohne Stapelüberlauf.

Der Pipe-Operator |>

|> reicht das Ergebnis als erstes Argument an die nächste Funktion weiter und macht Datenflüsse lesbar:

text = "  Elixir macht Spaß, Elixir ist schnell!  "
 
# Ohne Pipe (von innen nach außen lesen):
a = Enum.join(Enum.map(String.split(String.downcase(String.trim(text))), &String.capitalize/1), " ")
 
# Mit Pipe (von oben nach unten):
b =
  text
  |> String.trim()
  |> String.downcase()
  |> String.split()
  |> Enum.map(&String.capitalize/1)
  |> Enum.join(" ")
 
IO.puts(a)
IO.puts(a == b)
 
woerter =
  text
  |> String.downcase()
  |> String.replace(~r/[^\p{L}\s]/u, "")
  |> String.split()
  |> Enum.frequencies()
  |> Enum.sort_by(fn {w, n} -> {-n, w} end)
IO.inspect(woerter)
 
1..10
|> Enum.filter(&(rem(&1, 2) == 0))
|> Enum.map(&(&1 * &1))
|> Enum.sum()
|> IO.inspect()

Ausgabe:

Elixir Macht Spaß, Elixir Ist Schnell!
true
[{"elixir", 2}, {"ist", 1}, {"macht", 1}, {"schnell", 1}, {"spaß", 1}]
220

Import, alias, use und Dokumentation

defmodule Geometrie.Form do
  @moduledoc "Hilfsfunktionen für Formen."
 
  @doc "Berechnet die Fläche eines Rechtecks."
  @spec flaeche(number, number) :: number
  def flaeche(b, h), do: b * h
end
 
defmodule Demo do
  alias Geometrie.Form              # kurz: Form.flaeche
  import Enum, only: [map: 2]       # map ohne Präfix
 
  def lauf do
    IO.inspect(Form.flaeche(3, 4))
    IO.inspect(map([1, 2], &(&1 * 3)))
  end
end
 
Demo.lauf()
IO.inspect(Code.fetch_docs(Geometrie.Form) |> elem(0))
IO.inspect(function_exported?(Geometrie.Form, :flaeche, 2))

Ausgabe:

12
[3, 6]
:error
true
  • alias kürzt Modulnamen, import macht Funktionen ohne Präfix nutzbar, require lädt Makros, use bindet Verhalten ein
  • @doc/@moduledoc sind echte Dokumentation (h Modul.funktion in iex), @spec beschreibt Typen für Dialyzer

Merke

  • Anonyme Funktionen fn x -> ... end (Aufruf mit .), Capture &(&1 + 1)
  • Module mit def/defp, mehrere Klauseln mit Pattern Matching und Guards (when)
  • Standardwerte mit \\, Funktionen werden über Name/Stelligkeit identifiziert
  • Pipe |> reicht Ergebnisse als erstes Argument weiter
  • alias, import, @doc, @spec

Übungsaufgabe

Schreibe Textwerkzeug.woerter_zaehlen/1, das die Wörter eines Textes mit einer Pipe zählt.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Datentypen und Pattern Matching

Weiter: Kontrollfluss, Enum und Stream

Alle Kapitel: Elixir im Überblick