CodeGym /Kurse /Swift SELF /Tuples: Labels, Destrukturierung

Tuples: Labels, Destrukturierung

Swift SELF
Level 14, Lektion 4
Verfügbar

1. Labels in tuples fu?r bessere Lesbarkeit

Tuples sind leicht zu unterscha?tzen: Am Anfang wirken sie wie „ein Paar Werte, na gut“. Aber bei realen Aufgaben stellt sich heraus, dass tuples genau diese Jackentasche sind, in die man Schlu?ssel schnell hineinsteckt, statt fu?r sie eine separate Garage zu bauen. Unser heutiges Ziel ist es, tuples bewusst zu verwenden: fu?r Lesbarkeit (Labels), fu?r bequemes Herausziehen von Werten (Destrukturierung) und fu?r sauberes Verzweigen nach zwei Parametern (tuple-matching in switch).

Stellen Sie sich vor, wir schreiben eine kleine Konsolenanwendung (wie zuvor — eine Datei, readLine(), switch nach Befehlen). Sie soll eine Mini-Liste von Bu?chern speichern: Kennung und Titel. Keine struct (dazu kommen wir spa?ter im Kurs), also wird der Bucheintrag ein tuple sein.

Wenn Sie (Int, Int) sehen, stellt das Gehirn normalerweise zwei Fragen: „was fu?r zwei Integers?“ und „welcher von ihnen ist was?“. Labels in tuples sind die Art, diese Fragen direkt im Code zu beantworten: (x: Int, y: Int) liest sich wie „ein Punkt mit x und y“ und nicht wie „irgendwelche zwei Zahlen“. Klingt lustig, spart aber tatsa?chlich Zeit: weniger kognitive Schmerzen — weniger Bugs. Besonders dann, wenn die Typen gleich sind, die Bedeutung aber unterschiedlich.

Beispiel: „Buch“ als tuple mit Labels

import Foundation

let book = (id: 1, title: "1984")

print(book.id)       // 1
print(book.title)    // 1984

Hier ist die wichtige Idee: Labels ermo?glichen den Zugriff nicht u?ber book.0, sondern u?ber book.id. Das ist nicht „Scho?nheit um der Scho?nheit willen“, sondern ein direkter Beitrag zur Lesbarkeit.

Was ist wichtiger: Labels oder Positionen

Hier ist eine Nuance, die oft u?berrascht: Labels verbessern die Lesbarkeit, werden aber in den meisten Fa?llen nicht Teil der „Identita?t“ eines tuples. Wenn Sie also tuples vergleichen, geschieht der Vergleich anhand der Werte der Elemente und nicht anhand der Label-Namen. Das heißt, (x: 0, y: 0) und (0, 0) gelten als vergleichbar und als gleich in ihren Werten. Dieses Verhalten wird oft so formuliert: „labels werden nicht beru?cksichtigt“ bei Gleichheit/Vergleich.

Pru?fen wir das an einem einfachen Beispiel:

import Foundation

let a = (x: 0, y: 0)
let b = (0, 0)

print(a == b) // true

Ja, true. Labels helfen Ihnen beim Lesen des Codes, aber die „Mathematik“ eines tuples lebt meist von Positionen.

Mini-Tabelle: wie man tuple-Zugriff liest

Wie wir schreiben Wie es gelesen wird Wann es okay ist
point.0
„erstes Element“ schneller Prototyp, sehr kurzer Code
point.x
„Koordinate x“ fast immer besser, wenn es eine Bedeutung gibt
let (x, y) = point
„Punkt in x und y zerlegt“ wenn wir spa?ter viel mit beiden Teilen arbeiten

3. Destrukturierung: Werte sauber aus einem tuple holen

Mit Labels haben wir das tuple von außen klarer gemacht. Oft braucht man aber den na?chsten Schritt: Man bekommt ein tuple einmal und mo?chte dann mit seinen Teilen wie mit normalen Variablen arbeiten. Jedes Mal point.x zu schreiben ist zwar mo?glich, aber wenn Sie in einem Codeblock oft mit beiden Teilen arbeiten, ist es einfacher, das tuple „auseinanderzunehmen“. Das nennt man Destrukturierung (Entpacken).

Die einfachste Variante

import Foundation

let point = (x: 10, y: 5)

let (x, y) = point
print(x) // 10
print(y) // 5

Hier gibt es einen wichtigen Punkt: Das Entpacken erfolgt nach Positionen und nicht „nach Namen“. Die Variablen x und y sind einfach Ihre Namen links; Swift ordnet sie dem ersten und zweiten Element des tuples zu.

Ignorieren mit _

Manchmal brauchen Sie nur einen Teil des tuples: zum Beispiel speichern Sie (id, title), aber an der konkreten Stelle brauchen Sie nur title. Dann zeigt _ die Absicht: „den ersten Teil ignorieren wir bewusst“.

import Foundation

let book = (id: 7, title: "Dune")

let (_, title) = book
print(title) // Dune

Das wirkt kleinlich, aber in großem Code ist _ ein sehr nuetzlicher Hinweis darauf, dass das Weglassen absichtlich geschieht und nicht, weil „oh, ich habe die Variable vergessen“.

