Tracks
/
Crystal
Crystal
/
Lehrplan
/
Procs und Blocks
Pr

Procs und Blocks in Crystal

1 Übung

Über Procs und Blocks

Ein Block ist ein Stück Code, das an eine Methode übergeben werden kann. In anderen Programmiersprachen nennt man das eine anonyme Funktion oder Lambda. Eine Methode kann einen Block in einen Proc umwandeln, der ein Stück Code repräsentiert.

def my_method(&block)
  1 + yield
end

my_method { 2 }
# => 3

Blöcke und Procs werden in Crystal sehr häufig verwendet, zum Beispiel für Iteration und Callbacks. Aber auch, um DSLs (domänenspezifische Sprachen) zu erstellen. Beispiele für DSLs sind das Web-Framework Kemal oder das Test-Framework Spec (das verwendet wird, um deine Crystal-Lösungen zu testen).

Blöcke

Um eine Methode zu erstellen, die einen Block akzeptieren kann, musst du ein Ampersand (&) vor das letzte Argument setzen. Dadurch wird der Block in einen Proc umgewandelt und dem Argument zugewiesen. Mit yield führst du dann den Block aus, der der Methode übergeben wurde.

def my_method(&block)
  1 + yield
end

Es gibt zwei Möglichkeiten, einen Block zu übergeben: entweder mit den Schlüsselwörtern do ... end oder mit geschweiften Klammern ({}). Der Hauptunterschied zwischen beiden ist der Vorrang: do ... end unterstützt mehrzeilige Blöcke, geschweifte Klammern ({}) hingegen nicht.

my_method do
  2
end
# => 3

my_method { 2 }
# => 3

yield

yield ist ein Schlüsselwort, das den Block ausführt, der der Methode übergeben wurde. Das bedeutet, dass der Block niemals ausgeführt wird, wenn yield nicht aufgerufen wird. Ihm können auch Argumente übergeben werden, die dann an den Block weitergegeben werden. Es gibt keine Begrenzung für die Anzahl der Argumente, die an den Block übergeben werden können. Um die Argumente im Block zu verwenden, benutzt du die Syntax |x, y, ...|. Dabei sind x, y, ... die Namen der Argumente.

def my_method(&block)
  1 + yield 2
end

# With curly braces:

my_method { |x| x * 3 }
# => 7

# With do ... end:

my_method do |x|
  x * 3
end
# => 7

Im obigen Beispiel erhält der Block das Argument 2, multipliziert es mit 3 und gibt 6 zurück. Dann addiert die Methode 1 zum Ergebnis und gibt 7 zurück.

Wenn die Anzahl der an yield übergebenen Argumente größer ist als die Anzahl der Argumente, die der Block erwartet, wird ein Fehler ausgelöst.

def my_method(&block)
  1 + yield
end

my_method { |x| x * 3 }
# Error: too many block parameters (given 1, expected maximum 0)

Typen von Blöcken

Blöcke können explizit oder implizit geschrieben werden. Implizite Blöcke sind am häufigsten und die, die in den obigen Beispielen gezeigt werden. Du kannst dem Block sogar verschiedene Argumenttypen geben, und der Compiler leitet die Typen ab und erstellt einen Union-Typ.

def my_method(&block)
  yield "a"
  yield 1
end

my_method do |x|
  p typeof(x)
end

# output: (String | Int32)

Wenn du explizite Blöcke schreibst, kannst du die Typen der Argumente und den Rückgabetyp angeben. Das geschieht mit der Syntax Arg1Type, Arg2Type, ... -> ResultType. Dabei werden die Argumente durch Kommas (,) getrennt, und der Ergebnistyp wird mit -> abgetrennt.

def my_method(&block : Int32, Int32 -> Int32)
  1 + yield(2, 3)
end

my_method { |x, y| x * y }
# => 7

Wie bei anderen expliziten Typen erzwingt der Compiler auch hier, dass die Typen den Erwartungen entsprechen. Wenn der Block nicht dem Typ entspricht, der für die Methode angegeben wurde, wird ein Fehler ausgelöst.

Kurzschreibweise

Es gibt eine Kurzschreibweise für Blöcke, die nur ein Argument entgegennehmen und darauf nur eine Methode aufrufen. Statt |x| kannst du & vor den Argumentnamen setzen.

def my_method(&block)
  yield "a"
end

# This is: 

my_method { |x| x.upcase }
# => "A"

# Is the same as:

my_method &.upcase
# => "A"

Procs

Proc hat eine sehr starke Verbindung zu Blöcken. Der entscheidende Unterschied ist, dass ein Proc ein Typ ist und ein Block nicht. Ein Proc repräsentiert einen Funktionszeiger und kann als Variable weitergegeben werden.

Um einen Proc zu definieren, verwendest du die Syntax des Proc-Literals ->(args) { ... }. Die Argumente werden durch Kommas (,) getrennt, und der Block wird durch { ... } eingeschlossen. Um einen Proc aufzurufen, verwendest du die Methode call und übergibst ihr die Argumente. Die Typen der Parameter sind obligatorisch. Der Rückgabetyp wird aus dem Rumpf des Procs abgeleitet, kann aber auch explizit angegeben werden.

my_proc = ->(x : Int32, y : Int32) : Int32 { x * y }
my_proc.call(2, 3)
# => 6

Blöcke erfassen

Blöcke erfassen ist eine Möglichkeit, einen Proc aus einem Block zu erstellen. Dazu verwendest du den Operator & vor dem letzten Argument einer Methode. Dadurch wird der Block in einen Proc umgewandelt und dem Argument zugewiesen.

def my_method(&block)
  block.call
end

my_method { 1 + 2 }
# => 3

Procs an Methoden übergeben

Procs können an Methoden übergeben werden, die einen Block erwarten. Das kann praktisch sein, wenn du je nach Bedingung unterschiedliche Blöcke übergeben möchtest.

def my_method(&block)
  yield 5
end

my_proc = -> (x) { x * 2 }
my_method(&my_proc)
# => 10

Den Typ von Variablen deklarieren

Wenn du eine Variable deklarierst, kannst du mit der Syntax : Type den Typ der Variable angeben. Für Procs verwendest du die Syntax Proc((ArgType, ...), ResultType). Der letzte Typ ist immer der Rückgabetyp, und die übrigen sind die Argumenttypen. Das bedeutet: Wenn es nur einen Typ gibt, ist dieser der Rückgabetyp, und der Proc nimmt keine Argumente entgegen.

my_proc : Proc(Int32, Int32, Int32) = ->(x, y) { x * y }
other_proc : Proc(Int32) = -> { 1 }

Procs aus Methoden deklarieren

Du kannst auch einen Proc aus einer Methode deklarieren.

def my_method(a, b)
  a + b
end

my_proc = ->my_method(Int32, Int32)
my_proc.call(2, 3)
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab

Lerne Procs und Blocks

Das Üben ist gesperrt

Schalte 1 weitere Übung frei, um Procs und Blocks zu üben