Стрімка і мутуюча структура


96

Щось я не зовсім розумію, коли мова заходить про мутацію типів значень у Swift.

Як зазначено в iBook "Мова швидкого програмування" iBook: За замовчуванням властивості типу значення не можуть бути змінені в межах його методів екземпляра.

Тому, щоб зробити це можливим, ми можемо оголосити методи з mutatingключовим словом всередині structs і enums.

Річ, яка мені не зовсім зрозуміла, полягає в наступному: Ви можете змінити var з-поза структури, але ви не можете змінити його з його власних методів. Мені це здається протиінтуїтивним, оскільки в об’єктно-орієнтованих мовах ви, як правило, намагаєтесь інкапсулювати змінні, щоб їх можна було змінити лише зсередини. Зі структурами це, здається, навпаки. Для детальної роботи, ось фрагмент коду:

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) 

Хто-небудь знає причину, чому структури не можуть змінювати свій вміст усередині власного контексту, тоді як вміст легко змінювати в іншому місці?

Відповіді:


80

Атрибут змінності позначається на сховищі (константному чи змінному), а не на типі. Ви можете думати, що структура має два режими: змінний та незмінний . Якщо ви призначите значення структури незмінному сховищу (ми називаємо це letабо константа в Swift), значення переходить у незмінний режим, і ви не можете змінити будь-який стан у цьому значенні. (включаючи виклик будь-якого методу мутування)

Якщо значення присвоєно змінному сховищу (ми називаємо його varабо змінну в Swift), ви можете змінювати стан їх, і виклик методу мутації дозволено.

Крім того, класи не мають цього незмінного / змінного режиму. IMO, це тому, що класи, як правило, використовуються для представлення референтної сутності. А сутність, яку можна посилатись, зазвичай мінлива, оскільки дуже важко створювати та керувати графіками посилань на сутності незмінним чином з належним виконанням. Вони можуть додати цю функцію пізніше, але не принаймні зараз.

Для програмістів Objective-C поняття, що змінюються / незмінні, дуже добре знайомі. У Objective-C у нас були два окремі класи для кожної концепції, але в Swift ви можете зробити це за допомогою однієї структури. Половина роботи.

Для програмістів на C / C ++ це також дуже звичне поняття. Це саме те, що constроблять ключові слова в C / C ++.

Крім того, незмінне значення можна дуже приємно оптимізувати. Теоретично, компілятор Swift (або LLVM) може виконувати елісію копіювання над переданими значеннями let, як у C ++. Якщо розумно використовувати незмінну структуру, вона перевершить перераховані класи.

Оновлення

Оскільки @Joseph стверджував, що це не означає, чому , я додаю трохи більше.

Структури мають два типи методів. прості та мутуючі методи. Простий метод передбачає незмінність (або немутацію) . Цей поділ існує лише для підтримки незмінної семантики. Об'єкт у незмінному режимі не повинен взагалі змінювати свій стан.

Тоді незмінні методи повинні гарантувати цю семантичну незмінність . Що означає, що це не повинно змінювати жодного внутрішнього значення. Тож компілятор забороняє будь-які зміни стану самого себе незмінним методом. На відміну від цього, мутуючі методи можуть вільно змінювати стани.

І тоді, у вас може виникнути питання, чому незмінним є за замовчуванням? Це тому, що дуже важко передбачити майбутній стан мутуючих значень, і це, як правило, стає основним джерелом головного болю та помилок. Багато людей погодились, що рішення дозволяє уникнути змінних продуктів, а потім незмінним за замовчуванням десятиліттями займав перше місце серед бажань у мовах сімейства C / C ++ та його похідних.

Докладніше див. У чисто функціональному стилі . У будь-якому випадку, нам все ще потрібні змінні матеріали, оскільки незмінні речі мають деякі слабкі сторони, і дискусія про них, здається, не в темі.

Я сподіваюся, що це допомагає.


2
Отже, в основному я можу інтерпретувати це так: Структура не знає заздалегідь, чи буде вона змінною чи незмінною, тому вона передбачає незмінність, якщо не зазначено інакше. Бачимо, що це справді має сенс. Це правильно?
Mike Seghers

1
@MikeSeghers Я думаю, що це має сенс.
eonil

