Schnelle und mutierende Struktur


96

Es gibt etwas, das ich nicht ganz verstehe, wenn es darum geht, Werttypen in Swift zu mutieren.

Im iBook "The Swift Programming Language" heißt es: Standardmäßig können die Eigenschaften eines Wertetyps nicht innerhalb seiner Instanzmethoden geändert werden.

Um dies zu ermöglichen, können wir Methoden mit dem mutatingSchlüsselwort in Strukturen und Aufzählungen deklarieren.

Was mir nicht ganz klar ist, ist Folgendes: Sie können eine Variable von außerhalb einer Struktur ändern, aber Sie können sie nicht von ihren eigenen Methoden aus ändern. Dies scheint mir nicht intuitiv zu sein, da Sie in objektorientierten Sprachen im Allgemeinen versuchen, Variablen zu kapseln, damit sie nur von innen geändert werden können. Bei Strukturen scheint dies umgekehrt zu sein. Um dies zu erläutern, hier ein Code-Snippet:

struct Point {
    var x = 0, y = 0
    mutating func moveToX(x: Int, andY y:Int) { //Needs to be a mutating method in order to work
        self.x = x
        self.y = y
    }
}

var p = Point(x: 1, y: 2)
p.x = 3 //Works from outside the struct!
p.moveToX(5, andY: 5) 

Kennt jemand den Grund, warum Strukturen ihren Inhalt nicht aus ihrem eigenen Kontext heraus ändern können, während der Inhalt leicht an anderer Stelle geändert werden kann?

Antworten:


80

Das Veränderbarkeitsattribut wird in einem Speicher (Konstante oder Variable) und nicht in einem Typ markiert. Sie können sich vorstellen, dass struct zwei Modi hat: veränderlich und unveränderlich . Wenn Sie einem unveränderlichen Speicher einen Strukturwert zuweisen (wir nennen ihn letoder eine Konstante in Swift), wird der Wert unveränderlich und Sie können keinen Status im Wert ändern. (einschließlich des Aufrufs einer mutierenden Methode)

Wenn der Wert einem veränderlichen Speicher zugewiesen ist (wir nennen ihn varoder eine Variable in Swift), können Sie den Status ändern, und das Aufrufen der Mutationsmethode ist zulässig.

Außerdem haben Klassen diesen unveränderlichen / veränderlichen Modus nicht. IMO, dies liegt daran, dass Klassen normalerweise verwendet werden, um referenzierbare Entitäten darzustellen . Und referenzierbare Entitäten sind normalerweise veränderbar, da es sehr schwierig ist, Referenzdiagramme von Entitäten auf unveränderliche Weise mit angemessener Leistung zu erstellen und zu verwalten. Sie können diese Funktion später hinzufügen, aber zumindest jetzt nicht.

Für Objective-C-Programmierer sind veränderbare / unveränderliche Konzepte sehr vertraut. In Objective-C hatten wir zwei getrennte Klassen für jedes Konzept, aber in Swift können Sie dies mit einer Struktur tun. Halbe Arbeit.

Für C / C ++ - Programmierer ist dies ebenfalls ein sehr bekanntes Konzept. Dies ist genau das, was constSchlüsselwörter in C / C ++ tun.

Auch der unveränderliche Wert kann sehr schön optimiert werden. Theoretisch kann der Swift-Compiler (oder LLVM) eine Kopierelision für übergebene Werte durchführen let, genau wie in C ++. Wenn Sie unveränderliche Strukturen mit Bedacht verwenden, übertrifft sie neu gezählte Klassen.

Aktualisieren

Da @Joseph behauptete, dies biete keinen Grund , füge ich etwas mehr hinzu.

Strukturen haben zwei Arten von Methoden. einfache und mutierende Methoden. Eine einfache Methode impliziert eine unveränderliche (oder nicht mutierende) Methode . Diese Trennung dient nur zur Unterstützung der unveränderlichen Semantik. Ein Objekt im unveränderlichen Modus sollte seinen Status überhaupt nicht ändern.

Dann müssen unveränderliche Methoden diese semantische Unveränderlichkeit gewährleisten . Das heißt, es sollte keinen internen Wert ändern. Der Compiler verbietet daher alle Statusänderungen von sich selbst in einer unveränderlichen Methode. Im Gegensatz dazu können Mutationsmethoden Zustände ändern.

