Struttura rapida e mutevole


97

C'è qualcosa che non capisco del tutto quando si tratta di mutare i tipi di valore in Swift.

Come afferma l'iBook "The Swift Programming Language": Per impostazione predefinita, le proprietà di un tipo di valore non possono essere modificate dai suoi metodi di istanza.

E quindi per rendere possibile ciò possiamo dichiarare metodi con la mutatingparola chiave all'interno di structs ed enums.

La cosa che non mi è del tutto chiara è questa: puoi cambiare una var dall'esterno di una struttura, ma non puoi cambiarla dai suoi metodi. Questo mi sembra controintuitivo, poiché nei linguaggi orientati agli oggetti, generalmente si tenta di incapsulare le variabili in modo che possano essere modificate solo dall'interno. Con gli struct questo sembra essere il contrario. Per approfondire, ecco uno snippet di codice:

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) 

Qualcuno sa il motivo per cui gli struct non possono modificare il loro contenuto dall'interno del proprio contesto, mentre i contenuti possono essere facilmente modificati altrove?

Risposte:


81

L'attributo mutabilità è contrassegnato su una memoria (costante o variabile), non su un tipo. Puoi pensare che la struttura abbia due modalità: mutabile e immutabile . Se si assegna un valore struct a un archivio immutabile (lo chiamiamo leto costante in Swift) il valore diventa modalità immutabile e non è possibile modificare alcuno stato nel valore. (inclusa la chiamata a qualsiasi metodo mutante)

Se il valore è assegnato a un archivio modificabile (lo chiamiamo varo variabile in Swift), sei libero di modificarne lo stato ed è consentita la chiamata del metodo mutante.

Inoltre, le classi non hanno questa modalità immutabile / modificabile. IMO, questo perché le classi vengono solitamente utilizzate per rappresentare entità in grado di fare riferimento . E l'entità in grado di fare riferimento è generalmente mutabile perché è molto difficile creare e gestire grafici di riferimento di entità in modo immutabile con prestazioni adeguate. Potrebbero aggiungere questa funzione in un secondo momento, ma non almeno ora.

Per i programmatori Objective-C, i concetti mutabili / immutabili sono molto familiari. In Objective-C avevamo due classi separate per ogni concetto, ma in Swift, puoi farlo con una struttura. Metà lavoro.

Per i programmatori C / C ++, questo è anche un concetto molto familiare. Questo è esattamente ciò che le constparole chiave fanno in C / C ++.

Inoltre, il valore immutabile può essere ottimizzato molto bene. In teoria, il compilatore Swift (o LLVM) può eseguire l'elisione della copia sui valori passati let, proprio come in C ++. Se usi saggiamente la struttura immutabile, supererà le classi conteggiate di nuovo.

Aggiornare

Poiché @Joseph ha affermato che questo non fornisce il motivo , sto aggiungendo un po 'di più.

Le strutture hanno due tipi di metodi. metodi semplici e mutevoli . Il metodo semplice implica immutabile (o non mutante) . Questa separazione esiste solo per supportare la semantica immutabile . Un oggetto in modalità immutabile non dovrebbe cambiare affatto il suo stato.

Quindi, metodi immutabili devono garantire questa immutabilità semantica . Il che significa che non dovrebbe cambiare alcun valore interno. Quindi il compilatore non consente alcun cambiamento di stato di se stesso in un metodo immutabile. Al contrario, i metodi di mutazione sono liberi di modificare gli stati.

E poi, potresti avere una domanda sul perché immutabile è l'impostazione predefinita? Questo perché è molto difficile prevedere lo stato futuro dei valori mutanti e questo di solito diventa la principale fonte di mal di testa e insetti. Molte persone hanno convenuto che la soluzione è evitare cose mutabili, e quindi immutabile per impostazione predefinita era in cima alla lista dei desideri per decenni nei linguaggi della famiglia C / C ++ e nelle sue derivazioni.

Vedi lo stile puramente funzionale per maggiori dettagli. Ad ogni modo, abbiamo ancora bisogno di cose mutabili perché le cose immutabili hanno alcuni punti deboli e discuterne sembra essere fuori tema.

Spero che aiuti.


2
Quindi, fondamentalmente, posso interpretarlo come: uno struct non sa in anticipo se sarà mutabile o immutabile, quindi assume l'immutabilità a meno che non sia dichiarato diversamente. Visto così ha davvero senso. È corretto?
Mike Seghers

1
@MikeSeghers penso che abbia senso.
eonil

3
Sebbene questo sia finora il migliore di due risposte, non penso che risponda PERCHÉ, per impostazione predefinita, le proprietà di un tipo di valore non possono essere modificate dai suoi metodi di istanza. fai accenni che è perché le strutture predefinite sono immutabili, ma non penso che sia vero
Joseph Knight,

4
@JosephKnight Non è che le strutture siano di default immutabili. È che i metodi degli struct sono predefiniti immutabili. Pensalo come C ++ con l'ipotesi inversa. Nei metodi C ++ l'impostazione predefinita è non costante this, è necessario aggiungere esplicitamente consta al metodo se si desidera che abbia un 'const this' e sia consentito nelle istanze costanti (i metodi normali non possono essere utilizzati se l'istanza è const). Swift fa lo stesso con il valore predefinito opposto: un metodo ha un 'const this' per impostazione predefinita e lo contrassegni altrimenti se deve essere vietato per le istanze costanti.
File analogico

3
@golddove Sì, e non puoi. Non dovresti. Devi sempre creare o derivare una nuova versione del valore e il tuo programma deve essere progettato per adottare intenzionalmente questo modo. La funzione esiste per prevenire tale mutazione accidentale. Di nuovo, questo è uno dei concetti fondamentali della programmazione in stile funzionale .
eonil

25

Una struttura è un'aggregazione di campi; se una particolare istanza di struttura è mutabile, i suoi campi saranno mutabili; se un'istanza è immutabile, i suoi campi saranno immutabili. Un tipo di struttura deve quindi essere preparato per la possibilità che i campi di una particolare istanza possano essere mutabili o immutabili.

Affinché un metodo di struttura muti i campi della struttura sottostante, questi campi devono essere mutabili. Se un metodo che muta i campi della struttura sottostante viene invocato su una struttura immutabile, proverebbe a mutare i campi immutabili. Poiché non ne può derivare nulla di buono, tale invocazione deve essere vietata.

Per ottenere ciò, Swift divide i metodi di struttura in due categorie: quelli che modificano la struttura sottostante, e quindi possono essere invocati solo su istanze di struttura mutabile, e quelli che non modificano la struttura sottostante e dovrebbero quindi essere invocabili sia su istanze mutabili che immutabili . Quest'ultimo utilizzo è probabilmente il più frequente ed è quindi l'impostazione predefinita.

In confronto, .NET attualmente (ancora!) Non offre alcun mezzo per distinguere i metodi di struttura che modificano la struttura da quelli che non lo fanno. Invece, invocare un metodo di struttura su un'istanza di struttura immutabile farà sì che il compilatore crei una copia mutabile dell'istanza di struttura, lascerà che il metodo faccia tutto ciò che vuole e scarterà la copia quando il metodo è fatto. Questo ha l'effetto di costringere il compilatore a perdere tempo a copiare la struttura indipendentemente dal fatto che il metodo la modifichi o meno, anche se l'aggiunta dell'operazione di copia non trasformerà quasi mai quello che sarebbe codice semanticamente errato in codice semanticamente corretto; causerà semplicemente che il codice semanticamente sbagliato in un modo (modificando un valore "immutabile") sia sbagliato in un modo diverso (consentendo al codice di pensare che stia modificando una struttura, ma scartando le modifiche tentate). Consentire ai metodi struct di indicare se modificheranno la struttura sottostante può eliminare la necessità di un'operazione di copia inutile e garantisce anche che i tentativi di utilizzo errato vengano contrassegnati.


1
Il confronto con .NET e un esempio che non ha la parola chiave mutante mi ha reso chiaro. Il motivo per cui la parola chiave mutante creerebbe vantaggi.
Binarian

20

Attenzione: i termini del profano sono avanti.

Questa spiegazione non è rigorosamente corretta al livello di codice più nitido. Tuttavia è stato recensito da un ragazzo che lavora su Swift e ha detto che è abbastanza buono come spiegazione di base.

Quindi voglio provare a rispondere in modo semplice e diretto alla domanda del "perché".

Per essere precisi: perché dobbiamo contrassegnare le funzioni struct come mutatingquando possiamo cambiare i parametri della struttura senza modificare le parole chiave?

Quindi, il quadro generale, ha molto a che fare con la filosofia che mantiene Swift veloce.

Potresti pensarlo come il problema della gestione degli indirizzi fisici effettivi. Quando cambi il tuo indirizzo, se ci sono molte persone che hanno il tuo attuale, devi avvisare tutti che ti sei trasferito. Ma se nessuno ha il tuo indirizzo attuale, puoi spostarti dove vuoi e nessuno deve saperlo.

In questa situazione, Swift è un po 'come l'ufficio postale. Se molte persone con molti contatti si muovono molto, il sovraccarico è davvero elevato. Deve pagare un grande staff di persone per gestire tutte quelle notifiche e il processo richiede molto tempo e fatica. Ecco perché lo stato ideale di Swift è che tutti nella sua città abbiano il minor numero di contatti possibile. Quindi non ha bisogno di un grande staff per gestire i cambiamenti di indirizzo e può fare tutto il resto più velocemente e meglio.

Questo è anche il motivo per cui Swift-folks è entusiasta dei tipi di valore rispetto ai tipi di riferimento. Per natura, i tipi di riferimento accumulano "contatti" ovunque e i tipi di valore di solito non hanno bisogno di più di un paio. I tipi di valore sono "Swift" -er.

Ma torniamo al piccolo immagine: structs. Le strutture sono un grosso problema in Swift perché possono fare la maggior parte delle cose che gli oggetti possono fare, ma sono tipi di valore.

Continuiamo l'analogia dell'indirizzo fisico immaginando un misterStructche vive someObjectVille. L'analogia è un po 'vinta qui, ma penso che sia ancora utile.

Quindi, per modellare la modifica di una variabile su a struct, diciamo che misterStructha i capelli verdi e ottiene un ordine per passare ai capelli blu. L'analogia viene vinta, come ho detto, ma più o meno quello che succede è che invece di cambiare misterStructi capelli, la persona anziana se ne va e una nuova persona con i capelli blu si avvicina, e quella nuova persona inizia a chiamarsi misterStruct. Nessuno ha bisogno di ricevere una notifica di cambio di indirizzo, ma se qualcuno guarda quell'indirizzo, vedrà un ragazzo con i capelli blu.

Ora modelliamo cosa succede quando chiami una funzione su un file struct. In questo caso, è come misterStructricevere un ordine come changeYourHairBlue(). Quindi l'ufficio postale fornisce le istruzioni per misterStruct"vai a cambiarti i capelli in blu e dimmi quando hai finito".

Se sta seguendo la stessa routine di prima, se sta facendo quello che ha fatto quando la variabile è stata cambiata direttamente, quello misterStructche farà è uscire di casa sua e chiamare una nuova persona con i capelli blu. Ma questo è il problema.

L'ordine era "vai a cambiarti i capelli in blu e dimmi quando hai finito", ma è il ragazzo verde che ha ricevuto quell'ordine. Dopo che il ragazzo blu si è trasferito, una notifica di "completamento del lavoro" deve ancora essere rispedita. Ma il ragazzo blu non ne sa niente.

[Per risolvere davvero questa analogia con qualcosa di terribile, quello che tecnicamente è successo al ragazzo dai capelli verdi è stato che dopo essersi trasferito si è immediatamente suicidato. Quindi non può nemmeno avvisare nessuno che l'attività è stata completata ! ]

Per evitare questo problema, in casi come questo solo , Swift deve andare in direttamente a casa a quell'indirizzo e effettivamente cambiare i capelli del abitante corrente . Questo è un processo completamente diverso dal semplice invio di un nuovo ragazzo.

Ed è per questo che Swift vuole che usiamo la mutatingparola chiave!

Il risultato finale sembra lo stesso per tutto ciò che deve riferirsi alla struttura: l'abitante della casa ora ha i capelli blu. Ma i processi per ottenerlo sono in realtà completamente diversi. Sembra che stia facendo la stessa cosa, ma sta facendo una cosa molto diversa. Sta facendo una cosa che le strutture Swift in generale non fanno mai.

Quindi, per dare un piccolo aiuto al povero compilatore e non costringerlo a capire se una funzione muta structo meno, da sola, per ogni singola funzione struct, ci viene chiesto di avere pietà e di usare la mutatingparola chiave.

In sostanza, per aiutare Swift a rimanere veloce, dobbiamo tutti fare la nostra parte. :)

MODIFICARE:

Ehi amico / tizio che mi ha svalutato, ho appena riscritto completamente la mia risposta. Se ti sta meglio, rimuoverai il voto negativo?


1
Questo è così <f-word-here> scritto in modo sorprendente! Quindi, così facile da capire. Ho setacciato Internet per questo ed ecco la risposta (beh, senza dover passare comunque attraverso il codice sorgente). Grazie mille per aver dedicato del tempo a scriverlo.
Vaibhav

1
Voglio solo confermare che lo capisco correttamente: possiamo dire che, quando una proprietà di un'istanza di Struct viene modificata direttamente in Swift, tale istanza di Struct viene "terminata" e viene "creata" una nuova istanza con proprietà modificata dietro le quinte? E quando usiamo un metodo di mutazione all'interno dell'istanza per modificare la proprietà di quell'istanza, questo processo di terminazione e reinizializzazione non ha luogo?
Ted

@Teo, vorrei poter rispondere in modo definitivo, ma il meglio che posso dire è che ho eseguito questa analogia con un vero sviluppatore Swift, e loro hanno detto "è fondamentalmente accurato", il che implica che c'è di più in senso tecnico. Ma con quella comprensione - che c'è di più oltre a questo - in senso lato, sì, questo è ciò che sta dicendo questa analogia.
Le Mot Juiced

8

Le strutture Swift possono essere istanziate come costanti (tramite let) o variabili (tramite var)

Considera la Arraystruttura di Swift (sì, è una struttura).

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

Perché l'appendice non ha funzionato con i nomi dei pianeti? Perché append è contrassegnato dalla mutatingparola chiave. E poiché è planetNamesstato dichiarato utilizzando let, tutti i metodi così contrassegnati sono off limits.

Nel tuo esempio il compilatore può dire che stai modificando la struttura assegnandole a una o più delle sue proprietà al di fuori di un file init. Se modifichi un po 'il tuo codice lo vedrai xe ynon sono sempre accessibili al di fuori della struttura. Notare il letsulla prima riga.

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

5

Considera un'analogia con C ++. Un metodo struct in Swift essendo mutating/ not- mutatingè analogo a un metodo in C ++ essendo non- const/ const. constAllo stesso modo, un metodo contrassegnato in C ++ non può modificare la struttura.

Puoi cambiare una var dall'esterno di una struttura, ma non puoi cambiarla dai suoi metodi.

In C ++, puoi anche "modificare una var dall'esterno della struttura", ma solo se hai una constvariabile non- struct. Se hai una constvariabile struct, non puoi assegnare a una var e non puoi nemmeno chiamare un non- constmetodo. Allo stesso modo, in Swift, puoi modificare una proprietà di una struttura solo se la variabile struttura non è una costante. Se si dispone di una costante di struttura, non è possibile assegnare a una proprietà e non è nemmeno possibile chiamare un mutatingmetodo.


1

Mi sono chiesta la stessa cosa quando ho iniziato a imparare Swift, e ognuna di queste risposte, aggiungendo forse qualche intuizione, è piuttosto prolissa e confusa di per sé. Penso che la risposta alla tua domanda sia in realtà piuttosto semplice ...

Il metodo mutante definito all'interno della tua struttura richiede il permesso di modificare ogni singola istanza di se stesso che verrà mai creata in futuro. Cosa succede se una di queste istanze viene assegnata a una costante immutabile con let? Uh Oh. Per proteggerti da te stesso (e per far sapere all'editor e al compilatore cosa stai cercando di fare), sei costretto ad essere esplicito quando vuoi dare a un metodo di istanza questo tipo di potere.

Al contrario, l'impostazione di una proprietà dall'esterno della tua struttura opera su un'istanza nota di quella struttura. Se è assegnato a una costante, Xcode te lo farà sapere nel momento in cui hai digitato la chiamata al metodo.

Questa è una delle cose che amo di Swift quando comincio a usarlo di più: essere avvisato degli errori mentre li digito. Sicuramente batte il diavolo nella risoluzione dei problemi di bug JavaScript oscuri!


1

SWIFT: utilizzo della funzione mutante in Structs

I programmatori Swift hanno sviluppato Structs in modo tale che le sue proprietà non possano essere modificate all'interno dei metodi Struct. Ad esempio, controlla il codice fornito di seguito

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)

Eseguendo il codice precedente otteniamo un errore perché stiamo cercando di assegnare un nuovo valore alla popolazione di proprietà della Struct City. Per impostazione predefinita, le proprietà di Structs non possono essere modificate all'interno dei propri metodi. Questo è il modo in cui gli sviluppatori Apple lo hanno costruito, in modo che le strutture abbiano una natura statica per impostazione predefinita.

Come lo risolviamo? Qual è l'alternativa?

Parola chiave mutante:

Dichiarare la funzione come mutante all'interno di Struct ci consente di alterare le proprietà in Structs. Riga n. 5, del codice precedente cambia in questo,

mutating changePopulation(newpopulation : Int)

Ora possiamo assegnare il valore della nuova popolazione alla popolazione di proprietà nell'ambito del metodo.

Nota :

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

Se usiamo let invece di var per oggetti Struct, non possiamo mutare il valore di nessuna proprietà, anche questo è il motivo per cui otteniamo un errore quando proviamo a invocare la funzione mutante usando let istanza. Quindi è meglio usare var ogni volta che si modifica il valore di una proprietà.

Mi piacerebbe sentire i vostri commenti e pensieri ... ..

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.