let vs. var beim Entpacken

Wenn Sie let (x, y) = point schreiben, dann sind x und y Konstanten. Wenn Sie etwas a?ndern wollen, verwenden Sie var.

import Foundation

let point = (x: 1, y: 2)

var (x, y) = point
x += 10
print(x) // 11

Beachten Sie: point bleibt unvera?ndert. Wir haben Werte genommen und arbeiten mit Kopien in Variablen. In Swift wird das in der Regel auch genau so verstanden: Ein tuple ist ein Wert.

4. Tuple-matching in switch mit zwei Werten

In den vorherigen Themen haben Sie bereits switch fu?r einen einzelnen Wert geschrieben: Befehl, Zahl, Zeichen, Bereich. Aber manchmal haben Sie zwei wichtige Parameter. Der typische Einsteigerweg ist ein verschachteltes if oder ein switch innerhalb eines switch, und der Code sieht dann wie eine „Matroschka aus Bedingungen“ aus. Tuple-matching lo?st das einfacher: Wir fassen zwei Werte zu einem tuple zusammen und machen darauf ein switch.

Grundidee: „zwei Parameter zu einer Pr?fung zusammenkleben“

Stellen wir uns vor, unsere Konsolenanwendung hat einen „Modus“ und einen „Befehl“. Der Modus ist vereinfacht entweder Admin oder normaler Benutzer, der Befehl ist ein String.

import Foundation

let isAdmin = true
let command = "delete"

switch (isAdmin, command) {
case (true, "delete"):
    print("Admin lo?scht den Eintrag") // Admin lo?scht den Eintrag
case (false, "delete"):
    print("Lo?schen verboten")   // wird nicht ausgefu?hrt
default:
    print("Normaler Befehl")
}

Und wieder eine wichtige Nuance: switch gleicht tuples nach Positionen ab und nicht nach Labels. Selbst wenn Sie (isAdmin: true, command: "delete") schreiben, arbeitet das Muster in case weiterhin positional.

Werte u?ber case let extrahieren

Tuple-matching wird besonders angenehm, wenn Sie nicht nur pru?fen, sondern auch einen Teil der Daten direkt im case in eine Variable ziehen.

import Foundation

let point = (x: 0, y: 5)

switch point {
case (0, 0):
    print("origin")
case (0, let y):
    print("auf der Y-Achse, y=\(y)") // auf der Y-Achse, y=5
default:
    print("irgendwo sonst")
}

Hier greift let y nur innerhalb dieses Zweigs. Außerhalb des case existiert die Variable y nicht — und das ist gut so, weil die Gefahr kleiner wird, eine Variable dorthin zu schleppen, wo sie nicht hingehört.

Verfeinerung mit where

Manchmal braucht man nicht nur „das ist die Y-Achse“, sondern „das ist der obere Teil der Y-Achse“. Dafu?r fügen wir where hinzu.

import Foundation

let point = (x: 0, y: 5)

switch point {
case (0, 0):
    print("origin")
case (0, let y) where y > 0:
    print("auf Y+, y=\(y)")     // auf Y+, y=5
case (0, let y):
    print("auf Y-, y=\(y)")
default:
    print("nicht auf der Y-Achse")
}

Ein solcher switch liest sich meist besser als ein Kaskaden-if/else, weil alle Varianten in einer Spalte stehen und nicht tief ineinander verschachtelt sind.

5. Mini-Beispiel: Befehl und Anzahl der Argumente als tuple-Schlu?ssel

Jetzt bauen wir ein kleines praktisches Szenario, das fu?r CLI-Programme sehr realistisch wirkt: Wir lesen eine Zeile, teilen sie in Tokens und wollen verstehen, welcher Befehl vorliegt und wie viele Argumente er hat. Oft machen Anfa?nger das so: „erst pruefe ich den Befehl, dann innen die Anzahl der Argumente“. Wir machen es u?ber tuple-matching: (command, argCount).

Einfacher Input-Aufbau

import Foundation

let line = readLine() ?? ""
let parts = line.split(separator: " ")
let command = parts.first.map(String.init) ?? ""
let argCount = max(0, parts.count - 1)

print(command)   // zum Beispiel: add
print(argCount)  // zum Beispiel: 1

argCount ist die „Anzahl der Argumente nach dem Befehl“. Fu?r "add Dune" ist es 1, fu?r "list" ist es 0.

switch u?ber (command, argCount)

import Foundation

let line = readLine() ?? ""
let parts = line.split(separator: " ")
let command = parts.first.map(String.init) ?? ""
let argCount = max(0, parts.count - 1)

switch (command, argCount) {
case ("list", 0):
    print("Ich zeige die Buchliste an")
case ("add", 1):
    print("Ich fu?ge ein Buch hinzu")
default:
    print("Unbekannter Befehl oder falsche Argumente")
}

Hier wirkt tuple-matching wie eine kleine Befehls-Tabelle: links steht der Befehl, rechts die erwartete Form.

Argument innerhalb des Zweigs destrukturieren