Und dann haben Sie vielleicht eine Frage, warum unveränderlich die Standardeinstellung ist? Das liegt daran, dass es sehr schwierig ist, den zukünftigen Zustand mutierender Werte vorherzusagen, und dies wird normalerweise zur Hauptursache für Kopfschmerzen und Fehler. Viele Menschen waren sich einig, dass die Lösung veränderbare Dinge vermeidet und dann unveränderlich standardmäßig jahrzehntelang in den Sprachen der C / C ++ - Familie und ihren Ableitungen ganz oben auf der Wunschliste stand.

Weitere Informationen finden Sie unter rein funktionaler Stil . Wie auch immer, wir brauchen immer noch veränderbare Dinge, weil unveränderliche Dinge einige Schwächen haben und die Diskussion darüber nicht zum Thema zu gehören scheint.

Ich hoffe das hilft.


2
Im Grunde kann ich es so interpretieren: Eine Struktur weiß nicht im Voraus, ob sie veränderlich oder unveränderlich ist, also nimmt sie Unveränderlichkeit an, sofern nicht anders angegeben. So gesehen macht es tatsächlich Sinn. Ist das richtig?
Mike Seghers

1
@ MikeSeghers Ich denke das macht Sinn.
Eonil

3
Obwohl dies bisher die beste von zwei Antworten ist, glaube ich nicht, dass es antwortet, WARUM die Eigenschaften eines Werttyps standardmäßig nicht innerhalb seiner Instanzmethoden geändert werden können. Sie machen Hinweise, dass es ist, weil Strukturen standardmäßig unveränderlich sind, aber ich glaube nicht, dass das wahr ist
Joseph Knight

4
@JosephKnight Es ist nicht so, dass Strukturen standardmäßig unveränderlich sind. Es ist so, dass Methoden von Strukturen standardmäßig unveränderlich sind. Stellen Sie es sich wie C ++ mit der umgekehrten Annahme vor. In C ++ - Methoden, die standardmäßig nicht konstant sind, müssen Sie constder Methode explizit ein hinzufügen , wenn Sie möchten, dass sie eine 'const this' hat und für konstante Instanzen zulässig ist (normale Methoden können nicht verwendet werden, wenn die Instanz const ist). Swift macht dasselbe mit dem entgegengesetzten Standard: Eine Methode hat standardmäßig eine 'const this' und Sie markieren sie anders, wenn sie für konstante Instanzen verboten werden muss.
Analoge Datei

3
@golddove Ja, und du kannst nicht. Das solltest du nicht. Sie müssen immer eine neue Version des Werts erstellen oder ableiten, und Ihr Programm muss so konzipiert sein, dass es absichtlich auf diese Weise übernommen wird. Das Merkmal ist vorhanden, um eine solche versehentliche Mutation zu verhindern. Auch dies ist eines der Kernkonzepte der funktionalen Stilprogrammierung .
Eonil

25

Eine Struktur ist eine Aggregation von Feldern. Wenn eine bestimmte Strukturinstanz veränderbar ist, sind ihre Felder veränderbar. Wenn eine Instanz unveränderlich ist, sind ihre Felder unveränderlich. Ein Strukturtyp muss daher für die Möglichkeit vorbereitet werden, dass die Felder einer bestimmten Instanz veränderlich oder unveränderlich sein können.

Damit eine Strukturmethode die Felder der zugrunde liegenden Struktur mutieren kann, müssen diese Felder veränderbar sein. Wenn eine Methode, die Felder der zugrunde liegenden Struktur mutiert, für eine unveränderliche Struktur aufgerufen wird, würde sie versuchen, unveränderliche Felder zu mutieren. Da daraus nichts Gutes werden konnte, muss eine solche Anrufung verboten werden.

Um dies zu erreichen, unterteilt Swift Strukturmethoden in zwei Kategorien: diejenigen, die die zugrunde liegende Struktur ändern und daher möglicherweise nur für veränderbare Strukturinstanzen aufgerufen werden, und diejenigen, die die zugrunde liegende Struktur nicht ändern und daher sowohl für veränderbare als auch unveränderliche Instanzen aufrufbar sein sollten . Die letztere Verwendung ist wahrscheinlich die häufigere und daher die Standardeinstellung.

