Hızlı ve mutasyona uğramış yapı


101

Swift'deki değer türlerini değiştirmek söz konusu olduğunda tamamen anlamadığım bir şey var.

"Swift Programlama Dili" iBook'un belirttiği gibi: Varsayılan olarak, bir değer türünün özellikleri, kendi örnek yöntemleri içinden değiştirilemez.

Ve böylece bunu mümkün kılmak için, mutatingstructs ve enums içindeki anahtar kelimeyi içeren yöntemleri bildirebiliriz .

Benim için tam olarak açık olmayan şey şudur: Bir yapının dışından bir varyantı değiştirebilirsiniz, ancak onu kendi yöntemlerinden değiştiremezsiniz. Bu bana karşı sezgisel görünüyor, Nesne Yönelimli dillerde olduğu gibi, genellikle değişkenleri yalnızca içeriden değiştirilebilecek şekilde kapsüllemeye çalışıyorsunuz. Yapılar söz konusu olduğunda bu durum tam tersi gibi görünüyor. Ayrıntılı olarak açıklamak için, bir kod parçacığı:

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) 

İçerikler başka bir yerde kolayca değiştirilebilirken, yapıların neden içeriklerini kendi bağlamları içinden değiştiremeyeceğini bilen var mı?

Yanıtlar:


85

Değişebilirlik niteliği bir tipte değil, bir depolama alanında (sabit veya değişken) işaretlenir. Yapının iki modu olduğunu düşünebilirsiniz: değişken ve değişmez . Değişmez bir depolamaya bir yapı değeri atarsanız (biz buna Swift'de letveya sabit diyoruz ), değer değişmez kip olur ve değerdeki herhangi bir durumu değiştiremezsiniz. (herhangi bir mutasyon yönteminin çağrılması dahil)

Değer bir değişken depolama (biz diyoruz atanmışsa varveya değişken Swift), bunlardan durumunu değiştirmek için özgürsünüz ve yöntemi mutasyona ait çağıran izin verilir.

Ek olarak, sınıflar bu değişmez / değiştirilebilir moda sahip değildir. IMO, bunun nedeni sınıfların genellikle referans verilebilir varlığı temsil etmek için kullanılmasıdır . Ve referans alınabilen varlık genellikle değişebilir çünkü varlıkların referans grafiklerini uygun performansla değişmez bir şekilde yapmak ve yönetmek çok zordur. Bu özelliği daha sonra ekleyebilirler, ancak en azından şimdi değil.

Objective-C programcıları için değişken / değişmez kavramlar çok tanıdık geliyor. Objective-C'de her konsept için iki ayrı sınıfımız vardı, ancak Swift'de bunu tek bir yapı ile yapabilirsiniz. Yarım iş.

C / C ++ programcıları için bu aynı zamanda çok tanıdık bir kavramdır. Bu tam olarak constanahtar kelimenin C / C ++ 'da yaptığı şeydir .

Ayrıca, değişmez değer çok güzel bir şekilde optimize edilebilir. Teoride, Swift derleyicisi (veya LLVM), letC ++ 'da olduğu gibi, geçen değerler üzerinde kopya-eleme gerçekleştirebilir . Eğer immutable yapıyı akıllıca kullanırsanız, yeniden hesaplanan sınıflardan daha iyi performans gösterecektir.

Güncelleme

@Joseph bu sağlamaz iddia gibi neden , biraz daha ekliyorum.

Structs'ın iki tür yöntemi vardır. düz ve mutasyon yöntemleri. Düz yöntem, değişmez (veya mutasyona uğramayan) anlamına gelir . Bu ayrım yalnızca değişmez anlambilimini desteklemek için vardır . Değişmez moddaki bir nesne, durumunu hiç değiştirmemelidir.

Daha sonra, değişmez yöntemler bu anlamsal değişmezliği garanti etmelidir . Bu, herhangi bir dahili değeri değiştirmemesi gerektiği anlamına gelir. Dolayısıyla derleyici, değişmez bir yöntemde kendisinin herhangi bir durum değişikliğine izin vermez. Buna karşılık, mutasyon yöntemleri durumları değiştirmekte özgürdür.

Ve sonra, neden varsayılan değerin değişmez olduğuna dair bir sorunuz olabilir ? Bunun nedeni, değişen değerlerin gelecekteki durumunu tahmin etmenin çok zor olmasıdır ve bu genellikle baş ağrılarının ve hataların ana kaynağı haline gelir. Pek çok insan çözümün değişken şeylerden kaçınmak olduğu konusunda hemfikirdi ve daha sonra varsayılan olarak değişmez , C / C ++ aile dillerinde ve türevlerinde onlarca yıldır istek listesinin başında yer aldı.

Daha fazla ayrıntı için tamamen işlevsel stile bakın. Her neyse, hala değiştirilebilir şeylere ihtiyacımız var çünkü değişmez şeylerin bazı zayıf yönleri var ve bunlar hakkında tartışmak konu dışı görünüyor.

Umarım bu yardımcı olur.


2
Yani temelde, bunu şöyle yorumlayabilirim: Bir yapı, değişken mi yoksa değişmez mi olacağını önceden bilmez, bu yüzden farklı şekilde belirtilmedikçe değişmezliği varsayar. Böyle görüldüğünde gerçekten mantıklı. Bu doğru mu?
Mike Seghers

1
@MikeSeghers Bunun mantıklı olduğunu düşünüyorum.
eonil

3
Şimdiye kadar iki cevap arasında en iyisi bu olsa da, NEDEN yanıtladığını sanmıyorum, varsayılan olarak bir değer türünün özellikleri kendi örnek yöntemlerinden değiştirilemez. Eğer yapılar değişmez varsayılan çünkü onun o ipuçları yapmak, ama doğrudur sanmıyorum
Joseph Şövalye

4
@JosephKnight Bu yapılar varsayılan olarak immutable değil. Bu, yapı yöntemlerinin varsayılan olarak değişmez olmasıdır. Ters varsayımla C ++ gibi düşünün. C ++ yöntemlerinde buna sabit olmayan varsayılan olarak const, bir 'const this' olmasını ve sabit örneklerde izin verilmesini istiyorsanız yönteme açıkça bir eklemeniz gerekir (örnek const ise normal yöntemler kullanılamaz). Swift, ters varsayılanla aynı şeyi yapar: bir yöntemin varsayılan olarak bir 'const this' vardır ve sabit örnekler için yasaklanması gerekiyorsa, aksi takdirde işaretlersiniz.
Analog Dosya

3
@golddove Evet, yapamazsınız. Yapmamalısın. Her zaman değerin yeni bir versiyonunu oluşturmanız veya türetmeniz gerekir ve programınızın kasıtlı olarak bu yolu benimsemek üzere tasarlanması gerekir. Bu özellik, bu tür kazara mutasyonu önlemek için mevcuttur. Yine, bu, fonksiyonel stil programlamanın temel konseptlerinden biridir .
eonil

26

Yapı, alanların toplamıdır; belirli bir yapı örneği değiştirilebilirse, alanları değiştirilebilir; bir örnek değişmez ise, alanları değişmez olacaktır. Bu nedenle, herhangi bir özel durumun alanlarının değiştirilebilir veya değişmez olma olasılığı için bir yapı tipi hazırlanmalıdır.

Bir yapı yönteminin temeldeki yapının alanlarını değiştirmesi için bu alanların değiştirilebilir olması gerekir. Temel yapının alanlarını değiştiren bir yöntem, değişmez bir yapı üzerinde çağrılırsa, değişmez alanları değiştirmeye çalışır. Bundan iyi bir şey gelemeyeceğine göre, böyle bir çağrı yasaklanmalıdır.

Bunu başarmak için Swift, yapı yöntemlerini iki kategoriye ayırır: temel yapıyı değiştirenler ve bu nedenle yalnızca değiştirilebilir yapı örneklerinde çağrılabilenler ve temel yapıyı değiştirmeyenler ve bu nedenle hem değişken hem de değişmez örneklerde çağrılabilir olmalıdır. . İkinci kullanım muhtemelen daha sıktır ve bu nedenle varsayılandır.

Karşılaştırıldığında, .NET şu anda (hala!) Yapıyı değiştirmeyen yapı yöntemlerini değiştirmeyenlerden ayırt etme yolu sunmuyor. Bunun yerine, değişmez bir yapı örneğinde bir yapı yöntemini çağırmak, derleyicinin yapı örneğinin değiştirilebilir bir kopyasını oluşturmasına, yöntemin onunla istediğini yapmasına izin vermesine ve yöntem bittiğinde kopyayı atmasına neden olur. Bu, derleyiciyi, yöntem onu ​​değiştirsin ya da değiştirmesin yapıyı kopyalamakla zaman kaybetmeye zorlama etkisine sahiptir, ancak kopyalama işleminin eklenmesi anlamsal olarak yanlış olan kodu semantik olarak doğru koda dönüştürmeyecektir; yalnızca anlamsal olarak yanlış olan kodun ("değişmez" bir değeri değiştirerek) farklı bir şekilde yanlış olmasına neden olur (kodun bir yapıyı değiştirdiğini düşünmesine izin verir, ancak denenen değişiklikleri göz ardı ederek). Yapı yöntemlerinin temel yapıyı değiştirip değiştirmeyeceğini belirtmesine izin vermek, gereksiz bir kopyalama işlemi ihtiyacını ortadan kaldırabilir ve ayrıca hatalı kullanım girişiminin işaretlenmesini sağlar.


1
.NET ile yapılan karşılaştırma ve mutating anahtar sözcüğüne sahip olmayan bir örnek bana bunu açıkladı. Değişen anahtar kelimenin neden herhangi bir fayda yaratacağına dair mantık.
Binarian

22

Dikkat: layman'ın şartları önde.

Bu açıklama, en nitty-cesur kod düzeyinde kesinlikle doğru değildir. Ancak, Swift üzerinde çalışan bir adam tarafından incelendi ve temel bir açıklama olarak yeterince iyi olduğunu söyledi.

Bu yüzden "neden" sorusuna basitçe ve doğrudan cevap vermeye çalışmak istiyorum .

Kesin olmak gerekirse: neden struct işlevlerini mutatinganahtar sözcükleri değiştirmeden yapı parametrelerini değiştirebileceğimiz zamanki gibi işaretlememiz gerekiyor ?

Yani, büyük resmin Swift'i hızlı tutan felsefeyle çok ilgisi var .

Bunu gerçek fiziksel adresleri yönetme sorunu gibi düşünebilirsiniz. Adresinizi değiştirdiğinizde, mevcut adresinize sahip çok sayıda insan varsa, hepsini taşındığınızı bildirmeniz gerekir. Ancak, kimse mevcut adresinize sahip değilse, istediğiniz yere gidebilirsiniz ve kimsenin bilmesi gerekmez.

Bu durumda Swift bir nevi postane gibidir. Çok sayıda kişiyle çok sayıda insan çok hareket ederse, bu gerçekten yüksek bir ek yüke sahiptir. Tüm bu bildirimleri ele almak için büyük bir personele ödeme yapması gerekiyor ve süreç çok zaman ve çaba gerektiriyor. İşte bu yüzden Swift'in ideal durumu, kasabasındaki herkesin olabildiğince az bağlantıya sahip olmasıdır. O zaman adres değişikliklerini ele almak için büyük bir kadroya ihtiyaç duymaz ve diğer her şeyi daha hızlı ve daha iyi yapabilir.

Swift halkının referans türlerine karşı değer türleri hakkında öfkelenmesinin nedeni de budur. Doğası gereği, referans türleri her yerde "kişileri" toplar ve değer türleri genellikle birkaç taneden fazlasına ihtiyaç duymaz. Değer türleri "Swift" dir.

Küçük resme geri So: structs. Yapılar, Swift'de önemli bir konudur çünkü nesnelerin yapabileceği şeylerin çoğunu yapabilirler, ancak değer türleridir.

Fiziksel adres benzetmesine misterStructiçinde yaşayan bir hayal ederek devam edelim someObjectVille. Analoji burada biraz yanıltıcı olsa da yine de yardımcı olduğunu düşünüyorum.

Öyleyse, bir değişkeni değiştirmeyi modellemek için struct, diyelim ki misterStructyeşil saç var ve mavi saça geçme emri alıyor. Benzetme, dediğim gibi çıldırıyor, ama olan şu ki misterStruct, saçını değiştirmek yerine , yaşlı kişi dışarı çıkıyor ve mavi saçlı yeni bir kişi içeri giriyor ve bu yeni kişi kendini aramaya başlıyor misterStruct. Kimsenin adres değişikliği bildirimi almasına gerek yoktur, ancak biri bu adrese bakarsa mavi saçlı bir adam görür.

Şimdi, a'da bir işlevi çağırdığınızda ne olacağını modelleyelim struct. Bu durumda, gibi misterStructbir emir alır gibi changeYourHairBlue(). Bu yüzden postane misterStruct"gidip saçını maviye çevir ve işin bittiğinde bana söyle" talimatını veriyor .

Daha önce olduğu gibi aynı rutini takip ediyorsa, doğrudan değişken değiştirildiğinde yaptığı şeyi yapıyorsa misterStruct, yapacağı şey kendi evinden çıkıp mavi saçlı yeni bir insanı aramaktır. Ama sorun bu.

Emir "git saçını maviye değiştir ve işin bittiğinde bana söyle" idi, ama bu emri alan yeşil adam. Mavi adam eve taşındıktan sonra, "iş tamamlandı" bildiriminin yine de geri gönderilmesi gerekiyor. Ama mavi adam bunun hakkında hiçbir şey bilmiyor.

[Bu benzetmeyi gerçekten uyandırmak için korkunç bir şey, teknik olarak yeşil saçlı adama olan şey, taşındıktan sonra hemen intihar etmesiydi. Yani o kimseyi haberdar olamaz görev tam olduğunu ya! ]

Bu sorundan kaçınmak için , yalnızca bu gibi durumlarda , Swift doğrudan o adresteki eve gitmeli ve aslında mevcut sakinlerinin saçını değiştirmelidir . Bu, yeni bir adam göndermekten tamamen farklı bir süreç.

İşte bu yüzden Swift mutatinganahtar kelimeyi kullanmamızı istiyor !

Sonuç, yapıya atıfta bulunmak zorunda olan her şeye aynı görünüyor: evin sakini artık mavi saçlara sahip. Ancak bunu başarma süreçleri aslında tamamen farklıdır. Aynı şeyi yapıyor gibi görünüyor, ama çok farklı bir şey yapıyor. Swift yapısının genel olarak asla yapmadığı bir şeyi yapıyor .

Bu nedenle, zayıf derleyiciye biraz yardım etmek ve bir işlevin şimdiye kadarki her yapı işlevi içinstruct kendi başına değişip değişmediğini anlamak zorunda kalmamak için acımamız ve mutatinganahtar sözcüğü kullanmamız istenir .

Esasen, Swift'in hızlı kalmasına yardımcı olmak için hepimiz üzerimize düşeni yapmalıyız. :)


1
Bu çok <f-word-here> inanılmaz bir şekilde yazılmış! Yani anlaşılması çok kolay. Bunun için interneti taradım ve işte cevap (zaten kaynak kodunu gözden geçirmek zorunda kalmadan). Yazmak için zaman ayırdığınız için çok teşekkürler.
Vaibhav

1
Sadece doğru anladığımı onaylamak istiyorum: Swift'de bir Struct örneğinin bir özelliği doğrudan değiştirildiğinde, Struct'ın bu örneğinin "sonlandırıldığını" ve değiştirilmiş özelliğe sahip yeni bir örneğin "oluşturulduğunu" söyleyebilir miyiz? kamera ARKASI? Ve o örneğin özelliğini değiştirmek için örnek içinde bir mutasyon yöntemi kullandığımızda, bu sonlandırma ve yeniden başlatma işlemi gerçekleşmez mi?
Ted

@Teo, keşke kesin cevap verebilseydim, ancak söyleyebileceğim en iyi şey, bu benzetmeyi gerçek bir Swift geliştiricisinden geçirdim ve "bu temelde doğru" dediler, teknik anlamda daha fazlası olduğunu ima ettiler. Ama bu anlayışla - bunda bundan daha fazlası var - geniş anlamda, evet, bu benzetmenin söylediği şey bu.
Le Mot Juiced

8

Swift yapıları, sabitler (aracılığıyla let) veya değişkenler (aracılığıyla var) olarak başlatılabilir

Swift'in Arrayyapısını düşünün (evet bu bir yapı).

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

Ek neden gezegen isimleriyle çalışmadı? Çünkü append mutatinganahtar kelimesi ile işaretlenmiştir . Ve planetNameskullanılarak bildirildiğinden let, bu şekilde işaretlenen tüm yöntemler sınırsızdır.

Örneğinizde derleyici, yapının bir init. Kodunuzu biraz değiştirirseniz, bunu görürsünüz xve yyapı dışından her zaman erişilemez. letİlk satırdaki şeye dikkat edin .

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

5

C ++ ile bir benzetme düşünün. Swift varlıkta bir yapı yöntemi mutating/ değil- mutatingolmayan olmak C bir yöntem ++ benzerdir const/ ' const. constC ++ ile işaretlenmiş bir yöntem benzer şekilde yapıyı değiştiremez.

Bir yapının dışından bir değişkeni değiştirebilirsiniz, ancak onu kendi yöntemlerinden değiştiremezsiniz.

C ++ 'da, "yapının dışından bir değişkeni de değiştirebilirsiniz" - ancak yalnızcaconst yapısal olmayan bir değişkeniniz varsa. Bir conststruct değişkeniniz varsa, bir var'a atayamazsınız ve ayrıca constyöntem olmayan bir yöntemi de çağıramazsınız . Benzer şekilde, Swift'de bir yapının bir özelliğini ancak yapı değişkeni sabit değilse değiştirebilirsiniz. Bir yapı sabitiniz varsa, bir özelliğe atayamazsınız ve bir mutatingyöntemi de çağıramazsınız .


1

Swift'i öğrenmeye başladığımda da aynı şeyi merak ettim ve bu yanıtların her biri, belki de bazı bilgiler eklerken, kendi başlarına biraz sözlü ve kafa karıştırıcı. Sorunuzun cevabının aslında oldukça basit olduğunu düşünüyorum ...

Yapınızın içinde tanımlanan değiştirme yöntemi , gelecekte yaratılacak her bir örneğini değiştirmek için izin ister. Ya bu örneklerden biri ile değişmez bir sabite atanırsa let? Maalesef Sizi kendinizden korumak için (ve düzenleyicinin ve derleyicinin ne yapmaya çalıştığınızı bilmesini sağlamak için), bir örnek yöntemine bu tür bir güç vermek istediğinizde açık olmak zorundasınız.

Buna karşılık, yapınızın dışından bir özelliğin ayarlanması , bu yapının bilinen bir örneği üzerinde çalışıyor. Bir sabite atanmışsa, Xcode, yöntem çağrısına yazdığınız anda size bunu bildirir.

Swift'i daha fazla kullanmaya başladığımda sevdiğim şeylerden biri de bu - yazarken hatalar konusunda uyarılmak. Kesinlikle belirsiz JavaScript hatalarının giderilmesinden çok daha iyi!


1

SWIFT: Structs'ta mutasyon işlevinin kullanımı

Swift programcıları Structs'ı, özellikleri Struct yöntemleri içinde değiştirilemeyecek şekilde geliştirdiler. Örneğin, aşağıda verilen kodu kontrol edin

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)

Yukarıdaki kodu çalıştırırken bir hata alıyoruz çünkü Struct City'nin mülk popülasyonuna yeni bir değer atamaya çalışıyoruz. Varsayılan olarak Structs özellikleri kendi yöntemleri içinde değiştirilemez. Apple Geliştiricileri bunu böyle inşa etti, böylece Yapılar varsayılan olarak statik bir yapıya sahip olacak.

Nasıl çözeriz? Alternatif nedir?

Anahtar Kelime Değiştiriliyor:

Fonksiyonu Struct içinde mutasyon olarak bildirmek, Structs'taki özellikleri değiştirmemize izin verir. Yukarıdaki koddan Satır No: 5 şu şekilde değişir,

mutating changePopulation(newpopulation : Int)

Şimdi değerini atayabilirsiniz newpopulation özellik için nüfus yönteminin kapsamına için.

Not :

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

Struct nesneleri için var yerine let kullanırsak, o zaman herhangi bir özelliğin değerini değiştiremeyiz. Ayrıca, let örneğini kullanarak mutasyon işlevini çağırmaya çalıştığımızda hata almamızın nedeni budur. Bu nedenle, bir mülkün değerini her değiştirdiğinizde var kullanmak daha iyidir.

Yorumlarınızı ve düşüncelerinizi duymak isterim… ..

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.