Il y a quelque chose que je ne comprends pas tout à fait en ce qui concerne la mutation des types de valeur dans Swift.
Comme l'indique l'iBook "Swift Langage de programmation"): ) Par défaut, les propriétés d'un type de valeur ne peuvent pas être modifiées depuis ses méthodes d'instance. .
Pour rendre cela possible, nous pouvons déclarer des méthodes avec le mot clé mutating
à l'intérieur de structs et d'énums.
La chose qui n’est pas tout à fait claire pour moi est la suivante: vous pouvez modifier une variable de l’extérieur d’une structure, mais vous ne pouvez pas la modifier à partir de ses propres méthodes. Cela me semble contre-intuitif, car dans les langages orientés objet, vous essayez généralement d'encapsuler des variables afin qu'elles ne puissent être modifiées que de l'intérieur. Avec struct, cela semble être l'inverse. Pour élaborer, voici un extrait de code:
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)
Est-ce que quelqu'un sait la raison pour laquelle les structs ne peuvent pas changer leur contenu de l'intérieur de leur propre contexte, alors que le contenu peut facilement être changé ailleurs?
L'attribut de mutabilité est marqué sur une mémoire (constante ou variable), pas le type. Vous pouvez penser que struct a deux modes: mutable et immuable. Si vous affectez une valeur de struct à un stockage immuable (nous l'appelons let
ou constant dans Swift), la valeur devient immuable en mode, et vous ne pouvez changer aucun état dans la valeur. (y compris l'appel de toute méthode de mutation)
Si la valeur est affectée à un stockage mutable (nous l'appelons var
ou variable dans Swift), vous êtes libre de modifier leur état et l'appel de la méthode mutating est autorisée.
De plus, les classes n'ont pas ce mode immuable/mutable. IMO, cela est dû au fait que les classes sont généralement utilisées pour représenter une entité reference -able. Et une entité référencée est généralement modifiable car il est très difficile de créer et de gérer des graphes de référence d'entités de manière immuable avec des performances appropriées. Ils peuvent ajouter cette fonctionnalité plus tard, mais pas maintenant au moins.
Pour les programmeurs Objective-C, les concepts mutables/immuables sont très familiers. Dans Objective-C, nous avions deux classes séparées pour chaque concept, mais dans Swift, vous pouvez le faire avec une structure. Moitié travail.
Pour les programmeurs C/C++, ce concept est également très familier. C'est exactement ce que fait le mot-clé const
en C/C++.
En outre, la valeur immuable peut être très bien optimisée. En théorie, Swift (ou LLVM) peut effectuer une élection de copie sur les valeurs passées par let
, exactement comme C++. Si vous utilisez une structure immuable avec sagesse, elle surperformera refcounted Des classes.
Comme @Joseph a affirmé que cela ne fournissait pas pourquoi, j'ajoute un peu plus.
Les structures ont deux types de méthodes. plain et mutation méthodes. Plain cette méthode implique immuable (ou non-mutant). Cette séparation n’existe que pour supporter la sémantique immuable. Un objet en mode immuable ne devrait pas du tout changer d'état.
Ensuite, les méthodes immuables doivent garantir cette immuabilité sémantique. Ce qui signifie que cela ne devrait changer aucune valeur interne. Donc, le compilateur interdit tout changement d'état de lui-même dans une méthode immuable. En revanche, les méthodes de mutation sont libres de modifier les états.
Et puis, vous pouvez avoir une question de pourquoi le paramètre par défaut est-il immuable? C'est parce qu'il est très difficile de prédire l'état futur de la valeur de mutation, et qu'il devient généralement la principale source de maux de tête et de bugs. De nombreuses personnes ont convenu que la solution consistait à éviter les éléments mutables, puis que immuable par défaut était au sommet de la liste de souhaits pendant des décennies dans les langages de la famille C/C++ et leurs dérivés.
Voir style purement fonctionnel pour plus de détails. Quoi qu'il en soit, nous avons toujours besoin d'éléments modifiables, car les éléments immuables présentent des faiblesses, et les discussions à ce sujet semblent ne pas être d'actualité.
J'espère que ça aide.
Une structure est une agrégation de champs; si une instance de structure particulière est modifiable, ses champs le seront; si une instance est immuable, ses champs seront immuables. Un type de structure doit donc être préparé pour la possibilité que les champs d'une instance donnée puissent être mutables ou immuables.
Pour qu'une méthode de structure mute les champs de la structure sous-jacente, ces champs doivent être mutables. Si une méthode qui mute des champs de la structure sous-jacente est invoquée sur une structure immuable, elle essaiera de muter des champs immuables. Puisque rien de bon ne peut en résulter, une telle invocation doit être interdite.
Pour ce faire, Swift divise les méthodes de structure en deux catégories: celles qui modifient la structure sous-jacente et ne peuvent donc être invoquées que sur des instances de structure mutable, et celles qui ne modifient pas la structure sous-jacente et doivent Par conséquent, soyez invocable à la fois sur les instances mutables et immuables, cette dernière utilisation étant probablement la plus fréquente, elle est donc la valeur par défaut.
En comparaison, .NET actuellement (encore!) N’offre aucun moyen de distinguer les méthodes de structure qui modifient la structure de celles qui ne le font pas. Au lieu de cela, invoquer une méthode de structure sur une instance de structure immuable amènera le compilateur à créer une copie modifiable de l'instance de structure, à laisser la méthode faire ce qu'elle voudra et à ignorer la copie lorsque la méthode sera terminée. Cela a pour effet de forcer le compilateur à perdre du temps à copier la structure, que la méthode le modifie ou non, même si l'ajout de l'opération de copie ne transformera presque jamais un code sémantiquement incorrect en un code sémantiquement correct; cela fera simplement que le code sémantiquement incorrect d'une manière (modification d'une valeur "immuable") sera erroné d'une manière différente (permettant au code de penser qu'il modifie une structure, mais en ignorant les modifications tentées). Le fait de permettre aux méthodes de structure d'indiquer si elles modifieront la structure sous-jacente peut éliminer le besoin d'une opération de copie inutile et garantir également que toute tentative d'utilisation erronée sera marquée.
Attention: termes de profanes à venir.
Cette explication n’est pas rigoureusement correcte au niveau du code le plus critique. Cependant, il a été examiné par un gars qui travaille sur Swift) et il a dit que c’était suffisant comme explication de base.
Donc, je veux essayer simplement et directement de répondre à la question "pourquoi".
Pour être précis: pourquoi devons-nous marquer les fonctions de structure comme mutating
alors qu’il est possible de modifier les paramètres de structure sans modifier les mots-clés?
Donc, en gros, cela a beaucoup à voir avec la philosophie qui maintient Swift Swift.
Vous pourriez en quelque sorte penser à cela comme le problème de la gestion des adresses physiques réelles. Lorsque vous changez d'adresse, s'il y a beaucoup de gens qui ont votre adresse actuelle, vous devez les informer de votre déménagement. Mais si personne n'a votre adresse actuelle, vous pouvez simplement vous déplacer où vous voulez et personne n'a besoin de savoir.
Dans cette situation, Swift est un peu comme la poste. Si beaucoup de gens qui ont beaucoup de contacts se déplacent beaucoup, les frais généraux qui y sont associés sont très élevés. Les gens doivent gérer toutes ces notifications, et le processus prend beaucoup de temps et d’efforts. C’est pourquoi l’état idéal de Swift est que tous les habitants de sa ville aient le moins de contacts possible. changements, et il peut faire tout le reste plus rapidement et mieux.
C'est aussi la raison pour laquelle Swift-Folks s'émerveille des types de valeur par rapport aux types de référence. Par nature, les types de référence accumulent des "contacts" un peu partout et les types de valeur ne nécessitent généralement pas plus qu'un couple. Les types de valeur sont "Swift" -er.
Revenons donc à la petite image: structs
. Les structures sont un gros problème dans Swift car elles peuvent faire la plupart des choses que les objets peuvent faire, mais ce sont des types de valeur.
Continuons l'analogie de l'adresse physique en imaginant un misterStruct
résidant dans someObjectVille
. L’analogie est un peu fausse, mais je pense qu’elle est toujours utile.
Donc, pour modéliser le changement d'une variable sur un struct
, disons que misterStruct
a les cheveux verts et reçoit un ordre pour passer aux cheveux bleus. Comme je l'ai dit, l'analogie est perdue, mais en quelque sorte, au lieu de changer les cheveux de misterStruct
, la personne âgée déménage et une nouvelle personne aux cheveux bleus entre et cette nouvelle personne commence à s’appeler misterStruct
. Personne n'a besoin de recevoir une notification de changement d'adresse, mais si quelqu'un regarde cette adresse, il verra un type avec des cheveux bleus.
Modélisons maintenant ce qui se passe lorsque vous appelez une fonction sur un struct
. Dans ce cas, c'est comme si misterStruct
recevait un ordre tel que changeYourHairBlue()
. Donc, le bureau de poste donne l'instruction à misterStruct
"Va te changer les cheveux en bleu et dis-moi quand tu auras fini."
S'il suit la même procédure qu'auparavant, s'il fait ce qu'il a fait lorsque la variable a été modifiée directement, ce que misterStruct
fera est quitter sa propre maison et appelez une nouvelle personne aux cheveux bleus. Mais c'est le problème.
La commande était "allez changer vos cheveux en bleu et dites-moi quand vous avez terminé", mais c'est le type vert qui a reçu cette commande. Une fois que le gars bleu a emménagé, une notification "travail terminé" doit encore être renvoyée. Mais le type bleu n'en sait rien.
[Pour vraiment comprendre cette analogie, ce qui est techniquement arrivé à un homme aux cheveux verts, c’est qu’après son déménagement, il s’est immédiatement suicidé. Donc il ne peut notifier à personne que la tâche est terminée non plus! ]
Pour éviter ce problème, dans des cas comme celui-ci seulement , Swift doit entrer directement à la maison à cette adresse et change réellement les cheveux de l'habitant actuel . C'est un processus complètement différent de simplement envoyer un nouveau type.
Et c'est pourquoi Swift veut que nous utilisions le mot clé mutating
!
Le résultat final s’apparente à tout ce qui doit faire référence à la structure: l’habitant de la maison a maintenant les cheveux bleus. Mais les processus pour y parvenir sont en réalité complètement différents. On dirait que c'est faire la même chose, mais c'est très différent. C'est une chose que les structures Swift en général ne font jamais.
Donc, pour donner un peu d'aide au pauvre compilateur, sans lui demander de déterminer si une fonction mute le struct
ou non, seule pour chaque fonction de struct Cependant, on nous demande d’avoir pitié et d’utiliser le mot-clé mutating
.
Essentiellement, pour aider Swift rester Swift, nous devons tous faire notre part. :)
EDIT:
Salut mec/dudette qui m'a rétrogradé, j'ai simplement complètement réécrit ma réponse. Si cela vous convient mieux, retirerez-vous le vote négatif?
Les structures Swift peuvent être instanciées sous forme de constantes (via let
) ou de variables (via var
)
Considérons la structure Array
de Swift (oui, c'est une structure).
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
Pourquoi l'annexe n'a-t-elle pas fonctionné avec les noms de planète? Parce que append est marqué avec le mot clé mutating
. Et puisque planetNames
a été déclaré avec let
, toutes les méthodes ainsi marquées sont hors limites.
Dans votre exemple, le compilateur peut indiquer que vous modifiez la structure en attribuant une ou plusieurs de ses propriétés en dehors d'un init
. Si vous modifiez un peu votre code, vous verrez que x
et y
ne sont pas toujours accessibles en dehors de la structure. Notez le let
sur la première ligne.
let p = Point(x: 1, y: 2)
p.x = 3 //error
p.moveToX(5, andY: 5) //error
Prenons une analogie avec C++. Une méthode de struct dans Swift étant mutating
/pas -mutating
est analogue à une méthode en C++ non -const
/const
. De même, une méthode marquée const
en C++ ne peut pas muter la structure.
Vous pouvez modifier une variable de l'extérieur d'une structure, mais vous ne pouvez pas la modifier à partir de ses propres méthodes.
En C++, vous pouvez aussi "changer une variable de l'extérieur de la structure" - mais niquement si vous avez une variable non -const
struct. Si vous avez une variable const
struct, vous ne pouvez pas affecter de variable, ni appeler une méthode autre que const
. De même, dans Swift, vous pouvez modifier une propriété d'une structure uniquement si la variable de structure n'est pas une constante. Si vous avez une constante de structure, vous ne pouvez pas affecter de propriété ni appeler une méthode mutating
.
Je me demandais la même chose quand j'ai commencé à apprendre Swift, et chacune de ces réponses, bien que ajoutant quelques idées, est peut-être un peu verbeuse et déroutante. Je pense que la réponse à votre question est en fait assez simple…
La méthode de mutation définie à l'intérieur de votre structure demande l'autorisation de modifier chacune de ses instances qui seront créées dans le futur. Que se passe-t-il si l’une de ces instances est assignée à une constante immuable avec let
? Euh oh. Pour vous protéger de vous-même (et pour permettre à l'éditeur et au compilateur de savoir ce que vous essayez de faire), vous devez être explicite lorsque vous vouloir donner à une méthode d'instance ce genre de pouvoir.
En revanche, la définition d'une propriété à partir de en dehors de votre structure fonctionne sur une instance connue de cette structure. Si elle est assignée à une constante, Xcode vous le fera savoir au moment où vous avez saisi l'appel de méthode.
C’est l’une des choses qui me passionne Swift au moment où je commence à l’utiliser plus souvent - à être averti des erreurs au fur et à mesure que je les tape. C’est bien mieux que de résoudre des problèmes de bugs JavaScript obscurs!