Angenommen, "add" erwartet den Buchtitel in einem Token (ja, das ist eine Einschra?nkung: Leerzeichen im Titel unterstu?tzen wir noch nicht, weil Anfu?hrungszeichen und sauberes Parsen von Befehlen deutlich spa?ter kommen). Dann ko?nnen wir das zweite Token herausziehen:

import Foundation

let line = readLine() ?? ""
let parts = line.split(separator: " ")
let command = parts.first.map(String.init) ?? ""
let argCount = max(0, parts.count - 1)

switch (command, argCount) {
case ("add", 1):
    let title = String(parts[1])
    print("Ich fu?ge hinzu: \(title)")
default:
    print("OK")
}

Beachten Sie: Wir machen keinen komplizierten Index-Akrobatik. Wir haben die Befehlsform zuvor als ("add", 1) gefiltert und greifen erst dann sicher auf parts[1] zu.

6. Nuetzliche Feinheiten

Labels beeinflussen Gleichheit und Einzigartigkeit nicht

Dieser Punkt wird in den ersten Aufgaben selten gebraucht, aber er taucht direkt beim Thema Set und Dictionary auf. Wenn ein tuple an Gleichheit/Hashing beteiligt sein kann (wenn die Elemente das unterstu?tzen), dann werden beim Vergleich und bei der Hash-Berechnung u?blicherweise die Werte der Elemente und nicht die Labels beru?cksichtigt. Das bedeutet: Labels sind fu?r Menschen da, und die „Gleichheit“ fu?r Daten. Dieses Prinzip wird direkt so formuliert: Labels werden bei ==, Vergleich und Hashable nicht beru?cksichtigt.

Mini-Demo:

import Foundation

let p1 = (x: 1, y: 2)
let p2 = (1, 2)

print(p1 == p2) // true

Und wenn man Set verwendet:

import Foundation

let points: Set<(Int, Int)> = [(1, 2), (1, 2), (3, 4)]
print(points.count) // 2

Hier ist es wichtig, den Sinn nicht in den Namen der Labels zu „kodieren“. Der Sinn steckt in den Werten.

7. Typische Fehler

Fehler Nr. 1: u?berall .0 und .1 verwenden, auch wenn die Bedeutung wichtig ist.
Solange der Code klein ist, wirkt pair.0 schnell. Aber sobald drei bis vier Verwendungsstellen auftauchen und die Typen gleich sind (Int, Int), beginnt das Gehirn durcheinanderzukommen: Wo ist die Breite, wo die Ho?he, wo x, wo y? Labels (x:..., y:...) und der Zugriff point.x senken normalerweise die Wahrscheinlichkeit deutlich, Daten zu verwechseln.

Fehler Nr. 2: glauben, dass tuple-matching in switch nach Labels abgleicht.
Es ist sehr leicht zu denken: „Wenn ein tuple x und y hat, dann matched case (x: 0, y: 0) irgendwie besonders clever.“ In der Praxis erfolgt der Abgleich positional: Das erste Element wird mit dem ersten verglichen, das zweite mit dem zweiten. Labels helfen beim Lesen, a?ndern aber die Mechanik des Abgleichs nicht.

Fehler Nr. 3: die Reihenfolge bei der Destrukturierung verwechseln.
let (x, y) = point arbeitet positionsbasiert. Wenn Sie also versehentlich die Variablen vertauschen (let (y, x) = point), wird Swift nicht „erraten“, was Sie meinten. Er legt das erste Element ehrlich in y, das zweite in x. Das ist der Fall, in dem der Compiler alles richtig gemacht hat und der Fehler in der Bedeutung liegt.

Fehler Nr. 4: _ vergessen und „u?berflu?ssige“ Variablen erzeugen.
Wenn Sie let (id, title) = book schreiben, aber id nicht verwenden, beschwert sich die IDE (und das zu Recht). Wenn ein Wert nicht gebraucht wird, zeigt _ die Absicht und macht den Code sauberer: let (_, title) = book.

Fehler Nr. 5: ein tuple in ein „Pseudobjekt“ aus fu?nf Feldern verwandeln.
Ein tuple ist gut, solange er klein ist und sich mit einem Satz leicht erkla?ren la?sst: „id + title“, „x + y“, „Befehl + Anzahl der Argumente“. Wenn es viele Elemente werden, wird das Lesen zu einer Quest. Im Rahmen der aktuellen Themen sollte man sich besser auf 2–3 Elemente beschra?nken und nicht versuchen, aus einem tuple eine „universelle Struktur fu?r alle Fa?lle“ zu machen.

1
Aufgabe
Swift SELF, Level 14, Lektion 4
Gesperrt
Raumplan
Raumplan
1
Aufgabe
Swift SELF, Level 14, Lektion 4
Gesperrt
Buchtitel
Buchtitel
1
Aufgabe
Swift SELF, Level 14, Lektion 4
Gesperrt
Punkt-Navigator
Punkt-Navigator
1
Aufgabe
Swift SELF, Level 14, Lektion 4
Gesperrt
Konsolenassistent
Konsolenassistent
1
Umfrage/Quiz
Swift-Mengen, Level 14, Lektion 4
Nicht verfügbar
Swift-Mengen
Arbeiten mit Set und Tupel
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION