Structure rapide et mutante

96

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 «Le langage de programmation Swift»: Par défaut, les propriétés d'un type valeur ne peuvent pas être modifiées à partir de ses méthodes d'instance.

Et donc pour rendre cela possible, nous pouvons déclarer des méthodes avec le mutatingmot - clé à l'intérieur des structures et des énumérations.

La chose qui n'est pas tout à fait claire pour moi est la suivante: vous pouvez modifier une variable depuis 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 les structures, 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 connaît la raison pour laquelle les structures ne peuvent pas changer leur contenu de l'intérieur de leur propre contexte, alors que le contenu peut facilement être changé ailleurs?

Mike Seghers
la source

Réponses:

80

L'attribut de mutabilité est marqué sur un stockage (constante ou variable), pas sur un type. Vous pouvez penser que struct a deux modes: mutable et immuable . Si vous affectez une valeur struct à un stockage immuable (nous l'appelons letou constante dans Swift), la valeur devient le mode immuable et vous ne pouvez modifier aucun état de la valeur. (y compris l'appel de toute méthode de mutation)

Si la valeur est affectée à un stockage mutable (nous l'appelons varou variable dans Swift), vous êtes libre de modifier leur état et l'appel de la méthode de mutation est autorisé.

De plus, les classes n'ont pas ce mode immuable / mutable. OMI, c'est parce que les classes sont généralement utilisées pour représenter une entité référençable . Et l'entité référençable est généralement modifiable car il est très difficile de créer et de gérer des graphiques 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 du moins.

Pour les programmeurs Objective-C, les concepts mutables / immuables sont très familiers. En Objective-C, nous avions deux classes séparées pour chaque concept, mais dans Swift, vous pouvez le faire avec une structure. La moitié du travail.

Pour les programmeurs C / C ++, c'est également un concept très familier. C'est exactement ce que constfont les mots clés en C / C ++.

En outre, la valeur immuable peut être très bien optimisée. En théorie, le compilateur Swift (ou LLVM) peut effectuer une élision de copie sur les valeurs passées parlet , tout comme en C ++. Si vous utilisez judicieusement la structure immuable, elle surclassera les classes refcountées.

Mettre à jour

Comme @Joseph l'a affirmé, cela ne explique pas pourquoi , un peu plus.

Les structures ont deux types de méthodes. méthodes simples et mutantes . La méthode simple implique immuable (ou non mutante) . Cette séparation n'existe que pour soutenir immuable sémantique . Un objet en mode immuable ne doit pas du tout changer d'état.

Ensuite, les méthodes immuables doivent garantir cette immuabilité sémantique . Ce qui signifie qu'il ne devrait changer aucune valeur interne. Ainsi, le compilateur interdit tout changement d'état de lui-même dans une méthode immuable. En revanche, les méthodes mutantes sont libres de modifier les états.

Et puis, vous pouvez vous demander pourquoi immuable est la valeur par défaut? C'est parce qu'il est très difficile de prédire l'état futur des valeurs en mutation, et cela devient généralement la principale source de maux de tête et de bugs. Beaucoup de gens ont convenu que la solution évite les trucs mutables, puis immuable par défaut a été en tête de liste de souhaits pendant des décennies dans les langages de la famille C / C ++ et ses dérivés.

Voir le style purement fonctionnel pour plus de détails. Quoi qu'il en soit, nous avons toujours besoin de trucs mutables parce que les trucs immuables ont quelques faiblesses, et en discuter semble être hors de propos.

J'espère que ça aide.

éonil
la source
2
Donc, fondamentalement, je peux l'interpréter comme: Une structure ne sait pas à l'avance si elle sera mutable ou immuable, donc elle suppose l'immuabilité sauf indication contraire. Vu comme ça, cela a du sens. Est-ce correct?
Mike Seghers le
1
@MikeSeghers Je pense que c'est logique.
eonil
3
Bien que ce soit jusqu'à présent la meilleure des deux réponses, je ne pense pas qu'elle réponde POURQUOI, par défaut, les propriétés d'un type valeur ne peuvent pas être modifiées à partir de ses méthodes d'instance. vous faites des indices que c'est parce que les structures par défaut sont immuables, mais je ne pense pas que ce soit vrai
Joseph Knight
4
@JosephKnight Ce n'est pas que les structures par défaut sont immuables. C'est que les méthodes des structures par défaut sont immuables. Pensez-y comme C ++ avec l'hypothèse inverse. Dans les méthodes C ++ par défaut à non constant this, vous devez explicitement ajouter a constà la méthode si vous voulez qu'elle ait un 'const this' et soit autorisée sur les instances constantes (les méthodes normales ne peuvent pas être utilisées si l'instance est const). Swift fait de même avec la valeur par défaut opposée: une méthode a un 'const this' par défaut et vous la marquez autrement si elle doit être interdite pour les instances constantes.
Fichier analogique
3
@golddove Oui, et vous ne pouvez pas. Tu ne devrais pas. Vous devez toujours créer ou dériver une nouvelle version de la valeur, et votre programme doit être conçu pour adopter cette méthode intentionnellement. La fonction existe pour empêcher une telle mutation accidentelle. Encore une fois, c'est l'un des concepts fondamentaux de la programmation de style fonctionnel .
eonil
25

Une structure est une agrégation de champs; si une instance de structure particulière est mutable, ses champs seront mutables; 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 particulière 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 les champs de la structure sous-jacente est appelée sur une structure immuable, elle essaiera de muter les champs immuables. Puisque rien de bon ne peut en résulter, une telle invocation doit être interdite.

Pour y parvenir, Swift divise les méthodes de structure en deux catégories: celles qui modifient la structure sous-jacente, et ne peuvent donc être appelées que sur des instances de structure mutable, et celles qui ne modifient pas la structure sous-jacente et devraient donc être invocables sur les instances mutables et immuables. . Cette dernière utilisation est probablement la plus fréquente et est donc la valeur par défaut.

Par comparaison, .NET n'offre actuellement (encore!) 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 obligera le compilateur à faire une copie modifiable de l'instance de structure, laissera la méthode faire ce qu'elle veut avec elle et rejettera la copie une fois la méthode terminée. Cela a pour effet de forcer le compilateur à perdre du temps à copier la structure, que la méthode la modifie ou non, même si l'ajout de l'opération de copie ne transformera presque jamais ce qui serait un code sémantiquement incorrect en code sémantiquement correct; cela fera simplement en sorte que le code qui est sémantiquement erroné dans un sens (en modifiant une valeur «immuable») soit erroné d'une manière différente (permettant au code de penser qu'il modifie une structure, mais rejetant les modifications tentées). Permettre aux méthodes struct d'indiquer si elles modifieront la structure sous-jacente peut éliminer le besoin d'une opération de copie inutile, et garantit également que les tentatives d'utilisation erronées seront signalées.

supercat
la source
1
La comparaison avec .NET et un exemple qui n'a pas le mot-clé mutating m'a fait comprendre. La raison pour laquelle le mot-clé mutant créerait un avantage
Binarian
19

Attention: les termes du profane à venir.

Cette explication n'est pas rigoureusement correcte au niveau du code le plus précis. Cependant, il a été revu par un gars qui travaille réellement sur Swift et il a dit que c'était assez bon comme explication de base.

Je veux donc essayer de répondre simplement et directement à la question du «pourquoi».

Pour être précis: pourquoi devons-nous marquer les fonctions de structure comme mutatinglorsque nous pouvons changer les paramètres de structure sans modifier les mots-clés?

Donc, dans son ensemble, cela a beaucoup à voir avec la philosophie qui maintient Swift rapide.

Vous pourriez en quelque sorte penser au problème de la gestion des adresses physiques réelles. Lorsque vous changez d'adresse, s'il y a beaucoup de personnes qui ont votre adresse actuelle, vous devez les informer toutes que vous avez déménagé. 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 le bureau de poste. Si beaucoup de personnes avec beaucoup de contacts se déplacent beaucoup, cela a des frais généraux très élevés. Il doit payer un grand nombre de personnes pour 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 chacun dans sa ville ait le moins de contacts possible. Ensuite, il n'a pas besoin d'un gros personnel pour gérer les changements d'adresse, et il peut faire tout le reste plus rapidement et mieux.

C'est aussi pourquoi les gens de Swift raffolent tous des types de valeur par rapport aux types de référence. Par nature, les types de référence accumulent des "contacts" partout, et les types de valeur n'ont généralement pas besoin de plus d'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 une misterStructqui habite someObjectVille. L'analogie est un peu déconcertée ici, mais je pense qu'elle est toujours utile.

Donc, pour modéliser la modification d'une variable sur un struct, disons misterStructa les cheveux verts, et obtient un ordre de passer aux cheveux bleus. L'analogie est déconcertée, comme je l'ai dit, mais ce qui se passe en quelque sorte, c'est qu'au lieu de changer misterStructde cheveux, 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 gars aux cheveux bleus.

Modélisons maintenant ce qui se passe lorsque vous appelez une fonction sur un fichier struct. Dans ce cas, c'est comme misterStructobtenir une commande telle que changeYourHairBlue(). Alors le bureau de poste donne l'instruction misterStruct"d'aller changer vos cheveux en bleu et me dire quand vous avez terminé."

S'il suit la même routine qu'avant, s'il fait ce qu'il a fait lorsque la variable a été modifiée directement, ce qu'il misterStructva faire est de quitter sa propre maison et d'appeler une nouvelle personne aux cheveux bleus. Mais c'est ça le problème.

L'ordre était "allez changer vos cheveux en bleu et dites-moi quand vous avez terminé", mais c'est le gars vert qui a reçu cet ordre. Une fois que le gars bleu a emménagé, une notification "travail terminé" doit encore être renvoyée. Mais le gars bleu n'en sait rien.

[Pour vraiment contrarier cette analogie, quelque chose d'horrible, ce qui est techniquement arrivé à un gars aux cheveux verts, c'est qu'après son déménagement, il s'est immédiatement suicidé. Donc , il ne peut en aviser quiconque que la tâche est terminée , soit! ]

Pour éviter ce problème, dans des cas comme celui-ci uniquement , Swift doit se rendre directement à la maison à cette adresse et changer les cheveux de l'habitant actuel . C'est un processus complètement différent de celui d'envoyer simplement un nouveau gars.

Et c'est pourquoi Swift veut que nous utilisions le mutatingmot - clé!

Le résultat final ressemble à 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 fait complètement différents. On dirait que ça fait la même chose, mais ça fait une chose très différente. Cela fait quelque chose que les structures Swift en général ne font jamais.

Donc, pour donner un peu d'aide au pauvre compilateur, et ne pas l'obliger à déterminer si une fonction mute structou non, seule, pour chaque fonction struct, on nous demande d'avoir pitié et d'utiliser le mutatingmot - clé.

En substance, pour aider Swift à rester rapide, nous devons tous faire notre part. :)

ÉDITER:

Hey mec / dudette qui m'a refusé, je viens de réécrire complètement ma réponse. Si cela vous convient mieux, supprimerez-vous le vote défavorable?

Le Mot Juiced
la source
1
C'est tellement <f-word-here> que c'est incroyablement écrit! Donc, si facile à comprendre. J'ai parcouru Internet pour cela et voici la réponse (enfin, sans avoir à passer par le code source de toute façon). Merci beaucoup d'avoir pris le temps de l'écrire.
Vaibhav
1
Je veux juste confirmer que je comprends bien: pouvons-nous dire que, lorsqu'une propriété d'une instance Struct est directement modifiée dans Swift, cette instance de Struct est "terminée" et une nouvelle instance avec une propriété modifiée est "créée" Dans les coulisses? Et lorsque nous utilisons une méthode de mutation dans l'instance pour modifier la propriété de cette instance, ce processus de terminaison et de réinitialisation n'a pas lieu?
Ted
@Teo, j'aimerais pouvoir répondre définitivement, mais le mieux que je puisse dire, c'est que j'ai passé cette analogie avec un développeur Swift réel, et ils ont dit "c'est fondamentalement exact", ce qui implique qu'il y a plus à cela dans un sens technique. Mais avec cette compréhension - qu'il y a plus que cela - au sens large, oui, c'est ce que dit cette analogie.
Le Mot Juiced
8

Les structures Swift peuvent être instanciées sous forme de constantes (via let) ou de variables (via var)

Considérez la Arraystructure 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'ajout n'a-t-il pas fonctionné avec les noms de planètes? Parce que append est marqué avec le mutatingmot - clé. Et depuis qu'il a planetNamesété déclaré en utilisant let, toutes les méthodes ainsi marquées sont interdites.

Dans votre exemple, le compilateur peut indiquer que vous modifiez la structure en affectant une ou plusieurs de ses propriétés en dehors d'un fichier init. Si vous modifiez votre code un peu , vous verrez que xet yne sont pas toujours accessibles en dehors de la struct. Remarquez le letsur la première ligne.

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

Prenons une analogie avec C ++. Une méthode struct en Swift étant mutating/ non- mutatingest analogue à une méthode en C ++ étant non- const/ const. De même, une méthode marquée consten C ++ ne peut pas muter la structure.

Vous pouvez modifier une variable depuis l'extérieur d'une structure, mais vous ne pouvez pas la modifier à partir de ses propres méthodes.

En C ++, vous pouvez également "modifier une variable depuis l'extérieur de la structure" - mais uniquement si vous avez une constvariable non struct. Si vous avez une constvariable struct, vous ne pouvez pas l'assigner à un var, et vous ne pouvez pas non plus appeler une non- constméthode. De même, dans Swift, vous ne pouvez modifier une propriété d'un struct que si la variable struct n'est pas une constante. Si vous avez une constante struct, vous ne pouvez pas affecter à une propriété et vous ne pouvez pas non plus appeler une mutatingméthode.

newacct
la source
1

Je me suis demandé la même chose lorsque j'ai commencé à apprendre Swift, et chacune de ces réponses, tout en ajoutant peut-être quelques idées, est un peu verbeuse et déroutante en soi. Je pense que la réponse à votre question est en fait assez simple…

La méthode de mutation définie dans votre structure veut la permission de modifier chaque instance d'elle-même qui sera jamais créée à l'avenir. Que faire si l'une de ces instances est affectée à une constante immuable avec let? Oh 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 êtes obligé d'être explicite lorsque vous voulez donner à une méthode d'instance ce type de pouvoir.

En revanche, la définition d'une propriété depuis l' extérieur de votre structure fonctionne sur une instance connue de cette structure. S'il est attribué à une constante, Xcode vous en informera au moment où vous avez tapé l'appel de méthode.

C'est l'une des choses que j'aime chez Swift au fur et à mesure que je commence à l'utiliser: être alerté des erreurs lorsque je les saisis. Bien sûr, bat le diable du dépannage de bogues JavaScript obscurs!

Kal
la source
1

SWIFT: utilisation de la fonction de mutation dans Structs

Les programmeurs Swift ont développé Structs de telle sorte que ses propriétés ne puissent pas être modifiées dans les méthodes Struct. Par exemple, vérifiez le code ci-dessous

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)

Lors de l'exécution du code ci-dessus, nous obtenons une erreur car nous essayons d'attribuer une nouvelle valeur à la population de propriétés de Struct City. Par défaut, les propriétés Structs ne peuvent pas être mutées dans ses propres méthodes. C'est ainsi que les développeurs Apple l'ont construit, de sorte que les Structs aient un caractère statique par défaut.

Comment pouvons-nous le résoudre? Quelle est l'alternative?

Mot-clé mutant:

Déclarer une fonction comme mutante à l'intérieur de Struct nous permet de modifier les propriétés dans Structs. Ligne n °: 5, du code ci-dessus change comme ceci,

mutating changePopulation(newpopulation : Int)

Nous pouvons maintenant attribuer la valeur de la nouvelle population à la population de propriétés dans le cadre de la méthode.

Remarque :

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

Si nous utilisons let au lieu de var pour les objets Struct, alors nous ne pouvons muter la valeur d'aucune propriété.C'est également la raison pour laquelle nous obtenons une erreur lorsque nous essayons d'appeler une fonction de mutation en utilisant l'instance de let. Il est donc préférable d'utiliser var chaque fois que vous modifiez la valeur d'une propriété.

J'adorerais entendre vos commentaires et vos pensées… ..

sDev
la source