Cepat dan struct bermutasi

97

Ada sesuatu yang saya tidak sepenuhnya mengerti tentang mutasi tipe nilai di Swift.

Seperti yang dinyatakan iBook "The Swift Programming Language": Secara default, properti tipe nilai tidak dapat dimodifikasi dari dalam metode instansinya.

Dan untuk membuat ini mungkin kita bisa mendeklarasikan metode dengan mutatingkata kunci di dalam struct dan enums.

Hal yang tidak sepenuhnya jelas bagi saya adalah ini: Anda dapat mengubah var dari luar struct, tetapi Anda tidak dapat mengubahnya dari metodenya sendiri. Ini tampaknya kontra-intuitif bagi saya, seperti dalam bahasa Berorientasi Objek, Anda biasanya mencoba merangkum variabel sehingga hanya dapat diubah dari dalam. Dengan struct ini tampaknya sebaliknya. Untuk menguraikan, berikut cuplikan kode:

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) 

Adakah yang tahu alasan mengapa struct tidak dapat mengubah konten mereka dari dalam konteksnya sendiri, sedangkan konten dapat dengan mudah diubah di tempat lain?

Mike Seghers
sumber

Jawaban:

81

Atribut mutabilitas ditandai pada penyimpanan (konstanta atau variabel), bukan tipe. Anda dapat berpikir struct memiliki dua mode: bisa berubah dan tidak bisa diubah . Jika Anda menetapkan nilai struct ke penyimpanan yang tidak dapat diubah (kami menyebutnya letatau konstan di Swift), nilainya menjadi mode yang tidak dapat diubah, dan Anda tidak dapat mengubah status apa pun dalam nilai. (termasuk memanggil metode mutasi apa pun)

Jika nilai ditetapkan ke penyimpanan yang bisa berubah (kami menyebutnya varatau variabel di Swift), Anda bebas untuk mengubah statusnya, dan pemanggilan metode mutasi diperbolehkan.

Selain itu, kelas tidak memiliki mode yang tidak dapat diubah / dapat diubah ini. IMO, ini karena kelas biasanya digunakan untuk mewakili entitas yang dapat direferensikan . Dan entitas yang dapat memiliki referensi biasanya dapat berubah karena sangat sulit untuk membuat dan mengelola grafik referensi dari entitas dengan cara yang tidak dapat diubah dengan kinerja yang tepat. Mereka dapat menambahkan fitur ini nanti, tetapi setidaknya tidak sekarang.

Untuk programmer Objective-C, konsep yang bisa berubah / tidak berubah sangat familiar. Di Objective-C kami memiliki dua kelas yang terpisah untuk setiap konsep, tetapi di Swift, Anda dapat melakukan ini dengan satu struct. Setengah kerja.

Untuk programmer C / C ++, ini juga merupakan konsep yang sangat familiar. Inilah yang constdilakukan kata kunci di C / C ++.

Selain itu, nilai yang tidak dapat diubah dapat dioptimalkan dengan sangat baik. Secara teori, pengompilasi Swift (atau LLVM) dapat melakukan penghapusan salinan pada nilai-nilai yang diteruskan let, seperti di C ++. Jika Anda menggunakan struct immutable dengan bijak, itu akan mengungguli kelas refcount.

Memperbarui

Karena @Joseph mengklaim ini tidak menjelaskan alasannya , saya menambahkan sedikit lagi.

Struktur memiliki dua jenis metode. metode polos dan mutasi . Metode biasa menyiratkan tidak dapat diubah (atau tidak bermutasi) . Pemisahan ini hanya ada untuk mendukung semantik yang tidak dapat diubah . Objek dalam mode yang tidak dapat diubah tidak boleh mengubah statusnya sama sekali.

Kemudian, metode yang tidak dapat diubah harus menjamin keabadian semantik ini . Artinya tidak boleh mengubah nilai internal apa pun. Jadi compiler tidak mengizinkan perubahan status apa pun dengan sendirinya dalam metode yang tidak dapat diubah. Sebaliknya, metode mutasi bebas untuk mengubah status.

Dan kemudian, Anda mungkin memiliki pertanyaan mengapa tidak berubah adalah default? Itu karena sangat sulit untuk memprediksi keadaan mutasi nilai di masa depan, dan itu biasanya menjadi sumber utama sakit kepala dan bug. Banyak orang setuju bahwa solusinya adalah menghindari hal-hal yang dapat berubah, dan kemudian tidak dapat diubah secara default berada di atas daftar keinginan selama beberapa dekade dalam bahasa keluarga C / C ++ dan turunannya.

Lihat gaya fungsional murni untuk lebih jelasnya. Bagaimanapun, kita masih membutuhkan barang yang bisa berubah karena barang yang tidak bisa diubah memiliki beberapa kelemahan, dan membahasnya sepertinya di luar topik.

Saya harap ini membantu.

eonil
sumber
2
Jadi pada dasarnya, saya dapat menafsirkannya seperti: Sebuah struct tidak tahu sebelumnya apakah itu akan dapat berubah atau tidak dapat diubah, jadi ia mengasumsikan keabadian kecuali dinyatakan berbeda. Dilihat seperti itu memang masuk akal. Apakah ini benar?
Mike Seghers
1
@MikeSeghers Saya pikir itu masuk akal.
eonil
3
Meskipun sejauh ini ini adalah yang terbaik dari dua jawaban, saya rasa ini tidak menjawab MENGAPA, secara default, properti tipe nilai tidak dapat dimodifikasi dari dalam metode instansinya. Anda membuat petunjuk bahwa itu karena struct default untuk tidak berubah, tetapi saya rasa itu tidak benar
Joseph Knight
4
@JosephKnight Bukannya struct default untuk tidak berubah. Itu adalah metode struct default untuk tidak berubah. Anggap saja seperti C ++ dengan asumsi terbalik. Dalam metode C ++ default ke non konstan this, Anda harus secara eksplisit menambahkan constmetode jika Anda ingin memiliki 'const this' dan diizinkan pada instance konstan (metode normal tidak dapat digunakan jika instance adalah const). Swift melakukan hal yang sama dengan default kebalikan: sebuah metode memiliki 'const this' secara default dan Anda menandainya sebaliknya jika harus dilarang untuk instance konstan.
File Analog
3
@ Goldove Ya, dan Anda tidak bisa. Seharusnya tidak. Anda selalu perlu membuat atau mendapatkan versi baru dari nilai tersebut, dan program Anda perlu dirancang untuk mengadopsi cara ini dengan sengaja. Fitur tersebut ada untuk mencegah mutasi yang tidak disengaja. Sekali lagi, ini adalah salah satu konsep inti dari pemrograman gaya fungsional .
eonil
25

Struktur adalah kumpulan bidang; jika contoh struktur tertentu dapat berubah, bidangnya akan dapat berubah; jika sebuah instance tidak dapat diubah, bidangnya akan tetap. Oleh karena itu, tipe struktur harus disiapkan untuk kemungkinan bahwa bidang contoh tertentu dapat berubah atau tidak berubah.

Agar metode struktur dapat mengubah bidang dari struct yang mendasari, bidang tersebut harus bisa berubah. Jika metode yang mengubah bidang dari struct yang mendasarinya dipanggil pada struktur yang tidak dapat diubah, itu akan mencoba untuk mengubah bidang yang tidak dapat diubah. Karena tidak ada hal baik yang datang dari itu, doa seperti itu perlu dilarang.

Untuk mencapai itu, Swift membagi metode struktur menjadi dua kategori: metode yang memodifikasi struktur yang mendasarinya, dan dengan demikian hanya dapat dipanggil pada contoh struktur yang dapat berubah, dan yang tidak mengubah struktur yang mendasarinya dan karenanya harus dapat dipanggil pada contoh yang dapat berubah dan tidak dapat diubah . Penggunaan terakhir mungkin lebih sering, dan dengan demikian menjadi default.

Sebagai perbandingan, .NET saat ini (masih!) Tidak menawarkan cara untuk membedakan metode struktur yang memodifikasi struktur dari yang tidak. Sebaliknya, memanggil metode struktur pada instance struktur yang tidak dapat diubah akan menyebabkan compiler membuat salinan yang dapat diubah dari instance struktur, membiarkan metode melakukan apa pun yang diinginkan dengannya, dan membuang salinan tersebut ketika metode tersebut selesai. Ini memiliki efek memaksa compiler membuang waktu untuk menyalin struktur apakah metode memodifikasinya atau tidak, meskipun menambahkan operasi penyalinan hampir tidak akan pernah mengubah apa yang semantik kode salah menjadi kode yang benar secara semantik; itu hanya akan menyebabkan kode yang salah secara semantik dalam satu cara (memodifikasi nilai "tetap") menjadi salah dengan cara yang berbeda (memungkinkan kode untuk berpikir itu memodifikasi struktur, tetapi membuang perubahan yang dicoba). Mengizinkan metode struct untuk menunjukkan apakah mereka akan memodifikasi struktur yang mendasarinya dapat menghilangkan kebutuhan akan operasi penyalinan yang tidak berguna, dan juga memastikan bahwa percobaan penggunaan yang salah akan ditandai.

supercat
sumber
1
Perbandingan dengan .NET dan contoh yang tidak memiliki kata kunci mutasi membuatnya jelas bagi saya. Alasan mengapa kata kunci yang bermutasi akan menghasilkan keuntungan.
Binarian
20

Perhatian: istilah awam di depan.

Penjelasan ini tidak sepenuhnya benar pada tingkat kode yang paling rumit. Namun itu telah ditinjau oleh seorang pria yang benar-benar bekerja pada Swift dan dia mengatakan itu cukup bagus sebagai penjelasan dasar.

Jadi saya ingin mencoba menjawab pertanyaan "mengapa" secara sederhana dan langsung .

Tepatnya: mengapa kita harus menandai fungsi struct seperti mutatingketika kita dapat mengubah parameter struct tanpa mengubah kata kunci?

Jadi, gambaran besarnya, ini sangat berkaitan dengan filosofi yang membuat Swift cepat.

Anda dapat menganggapnya seperti masalah mengelola alamat fisik yang sebenarnya. Saat Anda mengubah alamat, jika ada banyak orang yang memiliki alamat Anda saat ini, Anda harus memberi tahu mereka semua bahwa Anda telah pindah. Tetapi jika tidak ada yang mengetahui alamat Anda saat ini, Anda dapat pindah ke mana pun Anda mau, dan tidak ada yang perlu tahu.

Dalam situasi ini, Swift seperti kantor pos. Jika banyak orang dengan banyak kontak sering berpindah-pindah, itu memiliki overhead yang sangat tinggi. Itu harus membayar banyak staf untuk menangani semua pemberitahuan itu, dan prosesnya memakan banyak waktu dan usaha. Itulah mengapa negara bagian Swift yang ideal adalah setiap orang di kotanya memiliki kontak sesedikit mungkin. Maka tidak perlu staf besar untuk menangani perubahan alamat, dan itu dapat melakukan segalanya dengan lebih cepat dan lebih baik.

Ini juga mengapa orang-orang Swift mengoceh tentang tipe nilai vs. tipe referensi. Secara alami, jenis referensi mengumpulkan "kontak" di semua tempat, dan jenis nilai biasanya tidak membutuhkan lebih dari pasangan. Jenis nilai adalah "Swift" -er.

Jadi kembali ke gambar kecil: structs. Struktur adalah masalah besar di Swift karena dapat melakukan sebagian besar hal yang dapat dilakukan objek, tetapi merupakan tipe nilai.

Mari lanjutkan analogi alamat fisik dengan membayangkan a misterStructyang hidup someObjectVille. Analoginya sedikit miring di sini, tetapi saya pikir itu masih membantu.

Jadi untuk memodelkan mengubah variabel pada a struct, katakanlah misterStructmemiliki rambut hijau, dan mendapat perintah untuk beralih ke rambut biru. Analoginya menjadi miring, seperti yang saya katakan, tetapi yang terjadi adalah bahwa alih-alih mengganti misterStructrambut, orang tua itu pindah dan orang baru dengan rambut biru masuk, dan orang baru itu mulai menyebut dirinya sendiri misterStruct. Tidak ada yang perlu mendapatkan pemberitahuan perubahan alamat, tetapi jika ada yang melihat alamat itu, mereka akan melihat pria berambut biru.

Sekarang mari kita buat model apa yang terjadi saat Anda memanggil fungsi di a struct. Dalam hal ini, seperti misterStructmendapat perintah seperti changeYourHairBlue(). Jadi, kantor pos memberikan instruksi untuk misterStruct"ganti rambut Anda menjadi biru dan beri tahu saya setelah selesai".

Jika dia mengikuti rutinitas yang sama seperti sebelumnya, jika dia melakukan apa yang dia lakukan ketika variabel diubah secara langsung, yang misterStructakan dilakukan adalah keluar dari rumahnya sendiri dan memanggil orang baru dengan rambut biru. Tapi itulah masalahnya.

Perintah itu "go mengubah rambut Anda menjadi biru dan katakan ketika Anda sudah selesai," tapi itu adalah hijau orang yang mendapat perintah itu. Setelah pria biru itu pindah, notifikasi "pekerjaan selesai" masih harus dikirim kembali. Tapi pria biru itu tidak tahu apa-apa tentang itu.

[Untuk benar-benar memenangkan analogi ini sesuatu yang mengerikan, yang secara teknis terjadi pada pria berambut hijau adalah setelah dia pindah, dia segera bunuh diri. Jadi dia tidak bisa memberi tahu siapa pun bahwa tugasnya sudah selesai juga! ]

Untuk menghindari masalah ini, dalam kasus seperti ini saja , Swift harus masuk langsung ke rumah di alamat itu dan benar - benar mengganti rambut penghuni saat ini . Itu adalah proses yang sama sekali berbeda dari sekedar mengirim orang baru.

Dan itulah mengapa Swift ingin kami menggunakan mutatingkata kunci!

Hasil akhirnya terlihat sama untuk semua yang harus mengacu pada struktur: penghuni rumah sekarang berambut biru. Tetapi proses untuk mencapainya sebenarnya sangat berbeda. Sepertinya itu melakukan hal yang sama, tetapi melakukan hal yang sangat berbeda. Itu melakukan hal yang tidak pernah dilakukan oleh struktur Swift secara umum.

Jadi untuk memberikan sedikit bantuan kepada compiler yang buruk, dan tidak membuatnya harus mencari tahu apakah suatu fungsi bermutasi structatau tidak, dengan sendirinya, untuk setiap fungsi struct yang pernah ada, kita diminta untuk mengasihani dan menggunakan mutatingkata kunci.

Intinya, untuk membantu Swift tetap gesit, kita semua harus melakukan bagian kita. :)

EDIT:

Hei bung / dudette yang meremehkan saya, saya baru saja menulis ulang jawaban saya sepenuhnya . Jika Anda merasa lebih baik, apakah Anda akan menghapus suara negatifnya?

Le Mot Juiced
sumber
1
Ini sangat <f-word-here> ditulis dengan luar biasa! Jadi, sangat mudah dimengerti. Saya telah menjelajahi internet untuk ini dan inilah jawabannya (yah, tanpa harus melalui kode sumber). Terima kasih banyak telah meluangkan waktu untuk menulisnya.
Vaibhav
1
Saya hanya ingin mengonfirmasi bahwa saya memahaminya dengan benar: Dapatkah kita mengatakan bahwa, ketika properti instance Struct secara langsung diubah di Swift, instance Struct tersebut "dihentikan" dan instance baru dengan properti yang diubah sedang "dibuat" di balik layar? Dan ketika kita menggunakan metode mutasi dalam instance untuk mengubah properti instance itu, proses penghentian dan inisialisasi ulang ini tidak terjadi?
Ted
@Teo, saya berharap saya dapat menjawab dengan pasti, tetapi yang terbaik yang dapat saya katakan adalah bahwa saya menjalankan analogi ini melewati pengembang Swift yang sebenarnya, dan mereka mengatakan "itu pada dasarnya akurat", menyiratkan bahwa ada lebih dari itu dalam pengertian teknis. Tapi dengan pemahaman itu - bahwa ada lebih dari itu - dalam arti luas, ya, itulah yang dikatakan analogi ini.
Le Mot Juiced
8

Struct Swift dapat dibuat sebagai konstanta (via let) atau variabel (via var)

Pertimbangkan Arraystruct Swift (ya itu struct).

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

Mengapa lampiran tidak berfungsi dengan nama planet? Karena append ditandai dengan mutatingkata kunci. Dan karena planetNamestelah dideklarasikan menggunakan let, semua metode yang ditandai tersebut terlarang.

Dalam contoh Anda, compiler dapat memberi tahu Anda sedang memodifikasi struct dengan menetapkan ke satu atau lebih propertinya di luar file init. Jika Anda mengubah sedikit kode Anda, Anda akan melihatnya xdan ytidak selalu dapat diakses di luar struct. Perhatikan letdi baris pertama.

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

Pertimbangkan analogi dengan C ++. Metode struct di Swift menjadi mutating/ tidak- mutatingsama dengan metode di C ++ menjadi non- const/ const. Metode yang ditandai constdalam C ++ juga tidak dapat mengubah struct.

Anda dapat mengubah var dari luar struct, tetapi Anda tidak dapat mengubahnya dari metodenya sendiri.

Di C ++, Anda juga dapat "mengubah var dari luar struct" - tetapi hanya jika Anda memiliki constvariabel non- struct. Jika Anda memiliki constvariabel struct, Anda tidak dapat menetapkan ke var, dan Anda juga tidak dapat memanggil constmetode non . Demikian pula, di Swift, Anda bisa mengubah properti struct hanya jika variabel struct bukan konstanta. Jika Anda memiliki konstanta struct, Anda tidak dapat menetapkan ke properti, dan Anda juga tidak dapat memanggil mutatingmetode.

newacct
sumber
1

Saya bertanya-tanya hal yang sama ketika saya mulai belajar Swift, dan masing-masing jawaban ini, sambil menambahkan beberapa wawasan mungkin, agak bertele-tele dan membingungkan. Saya rasa jawaban atas pertanyaan Anda sebenarnya cukup sederhana…

Metode mutasi yang didefinisikan di dalam struct Anda menginginkan izin untuk memodifikasi setiap instance dari dirinya sendiri yang akan pernah dibuat di masa depan. Bagaimana jika salah satu contoh tersebut ditetapkan ke konstanta yang tidak dapat diubah let? Uh oh. Untuk melindungi Anda dari diri Anda sendiri (dan untuk memberi tahu editor dan compiler apa yang Anda coba lakukan), Anda dipaksa untuk bersikap eksplisit ketika Anda ingin memberikan metode instance kekuatan semacam ini.

Sebaliknya, setelan properti dari luar struct Anda beroperasi pada instance yang diketahui dari struct itu. Jika itu ditetapkan ke sebuah konstanta, Xcode akan memberi tahu Anda tentang hal itu saat Anda mengetik dalam pemanggilan metode.

Ini adalah salah satu hal yang saya sukai tentang Swift saat saya mulai lebih sering menggunakannya — waspada terhadap kesalahan saat saya mengetiknya. Tentu mengalahkan pemecahan masalah bug JavaScript yang tidak jelas!

Kal
sumber
1

SWIFT: Penggunaan fungsi mutasi di Structs

Pemrogram Swift mengembangkan Structs sedemikian rupa sehingga propertinya tidak dapat dimodifikasi dalam metode Struct. Misalnya, Periksa kode yang diberikan di bawah ini

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)

Saat menjalankan kode di atas, kami mendapatkan kesalahan karena kami mencoba untuk menetapkan nilai baru ke populasi properti Kota Struct. Secara default, properti Structs tidak dapat dimutasi di dalam metodenya sendiri. Beginilah cara Pengembang Apple membuatnya, sehingga Structs akan memiliki sifat statis secara default.

Bagaimana kita mengatasinya? Apa alternatifnya?

Mutasi Kata Kunci:

Mendeklarasikan fungsi sebagai bermutasi di dalam Struct memungkinkan kita untuk mengubah properti di Structs. Baris No: 5, kode diatas berubah jadi seperti ini,

mutating changePopulation(newpopulation : Int)

Sekarang kita dapat menetapkan nilai dari newpopulation untuk properti populasi ke dalam lingkup metode.

Catatan :

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

Jika kita menggunakan let sebagai ganti var untuk objek Struct, maka kita tidak dapat mengubah nilai properti apa pun, Juga ini adalah alasan mengapa kita mendapatkan kesalahan saat kita mencoba memanggil fungsi mutasi menggunakan let instance. Jadi lebih baik menggunakan var setiap kali Anda mengubah nilai properti.

Akan sangat senang mendengar komentar dan pemikiran Anda… ..

sDev
sumber