3
Хоча це на сьогодні найкращий з двох відповідей, я не думаю, що він відповідає ЧОМУ, за замовчуванням властивості типу значення не можуть бути змінені в межах його методів екземпляра. ви натякаєте, що це тому, що за замовчуванням має незмінну структуру, але я не думаю, що це правда
Джозеф Найт

4
@JosephKnight Це не те, що структурує за замовчуванням незмінним. Це те, що методи структур за замовчуванням є незмінними. Подумайте про це як про C ++ із зворотним припущенням. У методах C ++ за замовчуванням не константним це, вам потрібно явно додати constметод до методу, якщо ви хочете, щоб він мав значення "const this" і дозволявся для постійних екземплярів (звичайні методи не можуть використовуватися, якщо екземпляр const). Swift робить те саме з протилежним за замовчуванням: метод має за замовчуванням "const this", і ви позначаєте його інакше, якщо це має бути заборонено для постійних екземплярів.
Аналоговий файл

3
@golddove Так, а ти не можеш. Ти не повинен. Вам завжди потрібно зробити або вивести нову версію значення, і ваша програма повинна бути розроблена таким чином, щоб навмисно прийняти цей спосіб. Функція існує для запобігання такій випадковій мутації. Знову ж таки, це одна з основних концепцій програмування функціонального стилю .
eonil

25

Структура - це об’єднання полів; якщо конкретний екземпляр структури є змінним, його поля будуть змінними; якщо екземпляр незмінний, його поля будуть незмінними. Таким чином, тип структури повинен бути підготовлений до можливості того, що поля будь-якого конкретного екземпляра можуть бути змінними або незмінними.

Для того, щоб метод структури мутував поля базової структури, ці поля повинні бути змінними. Якщо до незмінюваної структури викликається метод, що мутує поля основної структури, він спробує мутувати незмінні поля. Оскільки з цього нічого доброго не могло вийти, таке закликання потрібно забороняти.

Для цього Swift розділяє структурні методи на дві категорії: ті, що модифікують базову структуру, і, отже, можуть бути викликані лише в екземплярах змінної структури, і ті, які не модифікують базову структуру, і, отже, повинні бути викликані як на змінних, так і на незмінних екземплярах . Останнє використання, мабуть, найчастіше, і, отже, є типовим.

Для порівняння, .NET в даний час (все ще!) Не пропонує засобів розрізнення структурних методів, які змінюють структуру від тих, які цього не роблять. Натомість виклик методу структури на незмінному екземплярі структури призведе до того, що компілятор зробить змінну копію екземпляра структури, дозволить методу робити все, що він хоче, і відкине копію, коли метод буде зроблено. Це призводить до того, що компілятор змушує витрачати час на копіювання структури незалежно від того, модифікує її метод, навіть якщо додавання операції копіювання майже ніколи не перетворить семантично неправильний код на семантично правильний код; це просто призведе до того, що код, який є семантично помилковим в одному напрямку (модифікуючи "незмінне" значення), буде помилковим по-іншому (дозволяючи коду думати, що він змінює структуру, але відкидаючи спроби змін). Дозвіл методів struct вказати, чи будуть вони модифікувати базову структуру, може усунути необхідність у марній операції копіювання, а також гарантує, що спроба помилкового використання буде позначена.


1
Порівняння з .NET та приклад, який не має мутуючого ключового слова, зробив це для мене зрозумілим. Міркування, чому мутуюче ключове слово створить якусь користь.
Бінаріан

19

Застереження: терміни неспеціаліста попереду.

Це пояснення не є суворо правильним на найритузнішому рівні коду. Однак це було розглянуто хлопцем, який насправді працює над Свіфтом, і він сказав, що це досить добре як основне пояснення.

Тому я хочу спробувати просто і прямо відповісти на питання "чому".

Якщо бути точним: чому ми повинні позначати функції struct як такі, mutatingколи ми можемо змінювати параметри struct без будь-яких змін ключових слів?

Отже, загальна картина, вона багато в чому пов’язана з філософією, яка підтримує Свіфта стрімким.

Ви можете подумати про це як про проблему управління фактичними фізичними адресами. Коли ви змінюєте свою адресу, якщо багато людей мають вашу поточну, ви повинні повідомити всіх про те, що ви переїхали. Але якщо у вас немає поточної адреси, ви можете просто переїхати куди завгодно, і ніхто не повинен цього знати.