Im Vergleich dazu bietet .NET derzeit (noch!) Keine Möglichkeit, Strukturmethoden, die die Struktur ändern, von solchen zu unterscheiden, die dies nicht tun. Wenn Sie stattdessen eine Strukturmethode für eine unveränderliche Strukturinstanz aufrufen, erstellt der Compiler eine veränderbare Kopie der Strukturinstanz, lässt die Methode damit tun, was sie will, und verwirft die Kopie, wenn die Methode fertig ist. Dies hat zur Folge, dass der Compiler gezwungen wird, Zeit mit dem Kopieren der Struktur zu verschwenden, unabhängig davon, ob die Methode sie ändert oder nicht, obwohl das Hinzufügen des Kopiervorgangs fast nie semantisch inkorrekten Code in semantisch korrekten Code umwandelt. Es wird lediglich dazu führen, dass Code, der auf eine Weise semantisch falsch ist (Ändern eines "unveränderlichen" Werts), auf andere Weise falsch ist (Code kann glauben, dass er eine Struktur ändert). aber die versuchten Änderungen verwerfen). Durch das Zulassen, dass Strukturmethoden angeben, ob sie die zugrunde liegende Struktur ändern, kann die Notwendigkeit eines nutzlosen Kopiervorgangs beseitigt werden, und es wird auch sichergestellt, dass versuchte fehlerhafte Verwendung gekennzeichnet wird.


1
Der Vergleich mit .NET und einem Beispiel ohne das mutierende Schlüsselwort hat es mir klar gemacht. Die Begründung, warum das mutierende Schlüsselwort einen Nutzen bringen würde.
Binarian

19

Achtung: Laienbedingungen voraus.

Diese Erklärung ist auf der kleinsten Codeebene nicht streng korrekt. Es wurde jedoch von einem Mann überprüft, der tatsächlich an Swift arbeitet, und er sagte, es sei gut genug als grundlegende Erklärung.

Deshalb möchte ich versuchen, die Frage nach dem "Warum" einfach und direkt zu beantworten.

Um genau zu sein: Warum müssen wir Strukturfunktionen so markieren, mutatingals könnten wir Strukturparameter ändern, ohne Schlüsselwörter zu ändern?

Das große Ganze hat also viel mit der Philosophie zu tun, die Swift schnell hält .

Man könnte es sich wie das Problem vorstellen, tatsächliche physische Adressen zu verwalten. Wenn Sie Ihre Adresse ändern und viele Personen Ihre aktuelle Adresse haben, müssen Sie alle benachrichtigen, dass Sie umgezogen sind. Aber wenn niemand Ihre aktuelle Adresse hat, können Sie einfach umziehen, wohin Sie wollen, und niemand muss es wissen.

In dieser Situation ist Swift wie die Post. Wenn sich viele Menschen mit vielen Kontakten viel bewegen, hat dies einen sehr hohen Overhead. Für die Bearbeitung all dieser Benachrichtigungen muss eine große Anzahl von Mitarbeitern bezahlt werden, und der Vorgang nimmt viel Zeit und Mühe in Anspruch. Deshalb ist Swifts idealer Zustand, dass jeder in seiner Stadt so wenig Kontakte wie möglich hat. Dann braucht es kein großes Personal, um Adressänderungen zu handhaben, und alles andere kann schneller und besser erledigt werden.

Dies ist auch der Grund, warum Swift-Leute alle von Werttypen im Vergleich zu Referenztypen schwärmen. Referenztypen bilden von Natur aus überall "Kontakte", und Werttypen benötigen normalerweise nicht mehr als ein Paar. Werttypen sind "Swift" -er.

Also zurück zum kleinen Bild : structs. Strukturen sind in Swift eine große Sache, da sie die meisten Aufgaben von Objekten ausführen können, aber sie sind Werttypen.

Setzen wir die physikalische Adressanalogie fort, indem wir uns eine vorstellen misterStruct, in der wir leben someObjectVille. Die Analogie wird hier ein wenig verwirrt, aber ich denke, es ist immer noch hilfreich.

Um structalso misterStructdas Ändern einer Variablen auf a zu modellieren , haben wir beispielsweise grünes Haar und erhalten den Befehl, auf blaues Haar umzuschalten. Die Analogie wird, wie ich bereits sagte, verwirrt, aber irgendwie passiert, dass anstatt die misterStructHaare zu wechseln , die alte Person auszieht und eine neue Person mit blauen Haaren einzieht und diese neue Person beginnt, sich selbst anzurufen misterStruct. Niemand muss eine Benachrichtigung über eine Adressänderung erhalten, aber wenn sich jemand diese Adresse ansieht, sieht er einen Mann mit blauen Haaren.

Lassen Sie uns nun modellieren, was passiert, wenn Sie eine Funktion auf a aufrufen struct. In diesem Fall ist es misterStructso, als würde man eine Bestellung wie erhalten changeYourHairBlue(). Also gibt die Post die Anweisung: misterStruct"Ändere deine Haare in Blau und sag mir, wenn du fertig bist."

Wenn er der gleichen Routine wie zuvor folgt und das tut, was er getan hat, als die Variable direkt geändert wurde, misterStructwird er aus seinem eigenen Haus ausziehen und eine neue Person mit blauen Haaren hinzuziehen. Aber das ist das Problem.

Der Befehl lautete: "Ändere deine Haare in Blau und sag mir, wann du fertig bist", aber es ist der Grüne, der diesen Befehl erhalten hat. Nachdem der Blaue eingezogen ist, muss noch eine Benachrichtigung "Job abgeschlossen" zurückgesendet werden. Aber der Blaue weiß nichts davon.

[Um diese Analogie wirklich zu etwas Schrecklichem zu machen, passierte dem grünhaarigen Mann technisch gesehen, dass er nach seinem Auszug sofort Selbstmord begangen hat. Also er niemanden mitteilen kann , dass die Aufgabe abgeschlossen ist entweder! ]]

Um dieses Problem zu vermeiden , muss Swift nur in solchen Fällen direkt zum Haus unter dieser Adresse gehen und tatsächlich die Haare des aktuellen Bewohners wechseln . Das ist ein ganz anderer Prozess als nur einen neuen Mann einzusenden.

Und deshalb möchte Swift, dass wir das mutatingSchlüsselwort verwenden!

Das Endergebnis sieht für alles, was sich auf die Struktur beziehen muss, gleich aus: Der Bewohner des Hauses hat jetzt blaue Haare. Aber die Prozesse, um dies zu erreichen, sind tatsächlich völlig anders. Es sieht so aus, als würde es dasselbe tun, aber es macht etwas ganz anderes. Es macht etwas, was Swift-Strukturen im Allgemeinen niemals tun.

Um dem armen Compiler ein wenig Hilfe zu geben und ihn nicht dazu zu bringen, herauszufinden, ob eine Funktion die für jede einzelne Strukturfunktion selbst mutiert structoder nicht , werden wir gebeten, Mitleid zu haben und das mutatingSchlüsselwort zu verwenden.

Im Wesentlichen müssen wir alle unseren Beitrag leisten, damit Swift schnell bleibt. :) :)

BEARBEITEN:

Hey Dude / Dudette, die mich herabgestimmt hat, ich habe meine Antwort einfach komplett neu geschrieben. Wenn es besser zu Ihnen passt, werden Sie dann die Ablehnung entfernen?


1
Das ist so erstaunlich geschrieben! So einfach zu verstehen. Ich habe das Internet danach durchsucht und hier ist die Antwort (naja, ohne den Quellcode durchgehen zu müssen). Vielen Dank, dass Sie sich die Zeit genommen haben, es zu schreiben.
Vaibhav

1
Ich möchte nur bestätigen, dass ich es richtig verstehe: Können wir sagen, dass, wenn eine Eigenschaft einer Struct-Instanz direkt in Swift geändert wird, diese Instanz der Struct "beendet" wird und eine neue Instanz mit geänderter Eigenschaft "erstellt" wird? hinter den Kulissen? Und wenn wir eine Mutationsmethode innerhalb der Instanz verwenden, um die Eigenschaft dieser Instanz zu ändern, findet dieser Beendigungs- und Neuinitialisierungsprozess nicht statt?
Ted

@Teo, ich wünschte, ich könnte definitiv antworten, aber das Beste, was ich sagen kann, ist, dass ich diese Analogie an einem tatsächlichen Swift-Entwickler vorbeigeführt habe und sie sagten "das ist im Grunde genommen richtig", was bedeutet, dass es im technischen Sinne mehr gibt. Aber mit diesem Verständnis - dass es mehr als nur das gibt - im weiteren Sinne, ja, das sagt diese Analogie.
Le Mot Juiced

8

Schnelle Strukturen können entweder als Konstanten (via let) oder als Variablen (via var) instanziiert werden.

Betrachten Sie Swifts ArrayStruktur (ja, es ist eine Struktur).

var petNames: [String] = ["Ruff", "Garfield", "Nemo"]
petNames.append("Harvey") // ["Ruff", "Garfield", "Nemo", "Harvey"]

let planetNames: [String] = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
planetNames.append("Pluto") //Error, sorry Pluto. No can do

Warum funktionierte der Anhang nicht mit den Planetennamen? Weil Anhängen mit dem mutatingSchlüsselwort markiert ist . Und da planetNamesmit deklariert wurde let, sind alle so gekennzeichneten Methoden tabu.

In Ihrem Beispiel kann der Compiler feststellen, dass Sie die Struktur ändern, indem Sie einer oder mehreren ihrer Eigenschaften außerhalb von eine zuweisen init. Wenn Sie Ihren Code ein wenig ändern, werden Sie das sehen xund ysind außerhalb der Struktur nicht immer zugänglich. Beachten Sie die letin der ersten Zeile.

let p = Point(x: 1, y: 2)
p.x = 3 //error
p.moveToX(5, andY: 5) //error

5

Betrachten Sie eine Analogie zu C ++. Eine struct-Methode in Swift ist mutating/ not- mutatinganalog zu einer Methode in C ++, die non- const/ ist const. Eine constin C ++ markierte Methode kann die Struktur ebenfalls nicht mutieren.

Sie können eine Variable von außerhalb einer Struktur ändern, aber Sie können sie nicht von ihren eigenen Methoden aus ändern.

In C ++ können Sie auch "eine Variable von außerhalb der Struktur ändern" - allerdings nur, wenn Sie eine constVariable ohne Struktur haben. Wenn Sie eine constStrukturvariable haben, können Sie keine Variable zuweisen und auch keine Nichtmethode aufrufen const. Ebenso können Sie in Swift eine Eigenschaft einer Struktur nur ändern, wenn die Strukturvariable keine Konstante ist. Wenn Sie eine Strukturkonstante haben, können Sie keiner Eigenschaft zuweisen und auch keine mutatingMethode aufrufen .


1

Ich habe mich das Gleiche gefragt, als ich angefangen habe, Swift zu lernen, und jede dieser Antworten, obwohl ich vielleicht einige Erkenntnisse hinzufüge, ist für sich genommen etwas wortreich und verwirrend. Ich denke, die Antwort auf Ihre Frage ist eigentlich ziemlich einfach ...

Die in Ihrer Struktur definierte Mutationsmethode möchte die Berechtigung, jede einzelne Instanz von sich selbst zu ändern, die in Zukunft jemals erstellt wird. Was ist, wenn eine dieser Instanzen einer unveränderlichen Konstante mit zugewiesen wird let? Oh oh. Um Sie vor sich selbst zu schützen (und um den Editor und den Compiler wissen zu lassen, was Sie versuchen ), müssen Sie explizit angeben, wenn Sie einer Instanzmethode diese Art von Leistung verleihen möchten.

Im Gegensatz dazu wird die Einstellung einer Eigenschaft von außerhalb Ihrer Struktur auf eine bekannte Instanz dieser Struktur angewendet. Wenn es einer Konstante zugewiesen ist, informiert Sie Xcode, sobald Sie den Methodenaufruf eingegeben haben.

Dies ist eines der Dinge, die ich an Swift liebe, wenn ich anfange, es mehr zu verwenden - ich werde beim Tippen auf Fehler aufmerksam gemacht. Sicher übertrifft die Fehlerbehebung bei obskuren JavaScript-Fehlern!


1

SWIFT: Verwendung der Mutationsfunktion in Strukturen

Schnelle Programmierer haben Structs so entwickelt, dass die Eigenschaften innerhalb von Struct-Methoden nicht geändert werden können. Überprüfen Sie beispielsweise den unten angegebenen Code

struct City
{
  var population : Int 
  func changePopulation(newpopulation : Int)
  {
      population = newpopulation //error: cannot modify property "popultion"
  }
}
  var mycity = City(population : 1000)
  mycity.changePopulation(newpopulation : 2000)

Bei der Ausführung des obigen Codes wird eine Fehlermeldung angezeigt, da wir versuchen, der Immobilienpopulation der Strukturstadt einen neuen Wert zuzuweisen. Standardmäßig können Structs-Eigenschaften nicht innerhalb ihrer eigenen Methoden mutiert werden. So haben Apple-Entwickler es erstellt, sodass die Strukturen standardmäßig statisch sind.

Wie lösen wir das? Was ist die Alternative?

Mutierendes Schlüsselwort:

Durch das Deklarieren der Funktion als Mutation innerhalb von Struct können wir Eigenschaften in Structs ändern. Zeile Nr. 5 des obigen Codes ändert sich wie folgt:

mutating changePopulation(newpopulation : Int)

Jetzt können wir den Wert der neuen Population der Immobilienpopulation im Rahmen der Methode zuweisen .

Hinweis :

let mycity = City(1000)     
mycity.changePopulation(newpopulation : 2000)   //error: cannot modify property "popultion"

Wenn wir let anstelle von var für Struct-Objekte verwenden, können wir den Wert von Eigenschaften nicht mutieren. Dies ist auch der Grund, warum beim Versuch, die Mutationsfunktion mit der let-Instanz aufzurufen, eine Fehlermeldung angezeigt wird. Daher ist es besser, var zu verwenden, wenn Sie den Wert einer Eigenschaft ändern.

Würde gerne Ihre Kommentare und Gedanken hören… ..

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.