У цій ситуації Свіфт схожий на пошту. Якщо багато людей з великою кількістю контактів багато рухаються, це має дуже високі накладні витрати. Доводиться платити великому штату людей, щоб обробляти всі ці повідомлення, і процес забирає багато часу та зусиль. Ось чому ідеальний штат Свіфта - це те, що кожен у своєму місті має якомога менше контактів. Тоді йому не потрібен великий персонал для обробки змін адреси, і він може робити все інше швидше та якісніше.

Ось чому Swift-люди всі роздумують про типи значень проти стандартних типів. За своєю природою посилальні типи набирають "контактів" повсюдно, і типам цінностей зазвичай не потрібно більше пари. Типи значень - "швидкі" -er.

Отже , повернемося до маленької зображенні: structs. Структури - це велика справа в Swift, оскільки вони можуть робити більшість речей, які можуть робити об’єкти, але вони є типами цінностей.

Давайте продовжимо аналогію фізичної адреси, уявляючи собі, misterStructщо живе someObjectVille. Тут аналогія трохи підмотується, але я думаю, що це все одно корисно.

Тож для моделювання зміни змінної на struct, скажімо, misterStructмає зелене волосся і отримує наказ перейти на блакитне волосся. Як я вже говорив, аналогія підморгує, але відбувається щось на зразок того, що замість того, щоб змінити misterStructволосся, стара людина виїжджає, а нова людина з синім волоссям заходить, і ця нова людина починає називати себе misterStruct. Нікому не потрібно отримувати повідомлення про зміну адреси, але якщо хтось подивиться на цю адресу, то побачить хлопця з блакитним волоссям.

Тепер давайте змоделюємо, що відбувається, коли ви викликаєте функцію на struct. У цьому випадку це як misterStructотримати замовлення, наприклад changeYourHairBlue(). Тож пошта видає вказівку misterStruct«йди переодягни волосся в блакитне і скажи мені, коли закінчиш».

Якщо він дотримується тієї самої процедури, що і раніше, якщо він робить те, що робив, коли змінну змінювали безпосередньо, то misterStructбуде рухатися з власного будинку та закликати нову людину з блакитним волоссям. Але в цьому проблема.

Наказ був: «піди переодягни волосся на блакитне і скажи мені, коли закінчиш», але це зелений хлопець отримав це замовлення. Після того, як блакитний хлопець переїжджає, сповіщення про "роботу виконано" все одно потрібно надіслати назад. Але блакитний хлопець про це нічого не знає.

[Щоб насправді підняти цю аналогію чимось жахливим, технічно те, що трапилось із зеленоволосою хлопцею, було те, що після його виїзду він негайно покінчив життя самогубством. Тож він не може нікого повідомити, що завдання теж виконано ! ]

Щоб уникнути цієї проблеми, у випадках , як це тільки , Swift повинен йти безпосередньо до будинку за цією адресою і фактично змінити волосся поточного мешканця . Це зовсім інший процес, ніж просто надсилання нового хлопця.

І тому Свіфт хоче, щоб ми використовували mutatingключове слово!

Кінцевий результат виглядає однаковим для будь-чого, що має стосуватися структури: мешканець будинку тепер має сині волосся. Але процеси її досягнення насправді абсолютно різні. Схоже, він робить одне і те ж, але робить зовсім інше. Це робить те, чого Swift загалом ніколи не робить.

Тож, щоб надати бідному компілятору невелику допомогу, а не змушувати його з’ясовувати, чи функція мутує structчи ні, сама по собі для кожної окремої функції структури, нас просять пожаліти і використовувати mutatingключове слово.

По суті, щоб допомогти Свіфту залишатися стрімким, ми всі повинні зробити все, що від нас залежить. :)

Редагувати:

Гей, чувак / дудет, хто мене проголосував, я просто повністю переписав свою відповідь. Якщо вам це буде сидіти краще, ви знімете голос проти?


1
Це так <f-word-here> дивовижно написано! Отже, так легко зрозуміти. Я шукав це в Інтернеті, і ось відповідь (ну, без необхідності переглядати вихідний код у будь-якому випадку). Велике спасибі, що знайшли час написати його.
Вайбхав

1
Я просто хочу підтвердити, що я це правильно розумію: чи можемо ми сказати, що коли властивість екземпляра Struct змінюється безпосередньо в Swift, цей екземпляр Struct "припиняється" і створюється новий екземпляр із зміненим властивістю поза сценою? І коли ми використовуємо мутуючий метод всередині екземпляра для зміни властивості цього екземпляра, цей процес припинення та повторної ініціалізації не відбувається?
Тед,

@Teo, я би хотів, щоб міг дати остаточну відповідь, але найкраще, що я можу сказати, це те, що я провів цю аналогію повз фактичного розробника Swift, і вони сказали: "це в основному точно", маючи на увазі, що в технічному сенсі є щось більше. Але з цим розумінням - що тут не лише це - у широкому розумінні, так, саме про це говорить ця аналогія.
Le Mot Juiced

8

Стрімкі структури можна створити як константи (через let) або як змінні (через var)

Розглянемо Arrayструктуру Свіфта (так, це структура).

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

Чому додаток не працював з назвами планет? Оскільки додаток позначено mutatingключовим словом. І оскільки planetNamesбуло оголошено про використання let, усі позначені таким чином методи виходять за межі обмежень.

У вашому прикладі компілятор може сказати, що ви модифікуєте структуру, присвоївши одне або декілька її властивостей поза init. Якщо ви трохи зміните свій код, ви побачите це, xі yвони не завжди доступні поза структурою. Зверніть увагу letна перший рядок.

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

5

Розглянемо аналогію із C ++. Структурний метод у Swift being mutating/ not- mutatingє аналогом методу в C ++, який не є const/ const. Метод, позначений constна C ++, аналогічним чином не може мутувати структуру.

Ви можете змінити var за межами структури, але ви не можете змінити його із власних методів.

У C ++ ви також можете "змінити var поза межами структури", - але лише якщо у вас є constнеструктурна змінна. Якщо у вас є constзмінна struct, ви не можете призначити змінну, а також не можете викликати неметод const. Подібним чином у Swift ви можете змінити властивість структури лише в тому випадку, якщо змінна struct не є константою. Якщо у вас є константа struct, ви не можете призначити властивість, а також не можете викликати mutatingметод.


1

Я дивувався тому самому, коли я починав вчитися Свіфта, і кожна з цих відповідей, хоча й додає деякі ідеї, якась багатослівна і заплутана сама по собі. Я думаю, що відповідь на ваше запитання насправді досить проста ...

Метод мутації, визначений у вашій структурі, вимагає дозволу змінювати кожен екземпляр себе, який коли-небудь буде створений у майбутньому. Що робити, якщо один із цих випадків отримує незмінну константу з let? Ой-ой. Щоб захистити вас від себе (і дати редактору та компілятору зрозуміти, що ви намагаєтесь зробити), ви змушені бути явними, коли хочете надати методу екземпляра такий тип влади.

Навпаки, установка властивості поза вашої структури працює на відомому екземплярі цієї структури. Якщо він присвоєний константі, Xcode повідомить вас про це в той момент, коли ви введете виклик методу.

Це одна з речей, які я люблю в Swift, коли я починаю використовувати його більше - попереджаю про помилки під час їх набору. Звичайно, це неможливо вирішити незрозумілі помилки JavaScript!


1

SWIFT: Використання функції мутування у структурах

Програмісти Swift розробили Structs таким чином, що властивості його не можуть бути змінені за допомогою методів Struct. Наприклад, перевірте код, наведений нижче

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)

При виконанні вищезазначеного коду ми отримуємо помилку, оскільки ми намагаємося призначити нове значення для сукупності майна міста Структура. За замовчуванням властивості Structs не можуть мутувати всередині власних методів. Ось як його створили розробники Apple, так що структури за замовчуванням матимуть статичний характер.

Як ми це вирішуємо? Яка альтернатива?

Мутуюче ключове слово:

Оголошення функції як мутуючої всередині Struct дозволяє нам змінювати властивості в Structs. Рядок No: 5 із зазначеного вище коду змінюється на такий,

mutating changePopulation(newpopulation : Int)

Тепер ми можемо призначити значення новогопопуляції для сукупності майна в межах обсягу методу.

Примітка :

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

Якщо ми використовуємо let замість var для об’єктів Struct, тоді ми не можемо мутувати значення будь-яких властивостей. Крім того, це причина, через яку ми отримуємо помилку, коли намагаємося викликати функцію мутації за допомогою екземпляра let. Тож краще використовувати var, коли ви змінюєте значення властивості.

Хотілося б почути ваші коментарі та думки ... ..

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.