Go: semua: mendukung perbaikan kode bertahap saat memindahkan jenis antar paket

Dibuat pada 1 Des 2016  ·  225Komentar  ·  Sumber: golang/go

Judul Asli: proposal: mendukung perbaikan kode bertahap saat memindahkan jenis antar paket

Go harus menambahkan kemampuan untuk membuat nama alternatif yang setara untuk tipe, untuk mengaktifkan perbaikan kode bertahap selama pemfaktoran ulang basis kode. Ini adalah target fitur alias Go 1.8, diusulkan di #16339 tetapi tertahan dari Go 1.8. Karena kami tidak menyelesaikan masalah untuk Go 1.8, itu tetap menjadi masalah, dan saya harap kami bisa menyelesaikannya untuk Go 1.9.

Dalam diskusi tentang proposal alias, ada banyak pertanyaan tentang mengapa kemampuan untuk membuat nama alternatif untuk tipe tertentu ini penting. Sebagai upaya baru untuk menjawab pertanyaan-pertanyaan itu, saya menulis dan memposting artikel, “ Codebase Refactoring (dengan bantuan dari Go) .” Silakan baca artikel itu jika Anda memiliki pertanyaan tentang motivasi. (Untuk presentasi alternatif yang lebih pendek, lihat Robert's Gophercon lightning talk . Sayangnya, video itu tidak tersedia online hingga 9 Oktober. Pembaruan, 16 Des: inilah pembicaraan GothamGo saya , yang pada dasarnya adalah draf pertama artikel.)

Masalah ini _tidak_ mengusulkan solusi khusus. Sebagai gantinya, saya ingin mengumpulkan umpan balik dari komunitas Go tentang ruang solusi yang mungkin. Salah satu jalan yang mungkin adalah membatasi alias ke jenis, seperti yang disebutkan di akhir artikel. Mungkin ada orang lain yang harus kita pertimbangkan juga.

Silakan posting pemikiran tentang alias tipe atau solusi lain sebagai komentar di sini.

Terima kasih.

Pembaruan, 16 Des : Dokumen desain untuk alias tipe yang diposting .
Pembaruan, 9 Jan : Proposal diterima, repositori dev.typealias dibuat, implementasi jatuh tempo pada awal siklus Go 1.9 untuk eksperimen.


Ringkasan diskusi (terakhir diperbarui 2017-02-02)

Apakah kita mengharapkan solusi umum yang berfungsi untuk semua deklarasi?

Jika alias tipe 100% diperlukan, maka alias var mungkin 10% diperlukan, alias fungsi 1% diperlukan, dan alias const diperlukan 0%. Karena const sudah memiliki = dan func juga dapat menggunakan =, pertanyaan kuncinya adalah apakah var alias cukup penting untuk direncanakan atau diimplementasikan.

Seperti yang dikemukakan oleh @rogpeppe (https://github.com/golang/go/issues/16339#issuecomment-258771806) dan @ianlancetaylor (https://github.com/golang/go/issues/16339#issuecomment-233644777) dalam proposal alias asli dan seperti yang disebutkan dalam artikel, var global yang bermutasi biasanya merupakan kesalahan. Mungkin tidak masuk akal untuk memperumit solusi untuk mengakomodasi apa yang biasanya merupakan bug. (Faktanya, jika kita dapat mengetahui caranya, tidak akan mengejutkan saya jika dalam jangka panjang Go bergerak ke arah yang mengharuskan vars global tidak dapat diubah.)

Karena var alias yang lebih kaya kemungkinan tidak cukup penting untuk direncanakan, sepertinya pilihan yang tepat di sini adalah fokus hanya pada alias tipe. Sebagian besar komentar di sini tampaknya setuju. Saya tidak akan mencantumkan semua orang.

Apakah kita memerlukan sintaks baru (= vs => vs ekspor)?

Argumen terkuat untuk sintaks baru adalah kebutuhan untuk mendukung var alias, baik sekarang atau di masa mendatang (https://github.com/golang/go/issues/18130#issuecomment-264232763 oleh @Merovius). Tampaknya baik-baik saja untuk merencanakan untuk tidak memiliki var alias (lihat bagian sebelumnya).

Tanpa var alias, menggunakan kembali = lebih sederhana daripada memperkenalkan sintaks baru, baik => seperti di alias proposal, ~ (https://github.com/golang/go/issues/18130#issuecomment-264185142 oleh @joegrasse), atau ekspor (https://github.com/golang/go/issues/18130#issuecomment-264152427 oleh @cznic).

Menggunakan = in juga akan sama persis dengan sintaks alias tipe di Pascal dan Rust. Sejauh bahasa lain memiliki konsep yang sama, ada baiknya menggunakan sintaks yang sama.

Ke depan, mungkin ada Go masa depan di mana fungsi alias juga ada (lihat https://github.com/golang/go/issues/18130#issuecomment-264324306 oleh @nigeltao), dan kemudian semua deklarasi akan mengizinkan bentuk yang sama :

const C2 = C1
func F2 = F1
type T2 = T1
var V2 = V1

Satu-satunya yang tidak akan membentuk alias yang sebenarnya adalah deklarasi var, karena V2 dan V1 dapat didefinisikan ulang secara independen saat program dijalankan (tidak seperti deklarasi const, func, dan tipe yang tidak dapat diubah). Karena salah satu alasan utama variabel adalah untuk memungkinkan mereka bervariasi, pengecualian itu setidaknya akan mudah dijelaskan. Jika Go bergerak menuju vars global yang tidak dapat diubah, maka pengecualian itu pun akan hilang.

Untuk lebih jelasnya, saya tidak menyarankan alias func atau vars global yang tidak dapat diubah di sini, hanya membahas implikasi dari penambahan di masa depan tersebut.

@jimmyfrasche menyarankan (https://github.com/golang/go/issues/18130#issuecomment-264278398) alias untuk semuanya kecuali consts, sehingga const akan menjadi pengecualian alih-alih var:

const C2 = C1 // no => form
func F2 => F1
type T2 => T1
var V2 => V1
var V2 = V1 // different from => form

Memiliki inkonsistensi dengan const dan var tampaknya lebih sulit dijelaskan daripada hanya memiliki inkonsistensi untuk var.

Bisakah ini menjadi perubahan perkakas atau kompiler saja alih-alih perubahan bahasa?

Pasti patut ditanyakan apakah perbaikan kode bertahap dapat diaktifkan murni dengan informasi sampingan yang diberikan ke kompiler (misalnya, https://github.com/golang/go/issues/18130#issuecomment-264205929 oleh @btracey).

Atau mungkin jika kompiler dapat menerapkan semacam prapemrosesan berbasis aturan untuk mengubah file input sebelum kompilasi (misalnya, https://github.com/golang/go/issues/18130#issuecomment-264329924 oleh @tux21b).

Sayangnya, tidak, perubahan itu benar-benar tidak dapat dibatasi seperti itu. Setidaknya ada dua kompiler (gc dan gccgo) yang perlu dikoordinasikan, tetapi begitu juga alat lain yang menganalisis program, seperti go vet, guru, goimports, gocode (penyelesaian kode), dan lainnya.

Seperti yang dikatakan @bcmils (https://github.com/golang/go/issues/18130#issuecomment-264275574), “mekanisme 'non-language-change' yang harus didukung oleh semua implementasi adalah perubahan bahasa de facto — itu hanya satu dengan dokumentasi yang lebih buruk.”

Apa kegunaan lain yang mungkin dimiliki alias?

Kita tahu berikut ini. Mengingat bahwa alias tipe tertentu dianggap cukup penting untuk dimasukkan dalam Pascal dan Rust, kemungkinan ada yang lain.

  1. Alias ​​​​(atau cukup ketik alias) akan memungkinkan pembuatan pengganti drop-in yang memperluas paket lain. Misalnya lihat https://go-review.googlesource.com/#/c/32145/ , terutama penjelasannya di pesan komit.

  2. Alias ​​​​(atau cukup ketik alias) akan memungkinkan penataan paket dengan permukaan API kecil tetapi implementasi besar sebagai kumpulan paket untuk struktur internal yang lebih baik tetapi masih menyajikan hanya satu paket untuk diimpor dan digunakan oleh klien. Ada contoh abstrak yang dijelaskan di https://github.com/golang/go/issues/16339#issuecomment -232813695.

  3. Buffer protokol memiliki fitur "import publik" yang semantiknya sepele untuk diterapkan dalam kode C++ yang dihasilkan tetapi tidak mungkin diimplementasikan dalam kode Go yang dihasilkan. Ini menyebabkan frustrasi bagi pembuat definisi buffer protokol yang dibagikan antara klien C++ dan Go. Ketik alias akan memberikan cara bagi Go untuk mengimplementasikan fitur ini. Faktanya, kasus penggunaan asli untuk publik impor adalah perbaikan kode bertahap . Masalah serupa mungkin muncul pada jenis pembuat kode lainnya.

  4. Menyingkat nama panjang. Alias ​​lokal (tidak diekspor atau tidak-paket) mungkin berguna untuk menyingkat nama tipe yang panjang tanpa memperkenalkan overhead dari tipe yang sama sekali baru. Seperti semua penggunaan ini, kejelasan kode akhir akan sangat mempengaruhi apakah ini adalah penggunaan yang disarankan.

Masalah lain apa yang perlu ditangani oleh proposal untuk jenis alias?

Daftar ini untuk referensi. Tidak mencoba untuk memecahkan atau mendiskusikannya di bagian ini, meskipun beberapa telah dibahas kemudian dan diringkas dalam bagian terpisah di bawah ini.

  1. Penanganan di godoc. (https://github.com/golang/go/issues/18130#issuecomment-264323137 oleh @nigeltao dan https://github.com/golang/go/issues/18130#issuecomment-264326437 oleh @jimmyfrasche)

  2. Bisakah metode didefinisikan pada tipe yang dinamai dengan alias? (https://github.com/golang/go/issues/18130#issuecomment-265077877 oleh @ulikunitz)

  3. Jika alias ke alias diizinkan, bagaimana kita menangani siklus alias? (https://github.com/golang/go/issues/18130#issuecomment-264494658 oleh @thwd)

  4. Haruskah alias dapat mengekspor pengidentifikasi yang tidak diekspor? (https://github.com/golang/go/issues/18130#issuecomment-264494658 oleh @thwd)

  5. Apa yang terjadi ketika Anda menyematkan alias (bagaimana Anda mengakses bidang yang disematkan)? (https://github.com/golang/go/issues/18130#issuecomment-264494658 oleh @thwd , juga #17746)

  6. Apakah alias tersedia sebagai simbol dalam program yang dibangun? (https://github.com/golang/go/issues/18130#issuecomment-264494658 oleh @thwd)

  7. Injeksi string Ldflags: bagaimana jika kita merujuk ke alias? (https://github.com/golang/go/issues/18130#issuecomment-264494658 oleh @thwd; ini hanya muncul jika ada var alias.)

Apakah membuat versi menjadi solusi dengan sendirinya?

"Dalam hal ini mungkin versi adalah jawaban keseluruhan, bukan jenis alias."
(https://github.com/golang/go/issues/18130#issuecomment-264573088 oleh @iainmerrick)

Seperti disebutkan dalam artikel , saya pikir versi adalah masalah pelengkap. Dukungan untuk perbaikan kode bertahap, seperti dengan alias tipe, memberikan sistem versi lebih banyak fleksibilitas dalam cara membangun program besar, yang dapat menjadi perbedaan antara mampu membangun program dan tidak.

Bisakah masalah refactoring yang lebih besar diselesaikan?

Di https://github.com/golang/go/issues/18130#issuecomment -265052639, @niemeyer menunjukkan bahwa sebenarnya ada dua perubahan untuk memindahkan os.Error ke kesalahan: nama berubah tetapi begitu juga definisi (saat ini Metode kesalahan dulunya adalah metode String).

@niemeyer menyarankan bahwa mungkin kita dapat menemukan solusi untuk masalah refactoring yang lebih luas yang memperbaiki jenis perpindahan antar paket sebagai kasus khusus tetapi juga menangani hal-hal seperti perubahan nama metode, dan dia mengusulkan solusi yang dibangun di sekitar "adaptor".

Ada cukup banyak diskusi di komentar yang tidak dapat saya rangkum dengan mudah di sini. Diskusi belum selesai, tetapi sejauh ini tidak jelas apakah "adaptor" dapat masuk ke dalam bahasa atau diimplementasikan dalam praktik. Tampaknya jelas bahwa adaptor setidaknya satu urutan besarnya lebih kompleks daripada alias tipe.

Adaptor membutuhkan solusi yang koheren untuk masalah subtipe yang disebutkan di bawah ini juga.

Bisakah metode dideklarasikan pada tipe alias?

Tentu saja alias tidak mengizinkan melewati batasan definisi metode yang biasa: jika sebuah paket mendefinisikan tipe T1 = otherpkg.T2, paket tersebut tidak dapat mendefinisikan metode pada T1, seperti halnya paket tersebut tidak dapat mendefinisikan metode secara langsung pada pkg.T2 lainnya. Artinya, jika tipe T1 = otherpkg.T2, maka func (T1) M() sama dengan func (otherpkg.T2) M(), yang saat ini tidak valid dan tetap tidak valid. Namun, jika sebuah paket mendefinisikan tipe T1 = T2 (keduanya dalam paket yang sama), maka jawabannya kurang jelas. Dalam hal ini, func (T1) M() akan setara dengan func (T2) M(); karena yang terakhir diperbolehkan, ada argumen untuk mengizinkan yang pertama. Dokumen desain saat ini tidak memberlakukan pembatasan di sini (sesuai dengan penghindaran umum pembatasan), sehingga func (T1) M() valid dalam situasi ini.

Di https://github.com/golang/go/issues/18130#issuecomment -267694112, @jimmyfrasche menyarankan bahwa alih-alih mendefinisikan "tidak menggunakan alias dalam definisi metode" akan menjadi aturan yang jelas dan menghindari perlu tahu apa T didefinisikan untuk mengetahui apakah func (T) M() valid. Di https://github.com/golang/go/issues/18130#issuecomment -267997124, @rsc menunjukkan bahwa bahkan hari ini ada T tertentu yang func (T) M() tidak valid: https://play .golang.org/p/bci2qnldej. Dalam praktiknya ini tidak muncul karena orang menulis kode yang masuk akal.

Kami akan mengingat kemungkinan pembatasan ini tetapi menunggu sampai ada bukti kuat bahwa itu diperlukan sebelum memperkenalkannya.

Apakah ada cara yang lebih bersih untuk menangani penyematan dan, lebih umum, mengganti nama bidang?

Di https://github.com/golang/go/issues/18130#issuecomment -267691816, @Merovius menunjukkan bahwa jenis tertanam yang mengubah namanya selama pemindahan paket akan menyebabkan masalah ketika nama baru itu akhirnya harus diadopsi di menggunakan situs. Misalnya jika pengguna tipe U memiliki io.ByteBuffer tertanam yang pindah ke byte.Buffer, maka saat U menyematkan io.ByteBuffer nama bidangnya adalah U.ByteBuffer, tetapi ketika U diperbarui untuk merujuk ke byte.Buffer, nama bidang harus berubah menjadi U.Buffer.

Di https://github.com/golang/go/issues/18130#issuecomment -267710478, @neild menunjukkan bahwa setidaknya ada solusi jika referensi ke io.ByteBuffer harus dikeluarkan: paket P yang mendefinisikan U juga dapat tentukan 'ketik ByteBuffer = byte.Buffer' dan sematkan jenis itu ke dalam U. Kemudian U masih memiliki U.ByteBuffer, bahkan setelah io.ByteBuffer hilang sepenuhnya.

Di https://github.com/golang/go/issues/18130#issuecomment -267703067, @bcmills menyarankan gagasan alias bidang, untuk memungkinkan bidang memiliki banyak nama selama perbaikan bertahap. Alias ​​bidang akan memungkinkan pendefinisian sesuatu seperti type U struct { bytes.Buffer; ByteBuffer = Buffer } daripada harus membuat alias tipe tingkat atas.

Di https://github.com/golang/go/issues/18130#issuecomment -268001111, @rsc memunculkan kemungkinan lain: beberapa sintaks untuk 'sematkan tipe ini dengan nama ini', sehingga memungkinkan untuk menyematkan byte. Buffer sebagai nama bidang ByteBuffer, tanpa memerlukan tipe tingkat atas atau nama alternatif. Jika itu ada, maka nama tipe dapat diperbarui dari io.ByteBuffer ke byte.Buffer sambil mempertahankan nama aslinya (dan tidak memperkenalkan yang kedua, atau tipe ekspor yang kikuk).

Ini semua tampaknya perlu ditelusuri setelah kami memiliki lebih banyak bukti refactoring skala besar yang diblokir oleh masalah dengan bidang yang mengubah nama. Seperti yang ditulis @rsc , "Jika alias tipe membantu kami mencapai titik di mana kurangnya alias bidang adalah penghalang besar berikutnya untuk refactoring skala besar, itu akan menjadi kemajuan!"

Ada saran untuk membatasi penggunaan alias di bidang yang disematkan atau mengubah nama yang disematkan untuk menggunakan nama tipe target, tetapi itu membuat pengenalan alias mematahkan definisi yang ada yang kemudian harus diperbaiki secara atom, pada dasarnya mencegah perbaikan bertahap. @rsc : "Kami membahas ini cukup panjang di #17746. Saya awalnya menggunakan nama io.ByteBuffer alias Buffer, tetapi argumen di atas meyakinkan saya bahwa saya salah. @jimmyfrasche khususnya membuat beberapa hal baik argumen tentang kode tidak berubah tergantung pada definisi hal yang disematkan. Saya tidak berpikir itu dapat dipertahankan untuk melarang alias yang disematkan sepenuhnya."

Apa pengaruhnya terhadap program yang menggunakan refleksi?

Program menggunakan refleksi melihat melalui alias. Di https://github.com/golang/go/issues/18130#issuecomment -267903649, @atdiar menunjukkan bahwa jika suatu program menggunakan refleksi untuk, misalnya, menemukan paket di mana suatu tipe didefinisikan atau bahkan namanya dari suatu tipe, ia akan mengamati perubahan ketika tipe dipindahkan, bahkan jika alias penerusan tertinggal. Di https://github.com/golang/go/issues/18130#issuecomment -268001410, @rsc mengkonfirmasi ini dan menulis "Seperti situasi dengan embedding, itu tidak sempurna. Berbeda dengan situasi dengan embedding, saya tidak punya jawaban kecuali mungkin kode tidak boleh ditulis menggunakan reflect agar cukup sensitif terhadap detail itu."

Penggunaan paket vendor hari ini juga mengubah jalur impor paket yang dilihat oleh reflect, dan kami belum mengetahui masalah signifikan yang disebabkan oleh ambiguitas itu. Hal ini menunjukkan bahwa program biasanya tidak memeriksa reflect.Type.PkgPath dengan cara yang akan rusak dengan menggunakan alias. Meski begitu, ini adalah celah potensial, seperti embedding.

Apa pengaruhnya pada kompilasi terpisah dari program dan plugin?

Di https://github.com/golang/go/issues/18130#issuecomment -268524504, @atdiar menimbulkan pertanyaan tentang efek pada file objek dan kompilasi terpisah. Di https://github.com/golang/go/issues/18130#issuecomment -268560180, @rsc menjawab bahwa seharusnya tidak perlu membuat perubahan di sini: jika X mengimpor Y dan Y berubah dan dikompilasi ulang, maka X perlu dikompilasi ulang juga. Itu benar hari ini tanpa alias, dan akan tetap benar dengan alias. Kompilasi terpisah berarti dapat mengompilasi X dan Y dalam langkah-langkah yang berbeda (kompiler tidak harus memprosesnya dalam permintaan yang sama), bukan berarti mungkin untuk mengubah Y tanpa mengkompilasi ulang X.

Akankah jumlah tipe atau semacam subtipe menjadi solusi alternatif?

Di https://github.com/golang/go/issues/18130#issuecomment -264413439, @iand menyarankan "tipe yang dapat diganti", "daftar tipe yang dapat diganti dengan tipe bernama dalam argumen fungsi, nilai kembalian, dll. ". Di https://github.com/golang/go/issues/18130#issuecomment -268072274, @j7b menyarankan menggunakan tipe aljabar "jadi kami juga mendapatkan antarmuka kosong yang setara dengan pemeriksaan tipe waktu kompilasi sebagai bonus". Nama lain untuk konsep ini adalah tipe sum dan tipe varian.

Secara umum ini tidak cukup untuk memungkinkan tipe bergerak dengan perbaikan kode bertahap. Ada dua cara untuk memikirkan hal ini.

Di https://github.com/golang/go/issues/18130#issuecomment -268075680, @bcmills mengambil cara konkret, menunjukkan bahwa tipe aljabar memiliki representasi yang berbeda dari aslinya, yang membuatnya tidak mungkin untuk memperlakukan jumlah dan yang asli dapat dipertukarkan: yang terakhir memiliki tag tipe.

Di https://github.com/golang/go/issues/18130#issuecomment -268585497, @rsc mengambil cara teoretis, memperluas https://github.com/golang/go/issues/18130#issuecomment -265211655 oleh @gri menunjukkan bahwa dalam perbaikan kode bertahap, terkadang Anda perlu T1 menjadi subtipe T2 dan terkadang sebaliknya. Satu-satunya cara bagi keduanya untuk menjadi subtipe satu sama lain adalah agar mereka menjadi tipe yang sama, yang tidak secara kebetulan adalah apa yang dilakukan oleh alias tipe.

Sebagai tangen samping, selain tidak menyelesaikan masalah perbaikan kode bertahap, tipe aljabar / tipe penjumlahan / tipe gabungan / tipe varian dengan sendirinya sulit ditambahkan ke Go. Lihat
jawaban FAQ dan diskusi Go 1.6 AMA untuk lebih lanjut.

Di https://github.com/golang/go/issues/18130#issuecomment -265206780, @thwd menyarankan bahwa karena Go memiliki hubungan subtipe antara tipe dan antarmuka konkret (bytes.Buffer dapat dilihat sebagai subtipe dari io.Reader ) dan antar antarmuka (io.ReadWriter adalah subtipe dari io.Reader dengan cara yang sama), menjadikan antarmuka "kovarian rekursif (menurut aturan varians saat ini) hingga argumen metodenya" akan menyelesaikan masalah asalkan semua paket masa depan saja gunakan antarmuka, jangan pernah tipe konkret seperti struct ("mendorong desain yang bagus juga").

Ada tiga masalah dengan itu sebagai solusi. Pertama, ia memiliki masalah subtipe di atas, sehingga tidak menyelesaikan perbaikan kode bertahap. Kedua, itu tidak berlaku untuk kode yang ada, seperti yang dicatat @thwd dalam saran ini. Ketiga, memaksa penggunaan antarmuka di mana-mana mungkin sebenarnya bukan desain yang baik dan memperkenalkan overhead kinerja (lihat misalnya https://github.com/golang/go/issues/18130#issuecomment-265211726 oleh @Merovius dan https://github .com/golang/go/issues/18130#issuecomment-265224652 oleh @zombiezen).

Pembatasan

Bagian ini mengumpulkan batasan yang diusulkan untuk referensi, tetapi perlu diingat bahwa batasan menambah kompleksitas. Seperti yang saya tulis di https://github.com/golang/go/issues/18130#issuecomment -264195616 , "kita mungkin hanya harus menerapkan pembatasan itu setelah pengalaman nyata dengan desain yang tidak terbatas dan lebih sederhana membantu kita memahami apakah pembatasan akan membawa cukup manfaat untuk membayar biayanya."

Dengan kata lain, pembatasan apa pun perlu dibenarkan dengan bukti bahwa itu akan mencegah penyalahgunaan atau kebingungan yang serius. Karena kami belum menerapkan solusi, tidak ada bukti seperti itu. Jika pengalaman memang memberikan bukti itu, ini akan layak untuk kembali.

Larangan? Alias ​​​​jenis perpustakaan standar hanya dapat dideklarasikan di perpustakaan standar.

(https://github.com/golang/go/issues/18130#issuecomment-264165833 dan https://github.com/golang/go/issues/18130#issuecomment-264171370 oleh @iand)

Kekhawatirannya adalah "kode yang telah mengganti nama konsep pustaka standar agar sesuai dengan konvensi penamaan khusus", atau "rantai spageti panjang alias di beberapa paket yang berakhir kembali di pustaka standar", atau "aliasing hal-hal seperti antarmuka{} dan kesalahan" .

Sebagaimana dinyatakan, pembatasan akan melarang kasus "paket ekstensi" yang dijelaskan di atas yang melibatkan x/image/draw.

Tidak jelas mengapa perpustakaan standar harus istimewa: masalah akan ada dengan kode apa pun. Selain itu, baik antarmuka{} maupun kesalahan bukanlah tipe dari pustaka standar. Mengulangi batasan sebagai "aliasing tipe yang telah ditentukan sebelumnya" akan melarang kesalahan aliasing, tetapi kebutuhan untuk kesalahan alias adalah salah satu contoh motivasi dalam artikel.

Larangan? Target alias harus berupa pengenal yang memenuhi syarat paket.

(https://github.com/golang/go/issues/18130#issuecomment-264188282 oleh @jba)

Ini akan membuat tidak mungkin untuk membuat alias saat mengganti nama jenis dalam sebuah paket, yang dapat digunakan cukup luas untuk memerlukan perbaikan bertahap (https://github.com/golang/go/issues/18130#issuecomment-264274714 oleh @ bcmils).

Itu juga akan melarang kesalahan aliasing seperti dalam artikel.

Larangan? Target alias harus berupa pengenal yang memenuhi syarat paket dengan nama yang sama dengan alias.

(diusulkan selama diskusi alias di Go 1.8)

Selain masalah bagian sebelumnya dengan membatasi pengidentifikasi yang memenuhi syarat paket, memaksa nama untuk tetap sama akan melarang konversi dari io.ByteBuffer ke byte.Buffer dalam artikel.

Larangan? Alias ​​​​harus dicegah dengan cara tertentu.

"Bagaimana dengan menyembunyikan alias di balik impor, seperti untuk "C" dan "tidak aman", untuk lebih mencegah penggunaannya? Dalam nada yang sama, saya ingin sintaks alias bertele-tele dan menonjol sebagai perancah untuk melanjutkan refactoring ." - https://github.com/golang/go/issues/18130#issuecomment -264289940 oleh @xiegeo

"Haruskah kita juga secara otomatis menyimpulkan bahwa tipe alias adalah warisan dan harus diganti dengan tipe baru? Jika kita menerapkan golint, godoc, dan alat serupa untuk memvisualisasikan tipe lama sebagai usang, itu akan membatasi penyalahgunaan aliasing dengan sangat signifikan. Dan kekhawatiran terakhir dari fitur aliasing yang disalahgunakan akan diselesaikan." - https://github.com/golang/go/issues/18130#issuecomment -265062154 oleh @rakyll

Sampai kita tahu bahwa mereka akan digunakan salah, tampaknya terlalu dini untuk mencegah penggunaan. Mungkin ada penggunaan yang baik dan tidak sementara (lihat di atas).

Bahkan dalam kasus perbaikan kode, baik tipe lama atau baru dapat menjadi alias selama transisi, tergantung pada batasan yang dikenakan oleh grafik impor. Menjadi alias tidak berarti nama itu ditinggalkan.

Sudah ada mekanisme untuk menandai deklarasi tertentu sebagai usang (lihat https://github.com/golang/go/issues/18130#issuecomment-265294564 oleh @jimmyfrasche).

Larangan? Alias ​​​​harus menargetkan jenis yang disebutkan.

"Alias ​​tidak boleh berlaku untuk tipe yang tidak disebutkan namanya. Tidak ada cerita "perbaikan kode" dalam berpindah dari satu tipe tanpa nama ke tipe lain. Mengizinkan alias pada tipe yang tidak disebutkan namanya berarti saya tidak bisa lagi mengajarkan Go hanya sebagai tipe bernama dan tidak bernama." - https://github.com/golang/go/issues/18130#issuecomment -276864903 oleh @davecheney

Sampai kita tahu bahwa mereka akan digunakan salah, tampaknya terlalu dini untuk mencegah penggunaan. Mungkin ada kegunaan yang baik dengan target yang tidak disebutkan namanya (lihat di atas).

Seperti disebutkan dalam dokumen desain, kami berharap untuk mengubah terminologi untuk membuat situasi lebih jelas.

FrozenDueToAge Proposal Proposal-Accepted

Komentar yang paling membantu

@cznic , @iand , others: Harap dicatat bahwa _pembatasan menambah kompleksitas_. Mereka memperumit penjelasan fitur, dan mereka menambahkan beban kognitif untuk setiap pengguna fitur: jika Anda lupa tentang pembatasan, Anda harus mencari tahu mengapa sesuatu yang Anda pikir seharusnya tidak berhasil.

Ini sering merupakan kesalahan untuk menerapkan pembatasan pada percobaan desain semata-mata karena penyalahgunaan hipotetis. Itu terjadi dalam diskusi proposal alias, dan itu membuat alias dalam uji coba tidak dapat menangani konversi io.ByteBuffer => bytes.Buffer dari artikel. Bagian dari tujuan penulisan artikel adalah untuk mendefinisikan beberapa kasus yang kami tahu ingin kami tangani, sehingga kami tidak secara tidak sengaja membatasinya.

Sebagai contoh lain, akan mudah untuk membuat argumen penyalahgunaan untuk melarang penerima non-pointer, atau untuk melarang metode pada tipe non-struct. Jika kami telah melakukan salah satu dari itu, Anda tidak dapat membuat enum dengan metode String() untuk mencetak sendiri, dan Anda tidak dapat membuat http.Headers keduanya menjadi peta biasa dan menyediakan metode pembantu. Seringkali mudah untuk membayangkan penyalahgunaan; penggunaan positif yang menarik dapat membutuhkan waktu lebih lama untuk muncul, dan penting untuk menciptakan ruang untuk eksperimen.

Sebagai contoh lain, desain dan implementasi asli untuk metode pointer vs nilai tidak membedakan antara set metode pada T dan *T: jika Anda memiliki *T, Anda dapat memanggil metode nilai (penerima T), dan jika Anda memiliki a T, Anda dapat memanggil metode pointer (penerima *T). Ini sederhana, tanpa batasan untuk dijelaskan. Tetapi kemudian pengalaman aktual menunjukkan kepada kita bahwa mengizinkan metode penunjuk memanggil nilai-nilai menyebabkan kelas tertentu dari bug yang membingungkan dan mengejutkan. Misalnya, Anda dapat menulis:

var buf bytes.Buffer
io.Copy(buf, reader)

dan io.Copy akan berhasil, tetapi buf tidak memiliki apa-apa di dalamnya. Kami harus memilih antara menjelaskan mengapa program itu berjalan dengan tidak benar atau menjelaskan mengapa program itu tidak dapat dikompilasi. Bagaimanapun akan ada pertanyaan, tapi kami berusaha menghindari eksekusi yang salah. Meski begitu, kami masih harus menulis entri FAQ tentang mengapa desainnya berlubang.

Sekali lagi, harap diingat bahwa pembatasan menambah kerumitan. Seperti semua kerumitan, pembatasan membutuhkan pembenaran yang signifikan. Pada tahap ini dalam proses desain, ada baiknya untuk memikirkan batasan yang mungkin sesuai untuk desain tertentu, tetapi kita mungkin hanya menerapkan batasan tersebut setelah pengalaman nyata dengan desain yang tidak terbatas dan lebih sederhana membantu kita memahami apakah batasan tersebut akan membawa manfaat yang cukup. untuk membayar biayanya.

Semua 225 komentar

Saya suka bagaimana visual seragam ini terlihat.

const OldAPI => NewPackage.API
func  OldAPI => NewPackage.API
var   OldAPI => NewPackage.API
type  OldAPI => NewPackage.API

Tapi karena kita hampir bisa memindahkan sebagian besar elemen secara bertahap, mungkin yang paling sederhana
solusi _is_ hanya untuk mengizinkan = untuk tipe.

const OldAPI = NewPackage.API
func  OldAPI() { NewPackage.API() }
var   OldAPI = NewPackage.API
type  OldAPI = NewPackage.API

Jadi pertama-tama, saya hanya ingin mengucapkan terima kasih atas tulisan yang luar biasa itu. Saya pikir solusi terbaik adalah memperkenalkan alias tipe dengan operator penugasan. Ini tidak memerlukan kata kunci/operator baru, menggunakan sintaks yang sudah dikenal, dan harus menyelesaikan masalah refactoring untuk basis kode yang besar.

Seperti yang ditunjukkan artikel Russ, solusi mirip alias apa pun perlu diselesaikan dengan anggun https://github.com/golang/go/issues/17746 dan https://github.com/golang/go/issues/17784

Terima kasih atas penulisan artikel tersebut.

Saya menemukan alias tipe-saja menggunakan operator penugasan sebagai yang terbaik:

type OldAPI = NewPackage.API

Alasan saya:

  • Ini lebih sederhana.
    Solusi alternatif => memiliki arti yang agak berbeda berdasarkan operandnya terasa tidak pada tempatnya untuk Go.
  • Ini fokus dan konservatif.
    Masalah yang dihadapi dengan tipe diselesaikan dan Anda tidak perlu khawatir membayangkan komplikasi dari solusi umum.
  • Ini estetis.
    Saya pikir itu terlihat lebih menyenangkan.

Semua ini di atas: hasilnya sederhana, fokus, konservatif, dan estetis memudahkan saya membayangkannya menjadi bagian dari Go.

Jika solusinya akan terbatas pada tipe saja maka sintaksnya

type NewFoo = old.Foo

sudah dipertimbangkan sebelumnya, seperti yang dibahas dalam artikel @rsc , terlihat sangat bagus bagi saya.

Jika kita ingin dapat melakukan hal yang sama untuk konstanta, variabel, dan fungsi, sintaks pilihan saya adalah (seperti yang diusulkan sebelumnya)

package newfmt

import (
    "fmt"
)

// No renaming.
export fmt.Printf // Note: Same as `export Printf fmt.Printf`.

export (
        fmt.Sprintf
        fmt.Formatter
)

// Renaming.
export Foo fmt.Errorf // Foo must be exported, ie. `export foo fmt.Errorf` would be invalid.

export (
    Bar fmt.Fprintf
    Qux fmt.State
)

Seperti yang telah dibahas sebelumnya, kelemahannya adalah bahwa kata kunci tingkat atas saja yang baru diperkenalkan, yang memang diakui, meskipun secara teknis layak dan sepenuhnya kompatibel ke belakang. Saya suka sintaks ini karena mencerminkan pola impor. Tampaknya wajar bagi saya bahwa ekspor akan diizinkan hanya di bagian yang sama di mana impor diizinkan, yaitu. antara klausa paket dan TLD var, tipe, konstanta, atau fungsi apa pun.

Pengidentifikasi penggantian nama akan dideklarasikan dalam lingkup paket, namun, nama baru tidak terlihat dalam paket yang mendeklarasikannya (newfmt dalam contoh di atas) sehubungan dengan deklarasi ulang, yang tidak diizinkan seperti biasa. Mengingat contoh sebelumnya, TLDs

var v = Printf // undefined: Printf.
var Printf int // Printf redeclared, previous declaration at newfmt.go:8.

Dalam paket pengimporan, pengidentifikasi penggantian nama terlihat secara normal, seperti pengidentifikasi ekspor lainnya dari blok paket (newftm).

package foo

import "newfmt"

type bar interface {
    baz(qux newfmt.Qux) // qux type is identical to fmt.State.
}

Kesimpulannya, pendekatan ini tidak memperkenalkan pengikatan nama lokal baru di newfmt, yang menurut saya menghindari setidaknya beberapa masalah yang dibahas di #17746 dan menyelesaikan #17784 sepenuhnya.

Preferensi pertama saya adalah untuk tipe-saja type NewFoo = old.Foo .

Jika solusi yang lebih umum diinginkan, saya setuju dengan @cznic bahwa kata kunci khusus lebih baik daripada operator baru (terutama operator asimetris dengan arah yang membingungkan[1]). Meskipun demikian, menurut saya kata kunci export memberikan arti yang tepat. Baik sintaks, maupun semantik tidak mencerminkan import . Bagaimana dengan alias ?

Saya mengerti mengapa @cznic tidak ingin nama-nama baru dapat diakses dalam paket yang mendeklarasikannya, tetapi, setidaknya bagi saya, pembatasan itu terasa tidak terduga dan artifisial (walaupun saya sangat memahami alasan di baliknya).

[1] Saya telah menggunakan Unix selama hampir 20 tahun, dan saya masih tidak dapat membuat symlink pada percobaan pertama. Dan saya biasanya gagal bahkan pada percobaan kedua, setelah saya membaca manualnya.

Saya ingin mengusulkan batasan tambahan: tipe alias untuk tipe perpustakaan standar hanya dapat dideklarasikan di perpustakaan standar.

Alasan saya adalah saya tidak ingin bekerja dengan kode yang telah mengganti nama konsep perpustakaan standar agar sesuai dengan konvensi penamaan khusus. Saya juga tidak ingin berurusan dengan rantai alias spageti yang panjang di beberapa paket yang berakhir kembali di perpustakaan standar.

@iand : Batasan itu akan memblokir penggunaan fitur ini untuk memigrasikan apa pun ke perpustakaan standar. Contoh kasus, migrasi Context ke pustaka standar. Rumah lama Context harus menjadi alias untuk Context di pustaka standar.

@quentinmit sayangnya itu benar. Itu juga membatasi kasus penggunaan untuk golang.org/x/image/draw di CL ini https://go-review.googlesource.com/#/c/32145/

Kekhawatiran saya yang sebenarnya adalah dengan orang-orang yang mengasingkan hal-hal seperti interface{} dan error

Jika diputuskan untuk memperkenalkan operator baru, saya ingin mengusulkan ~ . Dalam bahasa Inggris, umumnya dipahami berarti "mirip dengan", "kira-kira", "tentang", atau "sekitar". Seperti yang dinyatakan @4ad di atas, => adalah operator asimetris dengan arah yang membingungkan.

Sebagai contoh:

const OldAPI ~ NewPackage.API
func  OldAPI ~ NewPackage.API
var   OldAPI ~ NewPackage.API
type  OldAPI ~ NewPackage.API

@iand jika kami membatasi sisi kanan ke pengidentifikasi yang memenuhi syarat paket, maka itu akan menghilangkan kekhawatiran khusus Anda.

Itu juga berarti Anda tidak dapat memiliki alias untuk tipe apa pun dalam paket saat ini, atau ekspresi tipe panjang seperti map[string]map[int]interface{} . Tetapi penggunaan itu tidak ada hubungannya dengan tujuan utama perbaikan kode bertahap, jadi mungkin itu bukan kerugian besar.

@cznic , @iand , others: Harap dicatat bahwa _pembatasan menambah kompleksitas_. Mereka memperumit penjelasan fitur, dan mereka menambahkan beban kognitif untuk setiap pengguna fitur: jika Anda lupa tentang pembatasan, Anda harus mencari tahu mengapa sesuatu yang Anda pikir seharusnya tidak berhasil.

Ini sering merupakan kesalahan untuk menerapkan pembatasan pada percobaan desain semata-mata karena penyalahgunaan hipotetis. Itu terjadi dalam diskusi proposal alias, dan itu membuat alias dalam uji coba tidak dapat menangani konversi io.ByteBuffer => bytes.Buffer dari artikel. Bagian dari tujuan penulisan artikel adalah untuk mendefinisikan beberapa kasus yang kami tahu ingin kami tangani, sehingga kami tidak secara tidak sengaja membatasinya.

Sebagai contoh lain, akan mudah untuk membuat argumen penyalahgunaan untuk melarang penerima non-pointer, atau untuk melarang metode pada tipe non-struct. Jika kami telah melakukan salah satu dari itu, Anda tidak dapat membuat enum dengan metode String() untuk mencetak sendiri, dan Anda tidak dapat membuat http.Headers keduanya menjadi peta biasa dan menyediakan metode pembantu. Seringkali mudah untuk membayangkan penyalahgunaan; penggunaan positif yang menarik dapat membutuhkan waktu lebih lama untuk muncul, dan penting untuk menciptakan ruang untuk eksperimen.

Sebagai contoh lain, desain dan implementasi asli untuk metode pointer vs nilai tidak membedakan antara set metode pada T dan *T: jika Anda memiliki *T, Anda dapat memanggil metode nilai (penerima T), dan jika Anda memiliki a T, Anda dapat memanggil metode pointer (penerima *T). Ini sederhana, tanpa batasan untuk dijelaskan. Tetapi kemudian pengalaman aktual menunjukkan kepada kita bahwa mengizinkan metode penunjuk memanggil nilai-nilai menyebabkan kelas tertentu dari bug yang membingungkan dan mengejutkan. Misalnya, Anda dapat menulis:

var buf bytes.Buffer
io.Copy(buf, reader)

dan io.Copy akan berhasil, tetapi buf tidak memiliki apa-apa di dalamnya. Kami harus memilih antara menjelaskan mengapa program itu berjalan dengan tidak benar atau menjelaskan mengapa program itu tidak dapat dikompilasi. Bagaimanapun akan ada pertanyaan, tapi kami berusaha menghindari eksekusi yang salah. Meski begitu, kami masih harus menulis entri FAQ tentang mengapa desainnya berlubang.

Sekali lagi, harap diingat bahwa pembatasan menambah kerumitan. Seperti semua kerumitan, pembatasan membutuhkan pembenaran yang signifikan. Pada tahap ini dalam proses desain, ada baiknya untuk memikirkan batasan yang mungkin sesuai untuk desain tertentu, tetapi kita mungkin hanya menerapkan batasan tersebut setelah pengalaman nyata dengan desain yang tidak terbatas dan lebih sederhana membantu kita memahami apakah batasan tersebut akan membawa manfaat yang cukup. untuk membayar biayanya.

Juga, harapan saya adalah kita dapat mencapai keputusan tentatif tentang apa yang harus dicoba dan kemudian menyiapkan sesuatu untuk eksperimen di awal siklus Go 1.9 (idealnya hari siklus dibuka). Memiliki lebih banyak waktu untuk bereksperimen akan memiliki banyak manfaat, di antaranya kesempatan untuk mempelajari apakah pembatasan tertentu menarik. Satu kesalahan dengan alias tidak melakukan implementasi lengkap hingga mendekati akhir siklus Go 1.8.

Satu hal tentang proposal alias asli adalah bahwa dalam kasus penggunaan yang dimaksudkan (mengaktifkan refactoring) penggunaan sebenarnya dari tipe alias seharusnya hanya bersifat sementara. Dalam contoh protobuffer, rintisan io.BytesBuffer dihapus setelah perbaikan bertahap selesai.

Jika mekanisme alias hanya terlihat sementara, apakah benar-benar memerlukan perubahan bahasa? Mungkin sebaliknya mungkin ada mekanisme untuk memasok gc dengan daftar "alias". gc dapat melakukan penggantian untuk sementara, dan pembuat basis kode hilir dapat secara bertahap menghapus item dalam file ini saat perbaikan digabungkan. Saya menyadari saran ini juga memiliki konsekuensi yang rumit, tetapi setidaknya mendorong mekanisme sementara.

Saya tidak akan berpartisipasi dalam bikeshedding tentang sintaks (saya pada dasarnya tidak peduli), dengan satu pengecualian: Jika menambahkan alias diputuskan dan jika diputuskan untuk membatasi mereka ke jenis, silakan gunakan sintaks yang secara konsisten dapat diperluas setidaknya var , jika tidak juga func dan const (semua konstruksi sintaksis yang diusulkan memungkinkan untuk semua, kecuali type Foo = pkg.Bar ). Alasannya adalah bahwa, meskipun saya setuju bahwa kasus di mana alias untuk var membuat perbedaan mungkin jarang terjadi, saya tidak berpikir mereka tidak ada dan karena itu percaya bahwa kami mungkin di beberapa titik memutuskan untuk menambahkan mereka juga. Pada saat itu kita pasti ingin agar semua deklarasi alias konsisten, akan buruk jika type Foo = pkg.Bar dan var Foo => pkg.Bar .

Saya juga sedikit berdebat untuk memiliki keempatnya. Alasannya adalah

1) ada perbedaan untuk var dan saya kadang-kadang menggunakannya. Misalnya saya sering mengekspos global var Debug *log.Logger , atau menetapkan kembali lajang global seperti http.DefaultServeMux untuk mencegat/menghapus pendaftaran paket yang menambahkan penangan ke dalamnya.

2) Saya juga berpikir bahwa, sementara func Foo() { pkg.Bar() } melakukan hal yang sama dengan func Foo => pkg.Bar , maksud yang terakhir jauh lebih jelas (terutama jika Anda sudah tahu tentang alias). Ini dengan jelas menyatakan "ini tidak benar-benar dimaksudkan untuk berada di sini". Jadi meskipun secara teknis identik, sintaks alias mungkin berfungsi sebagai dokumentasi.

Ini bukan bukit tempat saya akan mati; type-aliases saja untuk saat ini tidak masalah bagi saya, selama ada opsi untuk memperpanjangnya nanti.

Saya juga sangat senang bahwa ini ditulis seperti itu. Ini merangkum banyak pendapat yang saya miliki tentang desain dan stabilitas API untuk sementara waktu dan akan, di masa depan, berfungsi sebagai referensi sederhana untuk menghubungkan orang juga :)

Namun, saya juga ingin menekankan bahwa ada kasus penggunaan tambahan yang dicakup oleh alias yang berbeda dari dokumen (dan AIUI maksud yang lebih umum dari masalah ini, yaitu menemukan beberapa solusi untuk menyelesaikan perbaikan bertahap). Saya sangat senang jika masyarakat dapat menyetujui konsep memungkinkan perbaikan bertahap, tetapi jika keputusan yang berbeda dari alias diputuskan untuk mencapainya, saya juga berpikir bahwa dalam hal itu harus ada pembicaraan secara bersamaan tentang apakah dan bagaimana mendukung hal-hal seperti impor publik protobuf atau kasus penggunaan paket pengganti drop-in x/image/draw (keduanya agak dekat dengan hati saya juga) dengan solusi yang berbeda. @btracey 's proposal go-tool/gc flag untuk alias adalah contoh di mana saya percaya bahwa, meskipun mencakup perbaikan bertahap dengan relatif baik, itu tidak benar-benar dapat diterima untuk kasus penggunaan lainnya. Anda tidak dapat benar-benar mengharapkan semua orang yang ingin mengkompilasi sesuatu yang menggunakan x/image/draw untuk meneruskan flag-flag tersebut, mereka seharusnya hanya dapat go get .

@jba

@iand jika kami membatasi sisi kanan ke pengidentifikasi yang memenuhi syarat paket, maka itu akan menghilangkan kekhawatiran khusus Anda.

Ini juga berarti Anda tidak dapat memiliki alias untuk jenis apa pun dalam paket saat ini, […]. Tetapi penggunaan itu tidak ada hubungannya dengan tujuan utama perbaikan kode bertahap, jadi mungkin itu bukan kerugian besar.

Mengganti nama dalam sebuah paket (misalnya ke nama yang lebih idiomatis atau konsisten) tentu saja merupakan jenis pemfaktoran ulang yang mungkin ingin dilakukan seseorang, dan jika paket tersebut digunakan secara luas maka itu memerlukan perbaikan bertahap.

Saya pikir pembatasan hanya nama yang memenuhi syarat paket akan menjadi kesalahan. (Pembatasan hanya untuk nama yang diekspor mungkin lebih dapat ditoleransi.)

@btracey

Mungkin sebaliknya mungkin ada mekanisme untuk memasok gc dengan daftar "alias". gc dapat melakukan penggantian untuk sementara, dan pembuat basis kode hilir dapat secara bertahap menghapus item dalam file ini saat perbaikan digabungkan.

Mekanisme untuk gc dapat berarti bahwa kode hanya dapat dibuat dengan gc selama proses perbaikan, atau bahwa mekanisme tersebut harus didukung oleh kompiler lain (mis. gccgo dan llgo ) juga. Mekanisme "non-bahasa-perubahan" yang harus didukung oleh semua implementasi adalah perubahan bahasa de facto — hanya satu dengan dokumentasi yang lebih buruk.

@btracey dan @bcmils , dan bukan hanya kompiler: alat apa pun yang menganalisis kode sumber, seperti guru atau apa pun yang dibuat orang. Ini tentu saja perubahan bahasa tidak peduli bagaimana Anda mengirisnya.

Oke terima kasih.

Kemungkinan lain adalah alias untuk semuanya kecuali consts (dan @rsc mohon maafkan saya karena mengusulkan pembatasan!)

Untuk consts, => benar-benar hanya cara yang lebih panjang untuk menulis = . Tidak ada semantik baru, seperti halnya tipe dan vars. Tidak ada penekanan tombol yang disimpan seperti pada fungsi.

Itu akan menyelesaikan #17784 setidaknya.

Argumen tandingannya adalah bahwa perkakas dapat memperlakukan kasus secara berbeda dan itu bisa menjadi indikator niat. Itu argumen tandingan yang bagus, tapi menurut saya itu tidak melebihi fakta bahwa pada dasarnya ada dua cara untuk melakukan hal yang persis sama.

Yang mengatakan, saya baik-baik saja dengan mengetikkan alias untuk saat ini, mereka tentu saja yang paling penting. Saya sangat setuju dengan @Merovius bahwa kami harus sangat mempertimbangkan untuk mempertahankan opsi untuk menambahkan alias var dan func di masa mendatang, bahkan jika itu tidak terjadi untuk beberapa waktu.

Bagaimana dengan menyembunyikan alias di balik impor, seperti untuk "C" dan "tidak aman", untuk lebih mencegah penggunaannya? Dalam nada yang sama, saya ingin sintaks alias menjadi verbose dan menonjol sebagai perancah untuk melanjutkan refactoring.

Sebagai upaya untuk sedikit membuka ruang desain, berikut beberapa idenya. Mereka tidak disempurnakan. Mereka mungkin buruk dan/atau tidak mungkin; harapan utamanya adalah untuk memicu ide-ide baru/lebih baik pada orang lain. Dan jika ada minat, kita bisa mengeksplorasi lebih jauh.

Ide motivasi untuk (1) dan (2) adalah entah bagaimana menggunakan konversi alih-alih alias. Di #17746, alias mengalami masalah seputar memiliki banyak nama untuk jenis yang sama (atau banyak cara untuk mengeja nama yang sama, tergantung pada apakah Anda menganggap alias seperti #define atau hard link). Menggunakan konversi menghindari itu dengan menjaga jenisnya tetap berbeda.

  1. Tambahkan lebih banyak konversi otomatis.

Saat Anda memanggil fmt.Println("abc") atau menulis var e interface{} = "abc" , "abc" secara otomatis dikonversi menjadi interface{} . Kami dapat mengubah bahasa sehingga ketika Anda telah mendeklarasikan type T struct { S } , dan T tidak memiliki metode yang tidak dipromosikan, kompiler akan secara otomatis mengonversi antara S dan T seperlunya, termasuk secara rekursif di dalam struct lain. T kemudian dapat berfungsi sebagai alias de-facto dari S (atau sebaliknya) untuk tujuan refactoring bertahap.

  1. Tambahkan jenis "tampak seperti" baru.

Biarkan type T ~S mendeklarasikan tipe T baru yang merupakan tipe yang "terlihat seperti S". Lebih tepatnya, T adalah "semua tipe yang dapat dikonversi ke dan dari tipe S". (Seperti biasa, sintaks dapat didiskusikan nanti.) Seperti tipe antarmuka, T tidak dapat memiliki metode; untuk melakukan apa saja pada dasarnya dengan T, Anda perlu mengubahnya menjadi S (atau tipe yang dapat dikonversi ke/dari S). Tidak seperti tipe antarmuka, tidak ada "tipe beton", konversi antara S ke T dan T ke S tidak melibatkan perubahan representasi. Untuk pemfaktoran ulang bertahap, tipe "tampak seperti" ini akan memungkinkan penulis menulis API yang menerima tipe lama dan baru. (Tipe "Sepertinya" pada dasarnya adalah tipe gabungan yang sangat terbatas dan disederhanakan.)

  1. Ketik tag

Bonus ide super-mengerikan. (Tolong jangan repot-repot memberi tahu saya ini mengerikan--saya tahu itu. Saya hanya mencoba untuk memacu ide-ide baru pada orang lain.) Bagaimana jika kita memperkenalkan tag tipe (seperti tag struct), dan menggunakan tag tipe khusus untuk mengatur dan mengontrol alias, seperti misalnya type T S "alias:\"T\"" . Tag tipe akan memiliki kegunaan lain juga dan memberikan cakupan untuk spesifikasi alias yang lebih banyak oleh pembuat paket daripada sekadar "tipe ini adalah alias"; misalnya, pembuat kode dapat menentukan perilaku penyematan.

Jika kita mencoba alias lagi, mungkin ada baiknya memikirkan tentang "apa yang dilakukan godoc", mirip dengan masalah "apa yang dilakukan iota" dan "apa yang dilakukan penyematan".

Secara khusus, jika kita memiliki

type  OldAPI => NewPackage.API

dan NewPackage.API memiliki komentar dokumen, apakah kita diharapkan untuk menyalin/menempelkan komentar itu di sebelah "ketik OldAPI", apakah kita diharapkan untuk tidak berkomentar (dengan godoc secara otomatis menyediakan tautan atau secara otomatis menyalin/menempel), atau akan ada konvensi lain?

Agak tangensial, sementara motivasi utama adalah dan harus mendukung perbaikan kode bertahap, kasus penggunaan kecil (kembali ke proposal alias, karena itu adalah proposal konkret) dapat menghindari overhead panggilan fungsi ganda saat menyajikan fungsi tunggal didukung oleh beberapa implementasi yang bergantung pada tag build. Saya hanya melambaikan tangan sekarang, tetapi saya merasa alias bisa berguna di https://groups.google.com/d/topic/golang-nuts/wb5I2tjrwoc/discussion baru-baru ini "Menghindari panggilan fungsi overhead dalam paket dengan go+asm implementasi" diskusi.

@nigeltao re godoc, saya pikir:

Itu harus selalu menautkan ke aslinya, apa pun itu.

Jika ada dokumen di alias, itu harus ditampilkan, apa pun itu.

Jika tidak ada dokumen pada alias, godoc tergoda untuk menampilkan dokumen asli, tetapi nama jenisnya akan salah jika alias juga mengubah nama, dokumen dapat merujuk ke item yang tidak ada dalam paket saat ini, dan, jika sedang digunakan untuk refactoring bertahap, mungkin ada pesan yang mengatakan "Usang: gunakan X" saat Anda melihat X.

Namun, mungkin itu tidak masalah untuk sebagian besar kasus penggunaan. Itu adalah hal-hal yang bisa salah, bukan hal-hal yang akan salah. Dan beberapa di antaranya dapat dideteksi dengan linting, seperti nama alias yang diganti dan secara tidak sengaja menyalin peringatan penghentian.

Saya tidak yakin apakah ide berikut telah diposting sebelumnya, tetapi bagaimana dengan pendekatan seperti "gofix" / "gorename" yang sebagian besar berbasis alat? Untuk menguraikan:

  • paket apa pun dapat berisi seperangkat aturan penulisan ulang (mis. pemetaan pkg.Ident => otherpkg.Ident )
  • aturan penulisan ulang tersebut dapat ditentukan dengan tag //+rewrite ... di dalam file go arbitrary
  • aturan penulisan ulang tersebut tidak terbatas pada perubahan yang kompatibel dengan ABI, juga dimungkinkan untuk melakukan hal lain (misalnya pkg.MyFunc(a) => pkg.MyFunc(context.Contex(), a) )
  • alat seperti gofix dapat digunakan untuk menerapkan semua transformasi ke repositori saat ini. Ini memudahkan pengguna paket untuk memperbarui kode mereka.
  • tidak perlu memanggil alat gofix agar kompilasi berhasil. Pustaka yang masih ingin menggunakan API lama dari dependensi X (agar tetap kompatibel dengan versi X lama dan baru) masih dapat melakukannya. Perintah go build harus menerapkan transformasi (ditentukan dalam tag penulisan ulang paket X) dengan cepat tanpa mengubah file pada disk.

Langkah terakhir mungkin sedikit memperumit/memperlambat kompiler, tetapi pada dasarnya hanya pra-prosesor dan jumlah aturan penulisan ulang harus tetap kecil. Jadi, cukup brainstorming untuk hari ini :)

Menggunakan alias untuk menghindari overhead panggilan fungsi sepertinya merupakan peretasan untuk mengatasi ketidakmampuan kompiler untuk inline fungsi non-daun. Saya tidak berpikir kekurangan implementasi harus memengaruhi spesifikasi bahasa.

@josharian Meskipun Anda tidak menginginkannya sebagai proposal lengkap, izinkan saya menanggapi (walaupun hanya, sehingga siapa pun yang terinspirasi oleh Anda dapat mempertimbangkan kritik langsung):

  1. Tidak benar-benar menyelesaikan masalah, karena konversi sebenarnya bukan masalah. x/net/context.Context dapat dialihkan/dikonversi/apa pun ke context.Context . Masalahnya adalah tipe tingkat tinggi; yaitu tipe func (ctx x/net/context.Context) dan func (ctx context.Context) tidak sama, meskipun argumennya dapat dialihkan. Jadi, untuk 1 untuk memecahkan masalah, type T struct { S } harus berarti, bahwa T dan S adalah tipe yang identik. Artinya, Anda hanya menggunakan sintaks yang berbeda untuk alias (hanya saja sintaks ini sudah memiliki arti yang berbeda).

  2. Sekali lagi memiliki masalah dengan tipe tingkat tinggi, karena tipe yang dapat dialihkan/dikonversi tidak harus memiliki representasi memori yang sama (dan jika demikian, interpretasinya mungkin berubah secara signifikan). Misalnya, uint8 dapat dikonversi menjadi uint64 dan sebaliknya. Tapi itu berarti, bahwa, misalnya dengan type T ~uint8 , kompilator tidak dapat mengetahui cara memanggil func(T) ; apakah perlu Push 1, 2,4 atau 8 byte pada stack? Mungkin ada cara untuk mengatasi masalah ini, tetapi kedengarannya cukup rumit bagi saya (dan lebih sulit untuk dipahami daripada alias).

Terima kasih, @Merovius.

  1. Ya, saya melewatkan kepuasan antarmuka di sini. Anda benar, ini tidak berhasil.

  2. Saya ada dalam pikiran "memiliki representasi memori yang sama". Konvertibel bolak-balik jelas bukan penjelasan yang tepat untuk itu--terima kasih.

@uluyol ya, ini sebagian besar tentang ketidakmampuan kompiler untuk inline fungsi non-leaf, tetapi aliasing eksplisit mungkin kurang mengejutkan sehubungan dengan apakah panggilan inline ke non-daun harus muncul di jejak stack, runtime.Callers, dll.

Bagaimanapun, seperti yang saya katakan, itu adalah garis singgung kecil.

@josharian Masalah serupa: [2]uintptr dan interface{} memiliki representasi memori yang sama; jadi hanya mengandalkan representasi memori akan memungkinkan menghindari keamanan tipe. uint64 dan float64 memiliki kedua representasi memori yang sama dan dapat dikonversi kembali-dan-sebagainya, tapi masih akan mengakibatkan hasil benar-benar aneh setidaknya, jika Anda tidak tahu yang mana.

Anda mungkin lolos dengan "tipe dasar yang sama", meskipun. Tidak yakin apa implikasinya untuk itu. Di atas topi saya, itu mungkin menyebabkan kesalahan jika suatu jenis digunakan di bidang, misalnya. Jika Anda memiliki type S1 struct { T1 } dan type S2 struct { T2 } (dengan T1 dan T2 tipe dasar yang sama), maka di bawah type L1 ~T1 keduanya mungkin berfungsi sebagai type S struct { L1 } , tetapi karena T1 dan T2 masih memiliki tipe dasar yang berbeda (meskipun terlihat mirip), dengan type L2 ~S1 Anda tidak akan memiliki S2 mirip S1 dan tidak dapat digunakan sebagai L2 .

Jadi Anda harus, di banyak tempat dalam spesifikasi, mengganti atau mengubah "tipe identik" dengan "tipe dasar yang sama" untuk membuat ini berfungsi, yang tampaknya berat dan mungkin akan memiliki konsekuensi tak terduga untuk keamanan tipe. tipe "mirip" juga tampaknya memiliki potensi penyalahgunaan dan kebingungan yang lebih besar daripada alias, IMHO, yang tampaknya menjadi argumen utama menentang alias.

Jika ada yang bisa membuat aturan sederhana untuk itu, yang tidak memiliki masalah ini, itu pasti harus dipertimbangkan sebagai alternatif :)

Melanjutkan dari ide @josharian , ini dia variasi nomor 2:

Izinkan spesifikasi "tipe yang dapat diganti". Ini adalah daftar tipe yang dapat menggantikan tipe bernama dalam argumen fungsi, nilai kembalian, dll. Kompilator akan mengizinkan pemanggilan fungsi dengan argumen tipe bernama atau penggantinya. Tipe pengganti harus memiliki definisi yang kompatibel dengan tipe bernama. Kompatibel di sini berarti representasi memori yang identik dan deklarasi yang identik setelah mengizinkan jenis pengganti lain dalam deklarasi.

Satu masalah langsung adalah bahwa arah hubungan ini berlawanan dengan proposal alias yang membalikkan grafik ketergantungan. Ini saja mungkin membuatnya tidak bisa dijalankan tetapi saya mengusulkannya di sini karena orang lain mungkin memikirkan cara untuk mengatasi ini. Salah satu caranya adalah dengan mendeklarasikan pengganti sebagai komentar //go daripada melalui grafik impor. Dengan cara ini mereka mungkin menjadi lebih seperti makro.

Sebaliknya ada beberapa keuntungan dari pembalikan arah ini:

  • set tipe yang dapat diganti dikendalikan oleh penulis paket baru yang ditempatkan lebih baik untuk menjamin semantik
  • tidak ada perubahan kode yang diperlukan dalam paket asli sehingga klien tidak perlu memperbarui sampai mereka mulai menggunakan paket baru

Menerapkan ini ke refactoring Konteks: paket konteks perpustakaan standar akan mendeklarasikan bahwa context.Context dapat diganti dengan golang.org/x/net/context.Context . Ini berarti penggunaan apa pun yang menerima konteks. Konteks juga dapat menerima golang.org/x/net/context.Context sebagai gantinya. Namun fungsi dalam paket konteks yang mengembalikan Konteks akan selalu mengembalikan context.Context .

Proposal ini menghindari masalah penyematan (#17746) karena nama jenis yang disematkan tidak pernah berubah. Namun, tipe tertanam dapat diinisialisasi menggunakan nilai tipe pengganti.

@iand @josharian Anda meminta varian jenis kovarian tertentu.

@josharian , terima kasih atas sarannya.

Re type T struct { S } , yang terlihat seperti sintaks yang berbeda untuk alias, dan belum tentu lebih jelas.

Re type T ~S , saya tidak yakin bagaimana perbedaannya dari alias atau tidak yakin bagaimana ini membantu refactoring. Saya kira dalam refactoring (katakanlah, io.ByteBuffer -> byte.Buffer), Anda akan menulis:

package io
type ByteBuffer ~bytes.Buffer

tetapi kemudian jika, seperti yang Anda katakan, "pada dasarnya untuk melakukan apa pun dengan T, Anda perlu mengubahnya menjadi S", maka semua kode yang melakukan apa pun dengan io.ByteBuffer masih rusak.

Re type T S "alias" : Poin kunci @bcmills yang dibuat di atas adalah bahwa memiliki beberapa nama yang setara untuk tipe adalah perubahan bahasa, tidak peduli bagaimana ejaannya. Semua kompiler perlu mengetahui bahwa, katakanlah, io.ByteBuffer dan byte.Buffer adalah sama, seperti halnya alat apa pun yang menganalisis atau bahkan mengetik kode. Bagian penting dari saran Anda menurut saya seperti "mungkin kita harus merencanakan ke depan untuk penambahan lain". Mungkin, tetapi tidak jelas bahwa string akan menjadi cara terbaik untuk menggambarkannya, dan juga tidak jelas kami ingin mendesain sintaks (seperti anotasi umum Java) tanpa kebutuhan yang jelas. Bahkan jika kami memiliki bentuk umum, kami masih perlu mempertimbangkan dengan hati-hati semua implikasi dari setiap semantik baru yang kami perkenalkan, dan sebagian besar masih berupa perubahan bahasa yang memerlukan pembaruan semua alat (kecuali gofmt, memang). Pada keseimbangan tampaknya lebih mudah untuk terus menemukan cara paling jelas untuk menulis formulir yang kita butuhkan satu per satu daripada membuat meta-bahasa dari satu jenis atau lainnya.

@Merovius FWIW, saya akan mengatakan bahwa [2]uintptr dan antarmuka{} tidak memiliki representasi memori yang sama. Antarmuka{} adalah [2]unsafe.Pointer bukan [2]uintptr. Uintptr dan pointer adalah representasi yang berbeda. Tapi saya pikir poin umum Anda benar, bahwa kami tidak ingin membiarkan konversi langsung dari hal semacam itu. Maksud saya, dapatkah Anda mengonversi dari antarmuka{} ke [2]*byte juga? Ini jauh lebih banyak daripada yang dibutuhkan di sini.

@jimmyfrasche dan @nigeltao , kembali godoc: Saya setuju bahwa kita perlu bekerja lebih awal juga. Saya setuju bahwa kita tidak boleh membuat kode keras asumsi "fitur baru - apa pun itu - hanya akan digunakan untuk refactoring basis kode". Ini mungkin memiliki kegunaan penting lainnya, seperti yang ditemukan Nigel untuk membantu menulis paket ekstensi gambar dengan alias. Saya berharap hal-hal yang sudah usang akan ditandai sebagai usang dalam komentar dokumen mereka secara eksplisit, seperti yang dikatakan Jimmy. Saya memang berpikir untuk membuat komentar dokumen secara otomatis jika tidak ada, tetapi tidak ada yang jelas untuk dikatakan yang seharusnya belum jelas dari sintaks (berbicara secara umum). Untuk membuat contoh spesifik, pertimbangkan alias Go 1.8 yang lama. Diberikan

type ByteBuffer => bytes.Buffer

kita dapat mensintesis komentar dokumen yang mengatakan "ByteBuffer adalah alias untuk byte.Buffer", tetapi itu tampaknya berlebihan dengan menampilkan definisi. Jika seseorang menulis "tipe X struct{}" hari ini, kita tidak mensintesis "X adalah tipe bernama untuk struct{}".

@ian , terima kasih. Sepertinya proposal Anda mengharuskan penulis paket baru untuk menulis definisi yang tepat dari paket lama dan kemudian juga deklarasi yang menghubungkan keduanya, seperti (membuat sintaks):

package old
type T { x int }

package new
import "old"
type T1 { x int }
substitutable T1 <- old.T

Saya setuju bahwa pembalikan impor bermasalah dan mungkin menjadi penghalang pertunjukan dengan sendirinya, tetapi mari kita lewati itu. Pada titik ini basis kode sepertinya dalam keadaan rapuh: sekarang paket baru dapat dipecah dengan perubahan untuk menambahkan bidang struct di paket lama. Mengingat garis yang dapat disubstitusikan, hanya ada satu kemungkinan definisi untuk T1: persis sama dengan old.T. Jika kedua jenis masih memiliki definisi yang berbeda maka Anda juga harus khawatir tentang metode: apakah implementasi metode juga harus cocok? Jika tidak, apa yang terjadi saat Anda meletakkan T di antarmuka{} lalu menariknya keluar menggunakan pernyataan tipe sebagai T1 dan memanggil M()? Apakah Anda mendapatkan T1.M? Bagaimana jika Anda menariknya keluar sebagai antarmuka { M() }, tanpa menamai T1 secara langsung, dan memanggil M()? Apakah Anda mendapatkan TM? Ada banyak kerumitan yang disebabkan oleh ambiguitas memiliki kedua definisi di pohon sumber.

Tentu saja, Anda dapat mengatakan bahwa garis yang dapat diganti membuat sisanya menjadi berlebihan dan tidak memerlukan definisi untuk tipe T1 atau metode apa pun. Tapi itu pada dasarnya sama dengan menulis (dalam sintaks alias lama) type T1 => old.T .

Kembali ke masalah grafik impor, meskipun contoh dalam artikel semua membuat kode lama didefinisikan dalam istilah kode baru, jika grafik paket sedemikian rupa sehingga yang baru harus mengimpor yang lama, sama efektifnya dengan menempatkan redirect di paket baru selama masa transisi.

Saya pikir ini menunjukkan bahwa dalam transisi apa pun seperti ini, mungkin tidak ada perbedaan yang berguna antara pembuat paket baru dan pembuat paket lama. Pada akhirnya, tujuannya adalah bahwa kode telah ditambahkan ke yang baru dan dihapus dari yang lama, jadi kedua penulis (jika berbeda) perlu dilibatkan. Dan keduanya membutuhkan semacam kompatibilitas terkoordinasi di tengah juga, baik eksplisit (semacam pengalihan) atau implisit (definisi tipe harus sama persis, seperti dalam persyaratan substitusi).

@rsc skenario kerusakan itu menunjukkan bahwa semua jenis aliasing harus dua arah. Bahkan di bawah proposal alias sebelumnya, setiap perubahan dalam paket baru berpotensi merusak sejumlah paket yang kebetulan memiliki tipe alias.

@iand Jika hanya ada satu definisi (karena yang lain mengatakan "sama dengan _that_ one") maka tidak perlu khawatir mereka tidak sinkron.

Di #13467, @joegrasse menunjukkan bahwa akan lebih baik jika proposal ini menyediakan mekanisme untuk mengizinkan tipe C yang identik menjadi tipe Go yang identik saat menggunakan cgo dalam beberapa paket. Itu sama sekali bukan masalah yang sama dengan masalah ini, tetapi kedua masalah terkait dengan tipe aliasing.

Apakah ada ringkasan pembatasan/pembatasan yang diusulkan/diterima/ditolak? Beberapa pertanyaan yang muncul di benak adalah:

  • Apakah RHS selalu memenuhi syarat sepenuhnya?
  • Jika alias ke alias diizinkan, bagaimana kita menangani siklus alias?
  • Haruskah alias dapat mengekspor pengidentifikasi yang tidak diekspor?
  • Apa yang terjadi saat Anda menyematkan alias? (bagaimana Anda mengakses bidang yang disematkan)
  • Apakah alias tersedia sebagai simbol dalam program yang dibangun?
  • injeksi string ldflags: bagaimana jika kita merujuk ke alias?

@rsc Saya tidak ingin mengalihkan percakapan terlalu banyak tetapi di bawah proposal alias jika "baru" menghapus bidang yang "lama" andalkan itu berarti klien "lama" sekarang tidak dapat dikompilasi.

Namun, di bawah proposal pengganti, saya pikir dapat diatur bahwa hanya klien yang menggunakan yang lama dan yang baru yang akan rusak. Agar itu mungkin, maka arahan substitusi harus divalidasi hanya ketika kompiler mendeteksi penggunaan tipe "lama" dalam paket "baru".

@thwd Saya rasa belum ada tulisan yang bagus. Catatan Saya:

  • Siklus alias tidak menjadi masalah. Dalam kasus alias persilangan paket, siklus sudah tidak diizinkan karena siklus impor. Dalam kasus alias non-pelintasan paket, mereka jelas harus dilarang, yang sangat mirip dengan siklus dalam urutan inisialisasi. Secara pribadi, saya ingin memiliki alias untuk alias, karena saya tidak berpikir mereka harus dibatasi pada kasus penggunaan perbaikan bertahap (lihat komentar saya di atas) dan akan menyedihkan, jika paket A dapat rusak oleh seseorang yang memindahkan tipe paket B dengan alias (bayangkan x/image/draw.Image aliasing draw.Image dan kemudian seseorang memutuskan untuk memindahkan draw.Image ke image.Draw melalui alias, dengan asumsi itu aman. Tiba-tiba x/image/draw istirahat, karena alias ke alias tidak diperbolehkan).
  • Saya pikir para pendukung alias sebelumnya telah sepakat bahwa pengekspor alias pengidentifikasi yang tidak diekspor mungkin merupakan ide yang buruk karena keanehan yang mungkin ditimbulkannya. Secara efektif itu berarti bahwa alias ke pengidentifikasi yang tidak diekspor tidak berguna dan dapat dilarang sepenuhnya.
  • Pertanyaan penyematan, AFAIK, masih belum terpecahkan. Pembahasan lengkap ada di #17746, saya berharap diskusi ini akan dilanjutkan jika/kapan/sebelum diputuskan untuk maju dengan alias (tapi masih ada kemungkinan solusi alternatif atau keputusan untuk tidak menjadikan perbaikan bertahap sebagai tujuan sama sekali)

@iand , kembali "hanya klien yang menggunakan lama dan baru bersama-sama akan rusak", itu satu-satunya kasus yang menarik. Ini adalah klien campuran yang menjadikannya perbaikan kode bertahap. Klien yang hanya menggunakan kode baru atau hanya kode lama akan bekerja hari ini.

Ada hal lain yang perlu dipertimbangkan, yang belum saya lihat disebutkan di tempat lain:

Karena tujuan eksplisit di sini adalah untuk memungkinkan refactoring besar dan bertahap dalam basis kode besar yang terdesentralisasi, akan ada situasi di mana pemilik perpustakaan ingin melakukan semacam pembersihan yang akan memerlukan jumlah klien yang tidak diketahui untuk mengubah kode mereka (di final " pensiunkan langkah API lama"). Cara umum untuk melakukannya adalah dengan menambahkan peringatan penghentian, tetapi kompiler Go tidak memiliki peringatan apa pun.

Tanpa peringatan kompiler apa pun, bagaimana pemilik perpustakaan dapat yakin bahwa aman untuk menyelesaikan pemfaktoran ulang?

Satu jawaban mungkin semacam skema pembuatan versi — ini adalah rilis baru perpustakaan dengan API baru yang tidak kompatibel. Dalam hal ini mungkin versi adalah seluruh jawaban, bukan jenis alias.

Atau, bagaimana dengan mengizinkan penulis perpustakaan menambahkan "peringatan penghentian" yang sebenarnya menyebabkan kompilasi _error_ untuk klien, tetapi dengan algoritme eksplisit untuk pemfaktoran ulang yang perlu mereka lakukan? Saya membayangkan sesuatu seperti:

Error: os.time is obsolete, use time.time instead. Run "go upgrade" to fix this.

Untuk alias tipe, saya kira algoritma refactoring hanya akan "ganti semua instance OldType dengan NewType", tetapi mungkin ada kehalusan, saya tidak yakin.

Bagaimanapun, itu akan memungkinkan penulis perpustakaan melakukan upaya terbaik untuk memperingatkan semua klien bahwa kode mereka akan rusak, dan memberi mereka cara mudah untuk memperbaikinya, sebelum menghapus API lama sepenuhnya.

@iainmerrick Ada bug yang terbuka untuk ini: golang/lint#238 dan golang/gddo#456

Memecahkan masalah perbaikan kode bertahap, seperti yang diuraikan dalam artikel @rsc , mengurangi membutuhkan cara agar dua jenis dapat dipertukarkan (karena ada solusi untuk vars, funcs, dan consts).

Ini membutuhkan alat atau perubahan bahasa.

Karena membuat dua jenis dapat dipertukarkan, menurut definisi, mengubah cara kerja bahasa, alat apa pun akan menjadi mekanisme untuk mensimulasikan kesetaraan di luar kompiler, kemungkinan dengan menulis ulang semua instance dari tipe lama ke tipe baru. Tetapi ini berarti alat tersebut harus menulis ulang kode yang tidak Anda miliki, seperti paket vendor yang menggunakan golang.org/x/net/context alih-alih paket konteks stdlib. Spesifikasi untuk perubahan harus dalam file manifes terpisah atau komentar yang dapat dibaca mesin. Jika Anda tidak menjalankan alat ini, Anda mendapatkan kesalahan pembuatan. Itu semua menjadi berantakan untuk dihadapi. Sepertinya alat akan menciptakan banyak masalah seperti yang dipecahkannya. Itu masih menjadi masalah yang harus dihadapi semua orang yang menggunakan paket-paket ini, meskipun agak lebih baik karena sebagian diotomatisasi.

Jika bahasa diubah, kode hanya perlu dimodifikasi oleh pengelolanya, dan, bagi kebanyakan orang, semuanya berfungsi. Perkakas untuk membantu pengelola masih merupakan pilihan, tetapi akan jauh lebih sederhana karena sumbernya adalah spesifikasinya, dan hanya pengelola paket yang perlu memanggilnya.

Seperti yang ditunjukkan @griesemer (saya tidak ingat di mana, ada begitu banyak utas tentang ini) Go sudah memiliki alias, untuk hal-hal seperti byte uint8 , dan ketika Anda mengimpor paket dua kali, dengan nama lokal yang berbeda, ke dalam file sumber yang sama.

Menambahkan cara ke tipe alias secara eksplisit dalam bahasa hanya memungkinkan kita untuk menggunakan semantik yang sudah ada. Melakukannya memecahkan masalah nyata dengan cara yang dapat dikelola.

Perubahan bahasa masih merupakan masalah besar dan banyak hal yang perlu diselesaikan, tetapi saya pikir itu pada akhirnya adalah hal yang benar untuk dilakukan di sini.

Sejauh yang saya ketahui, satu "gajah di dalam ruangan" adalah fakta, bahwa untuk alias tipe, memperkenalkannya akan memungkinkan penggunaan non-sementara (yaitu "non-refactoring"). Saya telah melihat yang disebutkan secara sepintas (misalnya, "mengekspor ulang pengidentifikasi tipe dalam paket berbeda untuk menyederhanakan API"). Mengikuti tradisi yang baik dari proposal sebelumnya, harap cantumkan semua alternatif penggunaan alias jenis yang diketahui di bawah subbagian "dampak" . Hal ini juga harus membawa manfaat dari memicu imajinasi orang untuk menemukan kemungkinan penggunaan alternatif tambahan dan membawanya ke dalam diskusi saat ini. Seperti sekarang, proposal tersebut tampaknya berpura-pura bahwa penulis sama sekali tidak mengetahui kemungkinan penggunaan lain dari alias tipe. Juga, untuk mengekspor ulang, Rust/OCaml mungkin memiliki pengalaman dengan cara kerjanya untuk mereka.

Pertanyaan tambahan: tolong klarifikasi apakah alias tipe akan memungkinkan penambahan metode ke tipe dalam paket baru (bisa dibilang melanggar enkapsulasi) atau tidak? juga, apakah paket baru akan mendapatkan akses ke bidang pribadi dari struct lama, atau tidak?

Pertanyaan tambahan: tolong klarifikasi apakah alias tipe akan memungkinkan penambahan metode ke tipe dalam paket baru (bisa dibilang melanggar enkapsulasi) atau tidak? juga, apakah paket baru akan mendapatkan akses ke bidang pribadi dari struct lama, atau tidak?

Alias ​​hanyalah nama lain untuk suatu tipe. Itu tidak mengubah paket tipe. Jadi tidak untuk kedua pertanyaan Anda (kecuali paket baru == paket lama).

@akavel Sampai sekarang, tidak ada proposal sama sekali. Tapi kami tahu dua kemungkinan menarik yang muncul selama uji coba alias Go 1.8.

  1. Alias ​​​​(atau cukup ketik alias) akan memungkinkan pembuatan pengganti drop-in yang memperluas paket lain. Misalnya lihat https://go-review.googlesource.com/#/c/32145/ , terutama penjelasannya di pesan komit.

  2. Alias ​​​​(atau cukup ketik alias) akan memungkinkan penataan paket dengan permukaan API kecil tetapi implementasi besar sebagai kumpulan paket untuk struktur internal yang lebih baik tetapi masih menyajikan hanya satu paket untuk diimpor dan digunakan oleh klien. Ada contoh abstrak yang dijelaskan di https://github.com/golang/go/issues/16339#issuecomment -232813695.

Tujuan yang mendasari alias sangat bagus, tetapi masih terdengar seperti kami tidak cukup jujur ​​​​dengan tujuan kode refactoring, meskipun itu menjadi motivator nomor satu untuk fitur tersebut. Beberapa proposal menyarankan untuk mengunci nama, dan saya belum melihatnya menyebutkan bahwa tipe biasanya mengubah permukaannya dengan refactoring seperti itu juga. Bahkan contoh os.Error => error sering disebutkan di sekitar alias mengabaikan fakta bahwa os.Error memiliki metode String dan bukan Error . Jika kami baru saja memindahkan jenis dan menamainya, semua kode penanganan kesalahan akan rusak. Itu tempat umum selama refactoring.. metode lama diganti namanya, dipindahkan, dijatuhkan, dan kami tidak ingin mereka dalam tipe baru karena akan mempertahankan ketidakcocokan dengan kode baru.

Untuk membantu, inilah ide awal: bagaimana jika kita melihat masalah dalam hal adaptor, bukan alias? Adaptor akan memberi tipe yang ada nama alternatif _dan antarmuka_, dan dapat digunakan tanpa hiasan di tempat di mana tipe aslinya terlihat sebelumnya. Adaptor perlu secara eksplisit mendefinisikan metode yang didukungnya, daripada mengasumsikan antarmuka yang sama dari jenis adaptasi yang mendasarinya ada. Ini akan sangat mirip dengan perilaku type foo bar , tetapi dengan beberapa semantik tambahan.

io.ByteBuffer

Misalnya, berikut adalah contoh kerangka yang menangani kasus io.ByteBuffer , menggunakan kata kunci "adapts" sementara untuk saat ini:

type ByteBuffer adapts bytes.Buffer

func (old *ByteBuffer) Write(b []byte) (n int, err error) {
        buf := (*bytes.Buffer)(old)
        return buf.Write(b)
}

(... etc ...)

Jadi, dengan adaptor itu, semua kode ini akan valid:

func newfunc(b *bytes.Buffer) { ... }
func oldfunc(b *io.ByteBuffer) { ... }

func main() {
        var newvar bytes.Buffer
        var oldvar io.BytesBuffer

        // New code using the new type obviously just works.
        newfunc(&newvar)

        // New code using the old type receive the underlying value that was adapted.
        newfunc(&oldvar)

        // Old code using the old type receive the adapted value unchanged.
        oldfunc(&oldvar)

        // Old code gets new variable adapted on the way in. 
        oldfunc(&newvar)
}

Antarmuka newfunc dan oldfunc kompatibel. Keduanya benar-benar menerima *bytes.Buffer , dengan oldfunc mengadaptasinya ke *io.BytesBuffer saat masuk. Konsep yang sama berfungsi untuk tugas, hasil, dll.

os.Kesalahan

Logika yang sama mungkin dibuat untuk bekerja pada antarmuka juga, meskipun implementasi kompilernya sedikit lebih rumit. Berikut adalah contoh untuk os.Error => error , yang menangani fakta bahwa metode tersebut diubah namanya:

package os

type Error adapts error

func (e Error) String() string { return error(e).Error() }

Kasus ini perlu pemikiran lebih lanjut, karena metode seperti:

func (v *T) Read(b []byte) (int, os.Error) { ... }`

Akan mengembalikan tipe yang memiliki metode String , jadi kami biasanya ingin beradaptasi dengan arah yang berlawanan sehingga kode dapat diperbaiki secara bertahap.

_UPDATED: Perlu pemikiran lebih lanjut._

Masalah penyematan

Dalam hal bug penyematan yang menyeret fitur keluar dari 1,8, hasilnya sedikit lebih jelas dengan adaptor, karena mereka bukan hanya nama baru untuk hal yang sama: jika adaptor disematkan, nama bidang yang digunakan adalah nama adaptor sehingga logika lama tetap berfungsi, dan mengakses bidang akan menggunakan antarmuka adaptor kecuali secara eksplisit diserahkan ke dalam konteks yang menggunakan tipe yang mendasarinya. Jika tipe yang tidak diadaptasi disematkan, hal yang biasa terjadi.

kubernet, buruh pelabuhan

Masalah yang dinyatakan dalam posting tampak seperti variasi dari masalah di atas, dan diselesaikan dengan proposal.

vars, konstanta

Tidak masuk akal untuk mengadaptasi variabel atau konstanta di bawah skenario itu, karena kita tidak bisa benar-benar mengaitkan metode dengan mereka secara langsung. Itu tipe mereka yang akan diadaptasi atau tidak.

godoc

Kami akan secara eksplisit tentang fakta bahwa benda itu adalah adaptor, dan menunjukkan dokumentasi untuk itu seperti biasa, karena itu berisi antarmuka independen dari benda yang diadaptasi.

sintaksis

Silakan pilih sesuatu yang bagus. ;)

@iainmerrick @zombiezen

Haruskah kita juga secara otomatis menyimpulkan bahwa tipe alias adalah warisan dan harus diganti dengan tipe baru? Jika kami menerapkan golint, godoc, dan alat serupa untuk memvisualisasikan tipe lama sebagai usang, itu akan membatasi penyalahgunaan aliasing tipe dengan sangat signifikan. Dan kekhawatiran terakhir dari fitur aliasing yang disalahgunakan akan teratasi.

Dua pengamatan:

1. Semantik referensi tipe bergantung pada kasus penggunaan refactoring yang didukung

Proposal Gustavo menunjukkan bahwa perlu lebih banyak pekerjaan pada use case untuk referensi tipe dan semantik yang dihasilkan.

Proposal baru Ross menyertakan sintaks baru type OldAPI = newpkg.newAPI . Tapi apa itu semantik? Apakah tidak mungkin untuk memperluas OldAPI dengan metode atau bidang publik lama? Dengan asumsi ya sebagai jawaban yang memerlukan API baru untuk mendukung semua metode publik dan bidang OldAPI untuk menjaga kompatibilitas. Harap dicatat bahwa kode apa pun dalam paket dengan OldAPI yang bergantung pada bidang dan metode pribadi harus ditulis ulang untuk hanya menggunakan API baru publik dengan asumsi bahwa memodifikasi batasan visibilitas paket tidak dapat dilakukan.

Jalur alternatifnya adalah mengizinkan metode tambahan untuk didefinisikan untuk OldAPI. Itu bisa meringankan beban NewAPI untuk menyediakan semua metode lama publik. Tapi itu akan membuat OldAPI menjadi tipe yang berbeda dari NewAPI. Beberapa bentuk penetapan antara nilai-nilai dari dua jenis harus dipertahankan, tetapi aturan akan menjadi kompleks. Mengizinkan penambahan bidang akan menghasilkan lebih banyak kompleksitas.

2. Paket dengan NewAPI tidak dapat mengimpor paket dengan OldAPI

Pendefinisian ulang OldAPI mengharuskan paket O yang berisi definisi OldAPI mengimpor paket N dengan NewAPI. Itu menyiratkan bahwa paket N tidak dapat mengimpor O. Mungkin sangat jelas bahwa itu belum disebutkan, tetapi bagi saya tampaknya merupakan kendala penting untuk kasus penggunaan refactoring.

Pembaruan: Paket N tidak dapat memiliki ketergantungan pada paket O. Misalnya tidak dapat mengimpor paket yang mengimpor O.

@niemeyer Perubahan seperti mengganti nama metode sudah dimungkinkan secara bertahap: a) Tambahkan metode baru, panggil yang lama di bawah tenda (atau sebaliknya), b) ubah semua pengguna secara bertahap ke metode baru, c) hapus metode lama. Anda dapat menggabungkannya dengan alias tipe. Alasan mengapa ini berfokus pada perpindahan tipe adalah, bahwa ini adalah satu-satunya hal yang diidentifikasi, yang belum mungkin. Semua perubahan lain yang teridentifikasi dimungkinkan, bahkan jika mereka menggunakan beberapa langkah (misalnya mengubah kumpulan argumen dari suatu metode tanpa mengganti namanya). Saya percaya memilih perbaikan dengan area permukaan yang lebih sedikit (lebih sedikit hal untuk dipahami) lebih disukai.

@rakyll Secara pribadi, jika saya mempertimbangkan alias berguna untuk sesuatu yang non-refactoring (seperti paket pembungkus, yang menurut saya merupakan kasus penggunaan yang sangat baik) saya hanya akan menggunakannya, peringatan-peringatan terkutuk. Saya akan marah pada siapa pun yang secara artifisial melumpuhkan mereka dan membuat mereka membingungkan bagi pengguna saya, tetapi saya tidak akan berkecil hati.

Saya pikir pada titik tertentu perlu diperdebatkan apakah kita benar-benar menganggap paket pembungkus, impor publik protobuf atau mengekspos API paket internal sebagai hal yang buruk (dan saya tidak tahu cara terbaik untuk memperdebatkan sesuatu yang subjektif ini tanpa satu sisi hanya mengulangi berulang-ulang bahwa mereka tidak dapat dibaca dan yang lain mengatakan "tidak, mereka tidak". Tidak banyak argumen objektif yang bisa didapat di sini, menurut saya).

Saya setidaknya (jelas) berpikir itu adalah hal yang baik dan saya juga berpendapat bahwa menambahkan fitur bahasa dan secara artifisial membatasi hanya satu kasus penggunaan adalah hal yang buruk; bahasa ortogonal yang dirancang dengan baik memungkinkan Anda melakukan sebanyak mungkin dengan fitur sesedikit mungkin. Anda ingin fitur Anda memperluas "ruang vektor terbentang dari program yang memungkinkan" sebanyak mungkin, jadi menambahkan fitur yang hanya menambahkan satu titik ke ruang tampak aneh bagi saya.

Saya ingin kasus penggunaan lain yang sedikit berbeda untuk diingat karena semua jenis alias proposal dikembangkan.

Meskipun kasus penggunaan utama yang kita diskusikan dalam masalah ini adalah tipe _replacement_, alias tipe juga akan sangat berguna untuk melepaskan badan kode dari ketergantungan pada suatu tipe.

Misalnya, misalkan suatu tipe ternyata "tidak stabil" (yaitu terus berubah, mungkin dengan cara yang tidak kompatibel). Kemudian beberapa penggunanya mungkin ingin bermigrasi ke jenis pengganti yang "stabil". Saya sedang memikirkan pengembangan di github dll. Di mana pemilik tipe dan penggunanya tidak harus bekerja sama secara erat atau menyetujui tujuan stabilitas.

Contoh lain adalah di mana satu jenis adalah satu-satunya hal yang menghentikan ketergantungan pada paket besar atau bermasalah agar tidak dihapus, misalnya di mana ketidakcocokan lisensi telah ditemukan.

Jadi proses di sini akan menjadi:

  1. Tentukan jenis alias
  2. Ubah badan kode yang relevan untuk menggunakan jenis alias
  3. Ganti alias tipe dengan definisi tipe.

Pada akhir proses ini, akan ada dua jenis independen yang akan bebas berevolusi ke arah mereka sendiri.

Perhatikan bahwa dalam kasus penggunaan ini:

  • tidak ada opsi untuk mengubah paket yang berisi definisi tipe asli untuk menambahkan alias tipe di sana (karena pemiliknya tidak mungkin menyetujui ini)
  • jenis aslinya tidak ditinggalkan (walaupun mungkin dianggap seperti itu di badan kode dalam proses "disapih" jenisnya).

@Merovius Saat Anda menghapus atau mengganti nama metode lama, Anda membunuh setiap klien yang menggunakannya, sekaligus. Jika Anda bersedia melakukan itu, seluruh latihan non-sepele menambahkan fitur bahasa untuk mencegah kerusakan sekaligus dapat diperdebatkan. Kami mungkin juga mengatakan hal yang persis sama untuk memindahkan kode: cukup ganti nama jenis di setiap situs panggilan sekaligus. Selesai. Kedua tindakan tersebut hanyalah penggantian nama atom, yang memiliki kesamaan fakta bahwa mereka mengasumsikan akses penuh ke setiap baris kode di situs panggilan. Ini mungkin berlaku untuk Google, tetapi sebagai pengelola aplikasi dan perpustakaan open source yang besar, ini bukan dunia tempat saya tinggal.

Dalam kebanyakan kasus, saya menemukan kritik itu tidak adil, karena tim Go berusaha keras untuk membuat proyek inklusif bagi pihak eksternal, tetapi saat Anda menganggap Anda memiliki akses ke setiap baris kode yang memanggil paket tertentu, itu adalah penghalang. taman yang tidak sesuai dengan konteks komunitas open source. Menambahkan fitur refactoring tingkat bahasa yang hanya berfungsi di dalam taman bertembok akan menjadi tidak biasa, untuk sedikitnya.

@niemeyer Saya tampaknya tidak membuat diri saya jelas. Saya tidak menganjurkan untuk menghapus API lama dalam hal apa pun, saya hanya menunjukkan bahwa alur kerja apa pun yang ingin kami aktifkan dengan alias tipe sudah dimungkinkan dengan metode penggantian nama (baik pada saat yang sama atau tidak). Jadi, apa pun yang ingin Anda lakukan, untuk

  1. Tambahkan API baru, dapat dipertukarkan dengan API lama
  2. Ubah konsumen secara bertahap ke API baru
    3a. Setelah semuanya dimigrasikan atau periode penghentian habis, hapus API lama
    3b. Berikan stabilitas tanpa batas dengan menjaga kedua API tetap ada selamanya (lihat misalnya bagian artikel ini )

Anda tampaknya berdebat tentang melakukan 3a vs 3b. Tapi apa yang saya tunjukkan, bahwa 1. sudah mungkin untuk nama metode tetapi tidak mungkin untuk tipe, tentang apa ini.

Meskipun, sekarang saya menyadari bahwa saya pikir saya salah memahami Anda :) Anda mungkin telah menunjukkan, bahwa os.Error adalah definisi antarmuka yang berbeda, jadi langkahnya tidak benar-benar berjalan dengan baik. Saya pikir itu benar; jika Anda melarang menghapus API, alias tipe tidak akan memungkinkan untuk mengganti nama metode tipe antarmuka.

Mungkin Anda dapat mengklarifikasi sesuatu tentang ide adaptor Anda untuk saya: Bukankah itu juga memungkinkan untuk menggunakan (misalnya, dalam kasus os.Error) setiap fmt.Stringer sebagai os.Error?

Bagaimanapun, ide adaptor tampaknya layak untuk dikembangkan lebih lanjut, bahkan jika saya sedikit skeptis tentangnya. Tetapi memiliki cara untuk secara bertahap memperbaiki antarmuka tanpa merusak kemungkinan pelaksana dan/atau konsumen adalah tujuan yang baik.

@niemeyer Ya, Anda mengemukakan poin bagus tentang nama metode yang berubah karena kesalahan juga. Itu menimbulkan banyak komplikasi, dan itu bukan sesuatu yang saya coba atasi di sini. Karena hanya sebagian kecil dari kode yang menyebutkan error/os.Error yang benar-benar memanggil metode, langkahnya adalah bagian yang lebih menyakitkan daripada perubahan metode. Saya pikir kita dapat memperlakukan penggantian nama metode sebagai masalah independen dari mengubah lokasi kode. Jika pemindahan terjadi hari ini dan kami dapat melakukan reorg paket dengan mulus tetapi terjebak dengan nama metode lama, itu masih merupakan kemajuan yang signifikan. Memfokuskan masalah ini pada lokasi kode dimaksudkan untuk mencoba menyederhanakan.

Saya setuju bahwa jika ada beberapa perbaikan umum yang menangani kedua jenis perubahan, itu akan bagus. Saya tidak melihat perbaikan apa itu. Secara khusus saya tidak mengerti bagaimana sakelar tipe bekerja dengan adaptor yang Anda jelaskan: apakah nilainya entah bagaimana secara otomatis dikonversi selama sakelar tipe? Bagaimana dengan refleksi? Memiliki hanya satu tipe dengan dua nama menghindari banyak masalah yang muncul dengan memiliki dua tipe yang otomatis berkonversi bolak-balik.

@rsc Ya, adaptor akan secara otomatis mengonversi dalam setiap situasi, jadi jenis sakelar tidak akan berbeda. Kami akan melarang sakelar tipe yang berisi adaptor dan tipe dasarnya, karena itu akan ambigu. Saya mungkin melewatkan sesuatu, tetapi belum dapat melihat masalah dengan refleksi, karena setiap konteks kode harus menggunakan tipe yang diadaptasi, atau tipe dasarnya, secara eksplisit. Sama seperti hari ini, kita tidak bisa masuk ke interface{} tanpa mengetahui bagaimana kita sampai di sana, jika itu masuk akal.

@Merovius Dua komentar saya di atas secara tepat membahas poin-poin yang masih Anda buat. Jika Anda memindahkan jenis hari ini, Anda memecahkan kode yang perlu diperbaiki. Jika Anda mengganti nama metode, Anda memecahkan kode yang perlu diperbaiki. Jika Anda menghapus metode, mengubah argumennya, Anda memecahkan kode yang perlu diperbaiki. Saat memfaktorkan ulang kode dalam salah satu kasus ini, perbaikan perlu dilakukan secara atomik dengan kerusakan di setiap situs panggilan agar semuanya dapat terus berfungsi. Mengizinkan jenis untuk dipindahkan tetapi sama sekali tidak tersentuh adalah kasus refactoring yang sangat terbatas, yang IMO tidak membenarkan fitur bahasa.

@niemeyer Itu akan menangani tipe konkret. Bagaimana dengan pernyataan tipe untuk .(interface{String() string}) vs .(interface{Error() string}) atau bagian spesifik apa pun dari antarmuka yang diubah? Apakah pemeriksaan harus mempertimbangkan kedua kemungkinan jenis yang mendasarinya?

@niemeyer Tidak. Mengganti nama metode dimungkinkan secara non-atomik. misalnya untuk memindahkan metode dari A.Foo ke A.Bar , lakukan

  1. Tambahkan metode A.Bar sebagai pembungkus di sekitar A.Foo
  2. Migrasikan pengguna untuk hanya menelepon A.Bar melalui banyak komit secara sewenang-wenang
  3. Hapus A.Foo , atau tidak, tergantung pada apakah Anda bersedia menerapkan penghentian.

Ubah argumen fungsi dimungkinkan secara non-atomik. misalnya untuk menambahkan parameter x int ke func Foo() , lakukan

  1. Tambahkan func FooWithInt(x int) { Foo(); // use x somehow; }
  2. Migrasikan pengguna untuk menambahkan parameter melalui banyak komit secara sewenang-wenang
  3. Jika Anda tidak ingin menerapkan penghentian (atau Anda tidak terganggu dengan WithInt), Anda selesai. Jika tidak, ubah Foo menjadi func Foo(x int) { FooWithInt(x) } .
  4. Migrasikan pengguna dengan s/FooWithInt/Foo/g melalui banyak komit secara sewenang-wenang.
  5. Hapus FooWithInt .

Hal yang sama bekerja untuk hampir semua kasus kecuali jenis bergerak (dan, sebenarnya, vars). atomisitas tidak diperlukan. Anda dapat merusak kompatibilitas saat menerapkan penghentian, atau tidak, tetapi itu sepenuhnya ortogonal terhadap atom. Kemampuan untuk menggunakan dua nama berbeda untuk merujuk pada hal yang sama adalah, apa yang memungkinkan Anda untuk menghindari atom ketika melakukan perubahan yang pada dasarnya sewenang-wenang dan Anda memiliki kemampuan itu untuk semua kasus kecuali tipe. Ya, untuk melakukan langkah yang sebenarnya, bukan amandemen, Anda harus bersedia untuk menegakkan bantahan (sehingga melanggar membangun kode berpotensi tidak diketahui, yang berarti lebar kebutuhan ini dan pengumuman tepat waktu). Tetapi bahkan jika Anda tidak, kemampuan untuk menambah API dengan nama yang lebih nyaman atau pembungkus berguna lainnya (lihat x/gambar/gambar) juga tergantung pada kemampuan untuk merujuk ke hal lama dengan nama baru dan sebaliknya.

Perbedaan antara memindahkan tipe hari ini dan mengganti nama fungsi hari ini adalah, dalam kasus sebelumnya, Anda benar-benar membutuhkan perubahan atom, sedangkan untuk yang terakhir, Anda dapat membuat perubahan secara bertahap, melalui repo dan komit independen. Bukan sebagai "Saya akan membuat komit yang melakukan s/Foo/Bar/", tetapi ada proses untuk melakukannya.

Bagaimanapun. Aku tidak tahu di mana kita, rupanya, berbicara melewati satu sama lain. Saya menemukan dokumen @rsc cukup jelas untuk menyampaikan POV saya dan tidak terlalu mengerti milik Anda :)

@rsc saya bisa melihat dua jawaban yang masuk akal. Yang sederhana bahwa antarmuka membawa jenis yang masuk, adaptor atau lainnya, dan semantik biasa berlaku saat menegaskan antarmuka. Yang lainnya adalah bahwa nilainya mungkin tidak disesuaikan jika tidak memenuhi antarmuka tetapi nilai yang mendasarinya melakukannya. Yang pertama lebih sederhana dan mungkin cukup untuk kasus penggunaan refactoring yang ada dalam pikiran kita, sementara yang terakhir mungkin lebih konsisten dengan gagasan bahwa kita dapat mengetik-menegaskannya ke tipe yang mendasarinya juga.

@Merovius Tentu, mengganti nama metode dimungkinkan selama _Anda tidak benar-benar mengganti namanya_ dan memaksa situs panggilan untuk menggunakan API baru sebagai gantinya. Demikian pula, memindahkan jenis dimungkinkan selama _Anda tidak benar-benar memindahkannya,_ dan memaksa situs panggilan untuk menggunakan API baru sebagai gantinya. Kita semua telah melakukan kedua hal ini selama bertahun-tahun untuk menjaga agar kode lama tetap berfungsi.

@niemeyer Tetapi sekali lagi: Untuk tipe, Anda bahkan tidak dapat menambahkan sesuatu dengan cara yang layak. Lihat x/gambar/gambar. Dan tidak semua orang mungkin memiliki pandangan yang mutlak tentang stabilitas; Saya sendiri, baik-baik saja dengan mengatakan "dalam 6,12,… bulan $function,$type,… akan hilang, pastikan Anda bermigrasi darinya pada saat itu" dan kemudian pecahkan kode yang tidak terawat yang tidak berhasil mengikuti pemberitahuan penghentian itu (jika seseorang berpikir mereka membutuhkan dukungan jangka panjang untuk API, mereka pasti dapat menemukan seseorang untuk membayar untuk menyediakannya). Saya bahkan mengklaim bahwa kebanyakan orang tidak memiliki pandangan absolut tentang stabilitas; lihat Push terbaru untuk versi semantik, yang hanya benar-benar masuk akal jika Anda ingin memiliki opsi untuk memutuskan kompatibilitas. Dan doc berpendapat dengan sangat baik, bagaimana, bahkan dalam kasus itu, Anda masih akan mendapat untung dari kemampuan memiliki perbaikan bertahap dan bagaimana hal itu dapat meringankan, jika tidak pada dasarnya memecahkan masalah ketergantungan berlian.

Anda mungkin mengabaikan sebagian besar kasus penggunaan alias untuk perbaikan bertahap karena pendirian Anda terhadap stabilitas adalah mutlak. Tetapi saya akan mengklaim bahwa untuk sebagian besar komunitas go, itu berbeda, bahwa ada keinginan untuk kerusakan dan kegunaan dalam membuatnya semulus mungkin ketika itu terjadi.

@niemeyer @rsc @Merovius Saya telah mengikuti diskusi Anda (dan seluruh diskusi) dan saya ingin secara terang-terangan memukul posting ini tepat di tengahnya.

Semakin banyak kita mengulangi masalah ini, semakin dekat kita dengan beberapa bentuk semantik kovarians yang diperluas. Jadi, begini pemikirannya: kita sudah memiliki semantik subtipe ("is-a") yang didefinisikan dari tipe konkret ke antarmuka dan di antara antarmuka. Proposal saya adalah membuat antarmuka kovarian secara rekursif (sesuai dengan aturan varians saat ini) hingga ke argumen metode mereka.

Ini tidak menyelesaikan masalah untuk semua paket saat ini. Tapi itu bisa memecahkan masalah untuk semua paket masa depan, yang belum ditulis, di mana "bagian yang dapat dipindahkan" dari API dapat menjadi antarmuka (mendorong desain yang baik juga).

Saya pikir kita dapat menyelesaikan semua persyaratan dengan (ab) menggunakan antarmuka dengan cara ini. Apakah kita melanggar Go 1.0? Saya tidak tahu tapi saya pikir kita tidak.

@thwd Saya pikir Anda perlu mendefinisikan lebih tepat apa yang Anda maksud dengan "membuat antarmuka kovarian secara rekursif". Biasanya, dalam subtipe, argumen metode perlu diubah dengan cara yang kontravarian, dan hasilnya dengan cara yang kovarian. Juga, dari apa yang Anda katakan ini tidak akan menyelesaikan masalah yang ada dengan tipe konkret (non-antarmuka).

@thwd Saya tidak setuju, bahwa antarmuka (bahkan yang kovarian) adalah solusi yang baik untuk semua masalah ini (hanya untuk contoh yang sangat spesifik). Untuk menjadikannya satu, Anda harus menjadikan semua yang ada di API Anda sebagai antarmuka (karena Anda tidak pernah tahu apa yang mungkin ingin Anda pindahkan/ubah di beberapa titik), termasuk vars/consts/funcs/... semua, itu adalah desain yang bagus (saya pernah melihatnya di java. Ini memperburuk saya). Jika sesuatu adalah struct, buat saja struct. Segala sesuatu yang lain hanya menambahkan overhead sintaksis yang aneh dalam paket Anda dan setiap ketergantungan terbalik untuk hampir tidak ada manfaat. Ini juga satu-satunya cara untuk tetap waras ketika Anda mulai; mulai sederhana dan pindah ke sesuatu yang lebih umum nanti. Banyak komplikasi dalam API yang saya lihat sejauh ini berasal dari orang-orang yang terlalu memikirkan desain dan perencanaan API untuk hal yang jauh lebih umum daripada yang pernah dibutuhkan. Dan kemudian, dalam 80% (angka itu jelas bohong) dari kasus, tidak ada yang terjadi sama sekali, karena tidak ada "desain API yang bersih".

(untuk lebih jelasnya: Saya tidak mengatakan bahwa antarmuka kovarian bukan ide yang baik. Saya hanya mengatakan bahwa itu bukan solusi yang baik untuk masalah ini)

Untuk menambah poin @Merovius , banyak perbaikan kode bertahap yang saya lihat telah mengambil bentuk memindahkan tipe non-antarmuka yang umumnya berguna dari paket yang jauh lebih besar. Pertimbangkan hal berikut:

package foo

type Authority struct {
  Host string
  Port int
}

Seiring waktu, paket foo tumbuh dan akhirnya mendapatkan lebih banyak tanggung jawab (dan ukuran kode) daripada yang benar-benar diinginkan oleh seseorang yang hanya membutuhkan tipe Authority . Jadi memiliki cara untuk membuat paket fooauthority yang hanya berisi Authority dan memiliki pengguna foo.Authority masih berfungsi adalah kasus penggunaan yang diinginkan. Perhatikan bahwa solusi apa pun yang hanya mempertimbangkan jenis antarmuka tidak akan membantu di sini.

@Merovius Komentar terakhir Anda sepenuhnya subjektif dan ditujukan kepada saya secara pribadi alih-alih proposal saya. Ini tidak akan berakhir dengan baik, jadi saya akan menghentikan diskusi itu di sini.

@griesemer @Merovius Saya setuju dengan Anda berdua. Untuk menutup loop, maka, kita dapat menyetujui bahwa diskusi sejauh ini telah membawa kita pada beberapa gagasan tentang subtipe/kovarians. Juga, bahwa setiap implementasinya tidak boleh menimbulkan tipuan runtime. Itulah yang diusulkan @niemeyer (jika saya memahaminya dengan benar). Tapi saya ingin membaca lebih banyak ide. Saya juga akan memikirkan masalahnya.

@niemeyer Tidak ada _ad hominem_ dalam komentar @Merovius . Klaimnya bahwa "sikap Anda terhadap stabilitas adalah mutlak" adalah pengamatan tentang posisi Anda, bukan Anda, dan merupakan kesimpulan yang masuk akal dari beberapa pernyataan Anda, seperti

Saat Anda menghapus atau mengganti nama metode lama, Anda membunuh setiap klien yang menggunakannya sekaligus.

dan

Tentu, mengganti nama metode dimungkinkan selama Anda tidak benar-benar mengganti namanya dan memaksa situs panggilan untuk menggunakan API baru sebagai gantinya. Demikian pula, memindahkan jenis dimungkinkan selama Anda tidak benar-benar memindahkannya, dan memaksa situs panggilan untuk menggunakan API baru sebagai gantinya. Kita semua telah melakukan kedua hal ini selama bertahun-tahun untuk menjaga agar kode lama tetap berfungsi.

Saya mendapat kesan yang sama seperti Merovius dari pernyataan-pernyataan itu—bahwa Anda tidak bersimpati untuk mencela sesuatu untuk sementara waktu, lalu akhirnya menghapusnya; bahwa Anda berkomitmen untuk menjaga agar kode tetap bekerja tanpa batas waktu; bahwa "sikap Anda terhadap stabilitas adalah mutlak". (Dan untuk mencegah kesalahpahaman lebih lanjut, saya menggunakan "Anda" untuk merujuk pada ide Anda, bukan kepribadian Anda.)

@niemeyer Deklarasi adapts Anda sarankan tampaknya terkait erat dengan instance dari kelas tipe Haskell. Menerjemahkan secara longgar itu ke Go, mungkin terlihat seperti:

package os

type Error interface {
  String() string
}

instance error Error (
  func (e error) String() string { return e.Error() }
)

Sayangnya (seperti yang dicatat @zombiezen ), tidak jelas bagaimana ini akan membantu untuk tipe non-antarmuka.

Juga tidak jelas bagi saya bagaimana ia akan berinteraksi dengan tipe fungsi (argumen dan nilai kembali); misalnya, bagaimana semantik adapts membantu memigrasikan Context ke pustaka standar?

Saya mendapat kesan yang sama dengan Merovius dari pernyataan itu—bahwa Anda tidak bersimpati untuk mencela sesuatu untuk sementara waktu.

@jba Ini adalah fakta mutlak, bukan opini mutlak. Jika Anda menghapus metode atau tipe, kode Go yang menggunakannya rusak, jadi perubahan ini perlu dilakukan secara atom. Proposal saya, bagaimanapun, adalah tentang refactoring kode secara bertahap, yang merupakan subjek di sini dan menyiratkan penghentian. Proses penolakan itu, bagaimanapun, bukanlah masalah simpati. Saya memiliki beberapa paket Go publik dengan ribuan dependensi di alam liar masing-masing, dan beberapa API independen karena evolusi bertahap itu. Saat kami memecahkan API, ada baiknya melakukan kerusakan seperti itu dalam batch, daripada mengalirkannya, jika kami berharap tidak membuat orang gila. Kecuali, tentu saja, Anda tinggal di taman bertembok dan dapat menjangkau setiap situs panggilan untuk memperbaikinya. Tapi saya ulangi sendiri.. semua itu bisa dibaca dalam proposal asli di atas dengan cara yang lebih jelas.

@Merovius

Secara pribadi, jika saya menganggap alias berguna untuk sesuatu yang non-refactoring (seperti paket pembungkus, yang menurut saya merupakan kasus penggunaan yang sangat baik) saya hanya akan menggunakannya, peringatan penghentian terkutuk.

Kami memelihara paket dengan sejumlah besar API baru dan yang tidak digunakan lagi serta memiliki alias tanpa penjelasan yang jelas tentang status tipe lama (alias) tidak akan membantu perbaikan kode bertahap dan hanya akan berkontribusi pada peningkatan permukaan API yang berlebihan. Saya setuju dengan @niemeyer bahwa solusi kami perlu memenuhi persyaratan komunitas pengembang terdistribusi yang saat ini tidak memiliki sinyal lain selain teks godoc bentuk bebas yang mengatakan bahwa API "tidak digunakan lagi". Menambahkan fitur bahasa untuk membantu mencela tipe lama adalah topik utas ini, oleh karena itu secara alami mengarah pada pertanyaan tentang apa status tipe lama (alias).

Saya ingin membahas jenis aliasing di bawah tema yang berbeda seperti memberikan ekstensi ke jenis atau sebagian paket tetapi tidak di utas ini. Topik itu sendiri memiliki berbagai masalah khusus enkapsulasi yang harus ditangani sebelum pertimbangan apa pun.

Operator tertentu atau menyiratkan bahwa alias yang diketik agak diganti mungkin sehat untuk berkomunikasi dengan pengguna bahwa mereka perlu beralih. Diferensiasi semacam itu akan memungkinkan alat untuk secara otomatis melaporkan API yang diganti.

Untuk lebih jelasnya, kebijakan penghentian secara teknis tidak memungkinkan untuk tipe di luar pustaka standar. Jenis hanya tua dari perspektif paket aliasing. Mengingat kita tidak pernah dapat menerapkan ini di ekosistem, saya masih ingin melihat alias perpustakaan standar menyiratkan penghentian secara ketat (ditunjukkan oleh pemberitahuan penghentian yang tepat).

Saya juga menyarankan agar kita menstandardisasi gagasan penghentian dalam diskusi paralel dan mendapatkan dukungan untuk mereka di alat inti kita (golint, godoc, dll). Kurangnya pemberitahuan penghentian adalah masalah terbesar dalam ekosistem Go dan lebih luas daripada masalah perbaikan kode bertahap.

@rakyll Saya bersimpati dengan kasus penggunaan memiliki pemberitahuan penghentian yang dapat dibaca komputer; Saya hanya keberatan dengan gagasan a) alias menjadi itu dan b) memancarkannya sebagai peringatan kompiler.

Untuk a), terlepas dari kenyataan bahwa saya ingin menggunakan alias secara produktif untuk hal-hal lain selain bergerak, itu juga hanya berlaku untuk serangkaian penghentian yang sangat kecil. Misalnya, katakan saya ingin menghapus beberapa parameter dari suatu fungsi dalam beberapa rilis; Saya tidak dapat menggunakan alias, sungguh, karena tanda tangan dari API baru akan berbeda, tetapi saya tetap ingin mengumumkannya. Untuk b), peringatan kompiler IMHO secara universal buruk. Saya pikir ini sebagian besar sejalan dengan apa yang sudah dilakukan, jadi saya tidak berpikir itu membutuhkan pembenaran.

Saya setuju dengan semua yang Anda katakan tentang pemberitahuan penghentian. Sudah ada sintaks untuk ini, rupanya: #10909, jadi langkah selanjutnya untuk membuatnya lebih berguna adalah meningkatkan dukungan alat dengan menyorotnya di godoc dan memiliki tanda centang yang memperingatkan tentang penggunaannya (katakanlah go vet, golint atau alat yang terpisah sama sekali).

@rakyll Saya setuju bahwa stdlib harus dimulai dengan penggunaan alias tipe konservatif, jika diperkenalkan.


Bilah samping:

Latar belakang bagi mereka yang tidak mengetahui status komentar penghentian di Go dan alat terkait, karena agak tersebar:

Seperti yang disebutkan @Merovius di atas, ada konvensi standar untuk menandai item sebagai usang, #10909, lihat https://blog.golang.org/godoc-documenting-go-code

TL;DR: buat paragraf di dokumen item usang yang dimulai dengan "Usang: " dan jelaskan apa penggantinya.

Ada proposal yang diterima untuk godoc untuk menampilkan item usang dengan cara yang lebih berguna: #17056.

@rakyll mengusulkan agar golint memperingatkan saat item usang digunakan: golang/lint#238.


Bahkan jika stdlib mengambil sikap konservatif tentang penggunaan alias dalam stdlib, saya tidak berpikir bahwa keberadaan alias tipe harus menyiratkan (dengan cara apa pun yang terdeteksi secara mekanis atau dilambangkan secara visual) bahwa tipe lama sudah usang, bahkan jika itu selalu berarti bahwa dalam praktek.

Melakukannya berarti salah satu dari:

  • memindai paket stdlib lain untuk melihat apakah jenis apa pun, tidak secara eksplisit ditandai sebagai usang, alias di tempat lain
  • hardcoding semua alias stdlib menjadi alat otomatis
  • hanya melaporkan bahwa tipe lama tidak digunakan lagi ketika Anda sudah melihat penggantinya, yang tidak membantu penemuan

Ketika alias tipe diperkenalkan karena tipe lama sudah tidak digunakan lagi, itu perlu ditangani dengan menandai tipe lama yang tidak digunakan lagi, dengan referensi ke tipe baru, terlepas dari itu.

Ini memungkinkan perkakas yang lebih baik ada dengan membuatnya lebih sederhana dan lebih umum: tidak perlu kasus khusus apa pun atau bahkan tahu tentang alias tipe: itu hanya perlu mencocokkan "Usang: " dalam komentar dokumen.

Kebijakan resmi, jika mungkin sementara, bahwa alias di stdlib hanya untuk penghentian adalah baik, tetapi itu hanya boleh ditegakkan dengan komentar penghentian standar dan dengan melarang penggunaan lain untuk melewati tinjauan kode.

@niemeyer Balasan saya sebelumnya hilang karena kehilangan daya :( rusak:

Tapi saya ulangi sendiri..

FWIW, menurut saya balasan terakhir Anda cukup membantu. Itu meyakinkan saya, bahwa kami lebih setuju, daripada yang terlihat sebelumnya (dan mungkin masih tampak bagi Anda). Tampaknya masih ada miskomunikasi di suatu tempat.

Proposal saya, bagaimanapun, adalah tentang refactoring kode secara bertahap

Ini tidak kontroversial, saya pikir. :) Saya setuju, dari awal, bahwa proposal Anda adalah alternatif yang menarik untuk dipertimbangkan untuk mengatasi masalah tersebut. Yang membuat saya bingung adalah pernyataan seperti ini:

Jika Anda menghapus metode atau tipe, kode Go yang menggunakannya rusak, jadi perubahan ini perlu dilakukan secara atom.

Saya masih bertanya-tanya apa alasan Anda di sini. Saya memahami unit atom sebagai komit tunggal. Dengan asumsi itu, saya benar-benar tidak mengerti mengapa Anda yakin bahwa penghapusan suatu metode atau tipe tidak dapat pertama-tama terjadi secara terpisah, banyak komit secara sewenang-wenang di repositori tergantung dan kemudian, setelah tidak ada pengguna yang jelas lagi (dan cukup banyak penghentian interval telah berlalu) metode atau tipe dihapus dalam komit hulu (tanpa merusak apa pun, karena tidak ada yang bergantung lagi). Saya setuju bahwa ada faktor ketidakjelasan tertentu di sekitar dependensi terbalik yang tidak mematuhi penghentian atau yang tidak dapat Anda temukan (atau perbaiki secara wajar), tetapi, bagi saya, tampaknya sebagian besar tidak tergantung pada masalah yang dihadapi; Anda akan memiliki masalah itu setiap kali Anda menerapkan perubahan yang melanggar dan tidak peduli bagaimana Anda mencoba mengaturnya.

Dan, agar adil: Kebingungan tidak terlalu terbantu oleh kalimat seperti

Kecuali, tentu saja, Anda tinggal di taman bertembok dan dapat menjangkau setiap situs panggilan untuk memperbaikinya.

Jika apa pun yang saya katakan memberi Anda kesan bahwa ini adalah poin yang saya perdebatkan, saya harap Anda dapat mengambil langkah mundur dan mungkin membacanya kembali dengan asumsi bahwa saya berdebat sepenuhnya dari posisi open source komunitas (jika Anda tidak mempercayai saya, silakan lihat kontribusi saya sebelumnya untuk topik ini; Saya selalu menjadi orang pertama yang menunjukkan bahwa ini lebih merupakan masalah komunitas, daripada masalah monorepo. Monorepo memiliki cara untuk mengatasinya , seperti yang Anda tunjukkan).

Bagaimanapun. Saya menemukan ini sama menguras tenaga seperti Anda. Saya harap saya akan memahami posisi Anda di beberapa titik, meskipun.

secara bersamaan berbicara tentang jika dan bagaimana mendukung hal-hal seperti impor publik protobuf ...
Saya pikir pada titik tertentu perlu diperdebatkan apakah kita benar-benar menganggap paket pembungkus, impor publik protobuf atau mengekspos API paket internal sebagai hal yang buruk

nit: Saya tidak berpikir impor publik protobuf perlu disebutkan sebagai kasus penggunaan sekunder khusus. Mereka dirancang untuk perbaikan kode bertahap, seperti yang disebutkan secara eksplisit di kedua dokumen desain internal dan bahkan dokumentasi publik , jadi mereka sudah berada di bawah payung masalah yang dijelaskan oleh masalah ini. Juga, saya percaya alias tipe akan cukup untuk mengimplementasikan impor publik protobuf. (Kompiler proto menghasilkan vars, tetapi secara logika const, jadi "var Enum_name =import.Enum_name" sudah cukup.)

@Merovius Terima kasih atas tanggapan yang produktif. Biarkan saya mencoba memberikan beberapa konteks:

Saya masih bertanya-tanya apa alasan Anda di sini. Saya memahami unit atom sebagai komit tunggal. Dengan asumsi itu, saya sama sekali tidak mengerti mengapa Anda yakin bahwa penghapusan suatu metode atau tipe tidak dapat terjadi terlebih dahulu secara terpisah,

Tidak pernah mengatakan itu tidak bisa terjadi. Biarkan saya mundur selangkah dan menyatakan kembali dengan lebih jelas.

Kita mungkin semua setuju bahwa tujuan akhirnya ada dua: kita ingin perangkat lunak yang berfungsi, dan kami ingin meningkatkan perangkat lunak sehingga kami dapat terus mengerjakannya dengan cara yang waras. Beberapa yang terakhir melanggar perubahan, membuatnya bertentangan dengan tujuan sebelumnya. Jadi ada ketegangan, yang berarti ada subjektifitas di mana sweet spot berada. Bagian yang menarik dari perdebatan kita terletak di sini.

Salah satu cara yang berguna untuk mencari sweet spot itu adalah dengan memikirkan intervensi manusia. Artinya, begitu Anda melakukan sesuatu yang mengharuskan orang untuk memodifikasi kode secara manual agar tetap berfungsi, inersia terjadi. Dibutuhkan waktu lama untuk bagian yang relevan dari semua basis kode dependen untuk melalui proses ini. Kami meminta orang-orang sibuk untuk melakukan hal-hal yang dalam banyak kasus mereka tidak ingin repot.

Cara lain untuk melihat sweet spot itu adalah kemungkinan perangkat lunak yang berfungsi. Tidak masalah seberapa banyak kami meminta orang untuk tidak menggunakan metode yang sudah usang. Jika mudah diakses dan memecahkan masalah mereka di sini dan sekarang, sebagian besar pengembang hanya akan menggunakannya. Argumen tandingan yang umum di sini adalah: _oh, tapi kemudian itu masalah mereka ketika rusak!_ Tapi itu bertentangan dengan tujuan yang dinyatakan: kami ingin perangkat lunak yang berfungsi, bukan yang benar.

Jadi, semoga ini memberikan lebih banyak wawasan tentang mengapa hanya memindahkan suatu tipe tampaknya tidak membantu. Agar orang benar-benar menggunakan tipe baru itu di rumah barunya, kita perlu campur tangan manusia. Ketika orang mengalami kesulitan mengubah kode mereka secara manual, yang terbaik adalah memiliki intervensi yang _menggunakan tipe baru_ daripada sesuatu yang akan segera berubah lagi di bawah kaki mereka di masa mendatang. Jika kita mengalami kesulitan menambahkan fitur bahasa untuk membantu refactoring, idealnya itu akan memungkinkan orang untuk secara bertahap memindahkan kode mereka _ke tipe baru itu,_ tidak hanya ke rumah baru, karena alasan di atas.

Terima kasih untuk penjelasannya. Saya pikir saya lebih memahami posisi Anda sekarang dan setuju dengan asumsi Anda (yaitu, bahwa orang akan menggunakan barang-barang usang apa pun yang terjadi, jadi memberikan bantuan apa pun yang mungkin untuk memandu mereka ke penggantian adalah yang terpenting). FWIW, rencana naif saya untuk mengatasi masalah ini (tidak peduli solusi mana untuk perbaikan bertahap yang akan kami gunakan) adalah alat seperti perbaikan untuk secara otomatis memigrasikan paket-demi-paket dalam periode penghentian, tetapi saya dengan bebas mengakui topi Saya belum mencoba bagaimana dan apakah itu berhasil dalam praktik.

@niemeyer Saya tidak percaya saran Anda bisa diterapkan tanpa gangguan serius pada sistem tipe Go.

Pertimbangkan dilema yang disajikan oleh kode ini:

package old
import "new"
type A adapts new.A
func (a A) NewA() {}

package new
type A struct{}
func (a A) OldA() {}

package main
import (
    "new"
    "old"
    "reflect"
)
func main() {
    oldv := reflect.ValueOf(old.A{})
    newv := reflect.ValueOf(new.A{})
    if oldv.Type() == newv.Type() {
        // The two types are equal, therefore they must
        // have exactly the same method set, so either
        // oldv doesn't have the OldA method or newv doesn't
        // have the NewA method - both of which imply a contradiction
        // in the type system.
    } else {
         // The two types are not equal, which means that the
         // old adapted type is not fully compatible with the old
         // one. Any type that includes either new.A or new.B will
         // be incompatible as one of its components will likewise be
         // unequal, so any code that relies on dynamic type checking
         // will fail when presented with the type that's not using the
         // expected version.
    }
 }

Salah satu aksioma saat ini dari paket reflect adalah jika dua tipe sama, nilai reflect.Type mereka sama. Ini adalah salah satu dasar efisiensi konversi tipe runtime Go. Sejauh yang saya lihat tidak ada cara untuk mengimplementasikan kata kunci "adapts" tanpa melanggar ini.

@rogpeppe Lihat percakapan dengan @rsc tentang refleksi di atas. Kedua tipe ini tidak sama, jadi refleksikan hanya akan mengatakan yang sebenarnya dan memberikan detail untuk adaptor ketika ditanya tentang hal itu.

@niemeyer Jika kedua jenisnya tidak sama, maka saya rasa kami tidak dapat mendukung perbaikan kode bertahap saat memindahkan jenis di antara paket. Misalnya, kita ingin membuat paket gambar baru yang mempertahankan kompatibilitas tipe.

Kami mungkin melakukan:

package newimage
import "image"
type RGBA adapts image.RGB
func (r *RGBA) At(x, y) color.Color {
    return (*image.Buffer)(r).At(x, y)
}
etc for all the methods

Mengingat tujuan perbaikan kode bertahap, saya pikir masuk akal untuk mengharapkannya
gambar yang dibuat dalam paket baru kompatibel dengan fungsi yang ada
yang menggunakan tipe gambar lama.

Mari kita asumsikan demi argumen bahwa paket image/png memiliki
telah dikonversi untuk menggunakan gambar baru tetapi gambar/jpeg belum.

Saya percaya bahwa kita harus mengharapkan kode ini berfungsi:

img, err := png.Decode(r)
if err != nil { ... }
err = jpeg.Encode(w, img, nil)

tetapi, karena tipe ini menyatakan terhadap *image.RGBA bukan *newimage.RGBA,
itu akan gagal AFAICS, karena jenisnya berbeda.

Katakanlah kita membuat tipe assert di atas berhasil, apakah tipenya adalah *image.RGBA
atau tidak. Itu akan mematahkan invarian saat ini bahwa:

reflect.TypeOf(x) == reflect.TypeOf(x.(anyStaticType))

Artinya, menggunakan pernyataan tipe statis tidak hanya akan menegaskan tipe statis a
nilai tetapi kadang-kadang itu benar-benar akan mengubahnya.

Katakanlah kami memutuskan itu baik-baik saja, maka mungkin kami juga membutuhkan
untuk memungkinkan konversi tipe yang disesuaikan ke antarmuka apa pun yang kompatibel dengannya
dukungan jenis yang disesuaikan, jika tidak, kode baru atau lama akan berhenti
berfungsi saat mengonversi ke tipe antarmuka yang kompatibel dengan
jenis yang mereka gunakan.

Ini mengarah ke situasi kontradiktif lainnya:

// oldInterface is some interface with methods that
// are only supported by the old type.
type oldInterface interface {
    OldMethod()
}
var x = interface{} = newpackage.Type{}
switch x.(type) {
case oldInterface:
    // This would fail because the newpackage.Type
    // does not implement OldMethod, even though we
    // we just supposedly checked that x implements OldMethod.
    reflect.TypeOf(x).Method("OldMethod")
}

Secara keseluruhan, saya pikir memiliki dua tipe yang sama tetapi berbeda
akan menyebabkan sistem tipe yang sangat sulit dijelaskan dan ketidakcocokan yang tidak terduga
dalam kode yang menggunakan tipe dinamis.

Saya mendukung proposal "tipe X = Y". Sederhana untuk dijelaskan dan tidak
mengganggu sistem tipe terlalu banyak.

@rogpeppe : Saya percaya bahwa saran @niemeyer adalah secara implisit mengonversi tipe yang disesuaikan ke tipe dasarnya, mirip dengan saran @josharian sebelumnya .

Untuk membuatnya berfungsi untuk pemfaktoran ulang bertahap, itu juga harus secara implisit mengonversi fungsi dengan argumen dari tipe yang disesuaikan; pada dasarnya, itu akan membutuhkan penambahan kovarians ke bahasa. Itu tentu saja bukan tugas yang mustahil — banyak bahasa memungkinkan kovarians, terutama untuk tipe dengan struktur dasar yang sama — tetapi hal itu menambah banyak kerumitan pada sistem tipe, terutama untuk tipe antarmuka .

Itu memang mengarah ke beberapa kasus tepi yang menarik, seperti yang telah Anda catat, tetapi itu tidak selalu "bertentangan" dengan sendirinya:

type oldInterface interface {
    OldMethod()
}
var x = interface{} = newpackage.Type{}
switch y := x.(type) {
case oldInterface:
    reflect.TypeOf(y).Method("OldMethod")  // ok
    reflect.TypeOf(x).Method("NewMethod")  // ok

    // This would fail because y has been implicitly converted to oldInterface.
    reflect.TypeOf(y).Method("NewMethod")

    // This would fail because accessing OldMethod on newpackage.Type requires
    // a conversion to oldInterface.
    reflect.TypeOf(x).Method("OldMethod")
}
// This would fail because accessing OldMethod on newpackage.Type requires
// a conversion to oldInterface.

Bagi saya ini masih kontradiktif. Model saat ini sangat sederhana: nilai antarmuka memiliki tipe statis dasar yang terdefinisi dengan baik. Dalam kode di atas kami menyimpulkan sesuatu tentang tipe yang mendasarinya, tetapi ketika kami mengintip nilainya, sepertinya tidak seperti yang kami simpulkan. Ini adalah perubahan bahasa yang serius (dan sulit dijelaskan) menurut saya.

Diskusi di sini tampaknya mereda. Berdasarkan saran oleh @egonelbre di https://github.com/golang/go/issues/16339#issuecomment -247536289 , saya telah memperbarui komentar masalah asli (di atas) untuk menyertakan ringkasan tertaut dari diskusi sehingga jauh. Saya akan memposting komentar baru, seperti ini, setiap kali saya memperbarui ringkasan.

Secara keseluruhan, tampaknya sentimen di sini adalah untuk alias tipe daripada alias umum. Mungkin ide adaptor Gustavo akan menggantikan alias tipe, tetapi mungkin tidak. Tampaknya agak rumit saat ini, meskipun mungkin pada akhir diskusi akan diperoleh bentuk yang lebih sederhana. Saya menyarankan agar diskusi berlanjut sebentar lagi.

Saya masih tidak yakin bahwa vars global yang dapat berubah adalah "biasanya bug" (dan dalam kasus di mana mereka adalah bug, detektor balapan adalah alat pilihan untuk menemukan bug semacam itu). Saya akan meminta bahwa, jika argumen itu digunakan untuk membenarkan kurangnya sintaks yang dapat diperluas, pemeriksaan dokter hewan diimplementasikan yang - katakanlah - memeriksa penugasan ke variabel global dalam kode yang tidak secara eksklusif dapat dijangkau oleh init() atau deklarasinya. Saya secara naif berpikir bahwa ini tidak terlalu sulit untuk diterapkan dan seharusnya tidak banyak pekerjaan untuk menjalankannya - katakanlah - semua paket terdaftar godoc.org untuk melihat apa kasus penggunaan untuk vars global yang dapat berubah dan apakah kita melakukannya menganggap mereka semua bug.

(Saya juga ingin percaya bahwa, jika go menumbuhkan vars global yang tidak dapat diubah, mereka harus menjadi bagian dari deklarasi-konst, karena itulah mereka secara konseptual dan karena itu akan kompatibel ke belakang, tetapi saya mengakui bahwa ini kemungkinan akan mengarah ke komplikasi di sekitar jenis ekspresi apa yang dapat digunakan dalam tipe array, misalnya dan akan membutuhkan lebih banyak pemikiran)

Re "Pembatasan? Alias ​​​​jenis perpustakaan standar hanya dapat dideklarasikan di perpustakaan standar." -- terutama, itu akan mencegah kasus penggunaan drop-in untuk x/image/draw , paket yang ada yang telah menyatakan minatnya untuk menggunakan alias. Saya juga bisa membayangkan dengan baik, misalnya, paket router atau sejenisnya menggunakan alias ke net/http dengan cara yang sama ( gelombang tangan ).

Saya juga setuju dengan kontra-argumen Re semua pembatasan, yaitu saya mendukung tidak memiliki salah satu dari mereka.

@Merovius , bagaimana dengan vars global _exported_ yang dapat diubah? Memang benar bahwa global yang tidak diekspor mungkin baik-baik saja karena semua kode dalam paket tahu cara menanganinya dengan benar. Kurang jelas bahwa ekspor global yang bisa berubah menjadi masuk akal. Kami membuat kesalahan ini sendiri beberapa kali di perpustakaan standar. Misalnya tidak ada cara yang sepenuhnya aman untuk memperbarui runtime.MemProfileRate. Yang terbaik yang dapat Anda lakukan adalah mengaturnya di awal program Anda dan berharap bahwa tidak ada paket yang Anda impor yang memulai goroutine inisialisasi yang mungkin mengalokasikan memori. Anda mungkin benar tentang var vs const, tetapi kita dapat membiarkannya untuk hari lain.

Poin bagus tentang x/gambar/gambar. Akan ditambahkan ke ringkasan di pembaruan berikutnya.

Saya sangat ingin mengumpulkan korpus perwakilan kode Go yang dapat kami analisis untuk menjawab pertanyaan seperti yang Anda ajukan. Saya mulai mencoba melakukan ini beberapa minggu yang lalu dan saya mengalami beberapa masalah. Ini sedikit lebih banyak pekerjaan daripada yang seharusnya, tetapi sangat penting untuk memiliki kumpulan data itu, dan saya berharap kita akan sampai di sana.

@rsc presentasi GothamGo Anda tentang topik ini telah diposting di youtube https://www.youtube.com/watch?v=h6Cw9iCDVcU dan akan menjadi tambahan yang bagus untuk posting pertama.

Di bagian "Masalah apa lagi yang perlu ditangani oleh proposal untuk jenis alias?" bagian akan sangat membantu untuk menentukan bahwa jawaban untuk "Dapatkah metode didefinisikan pada tipe yang dinamai dengan alias?" adalah tidak sulit. Saya menyadari bahwa hal itu bertentangan dengan semangat yang ditetapkan dari bagian tersebut, tetapi saya perhatikan bahwa, dalam banyak percakapan tentang alias, di sini dan di tempat lain, ada orang yang langsung menolak konsep tersebut karena mereka percaya bahwa alias akan memungkinkan hal ini dan dengan demikian menyebabkan masalah daripada memecahkannya. Ini tersirat dalam definisi, tetapi secara eksplisit menyebutkannya akan membuat banyak hubungan pendek bolak-balik yang tidak perlu. Meskipun mungkin itu termasuk dalam FAQ alias dalam proposal baru untuk alias, apakah itu hasil dari utas ini.

@Merovius, setiap variabel yang dapat diubah paket-global yang diekspor dapat disimulasikan oleh

Diberikan versi n dari sebuah paket p ,

package p
var Global = 0

pada versi n+1 getter dan setter dapat diperkenalkan dan variabel tidak digunakan lagi

package p
//Deprecated: use GetGlobal and SetGlobal.
var Global = 0
func GetGlobal() int {
    return Global
}
func SetGlobal(n int) {
   Global = n
}

dan versi n + 2 dapat membatalkan ekspor Global

package p
var global = 0
func GetGlobal() int {
    return global
}
func SetGlobal(n int) {
   global = n
}

(Latihan diserahkan kepada pembaca: Anda juga dapat membungkus akses ke global dalam mutex di n + 2 dan menghentikan GetGlobal() demi Global() lebih idiomatis.)

Itu bukan perbaikan cepat, tetapi itu mengurangi masalah sehingga hanya alias fungsi (atau solusi mereka saat ini) yang benar-benar diperlukan untuk perbaikan kode bertahap.

@rsc Satu penggunaan sepele untuk alias yang Anda tinggalkan dari ringkasan Anda: menyingkat nama panjang. (Mungkin satu-satunya motivasi untuk Pascal, yang awalnya tidak memiliki fitur pemrograman dalam skala besar seperti paket.) Meskipun sepele, ini adalah satu-satunya kasus penggunaan di mana alias yang tidak diekspor masuk akal, jadi mungkin layak disebutkan karena alasan itu.

@jimmyfrasche Anda benar. Saya tidak suka ide menggunakan getter dan setter (sama seperti saya tidak suka memilikinya untuk bidang struct) tetapi analisis Anda, tentu saja, benar.

Ada poin yang harus dibuat tentang penggunaan alias yang tidak dapat diperbaiki (misalnya membuat paket pengganti drop-in), tetapi saya akui bahwa itu melemahkan kasus untuk var-alias.

@Merovius menyetujui semua poin. Saya juga tidak senang tentang itu tetapi harus mengikuti logika v☹v

@niemeyer dapatkah Anda mengklarifikasi bagaimana adaptor akan membantu jenis migrasi di mana baik yang lama maupun yang baru memiliki metode dengan nama yang sama tetapi tanda tangan yang berbeda. Menambahkan argumen ke metode atau mengubah tipe argumen sepertinya merupakan evolusi umum dari basis kode.

@rogpeppe Perhatikan bahwa inilah yang terjadi hari ini:

type two one

Ini membuat one dan two tipe independen, dan apakah mencerminkan atau di bawah interface{} , itulah yang Anda lihat. Anda juga dapat mengonversi antara one dan two . Proposal adaptor di atas hanya membuat langkah terakhir itu otomatis untuk adaptor. Anda mungkin tidak menyukai proposal karena berbagai alasan, tetapi tidak ada yang kontradiktif tentang itu.

@iand Seperti dalam kasus type two one , kedua jenis memiliki set metode yang sepenuhnya independen, jadi tidak ada yang istimewa tentang pencocokan nama. Sebelum basis kode lama dimigrasikan, mereka akan tetap menggunakan tanda tangan lama di bawah jenis sebelumnya (sekarang adaptor). Kode baru menggunakan tipe baru akan menggunakan tanda tangan baru. Melewati nilai tipe baru ke kode lama secara otomatis menyesuaikannya karena kompiler mengetahui yang terakhir adalah adaptor dari yang pertama, dan dengan demikian menggunakan set metode masing-masing.

@niemeyer Sepertinya ada banyak kerumitan yang bersembunyi di balik adaptor ini yang tidak sepenuhnya ditentukan. Pada titik ini saya pikir kesederhanaan alias tipe sangat menguntungkan mereka. Saya duduk untuk membuat daftar semua hal yang perlu diperbarui hanya untuk alias tipe, dan itu daftar yang sangat panjang. Daftarnya tentu akan lebih panjang untuk adaptor, dan saya masih belum sepenuhnya memahami semua detailnya. Saya ingin menyarankan agar kita melakukan jenis alias untuk saat ini dan meninggalkan keputusan tentang adaptor yang relatif lebih berat di lain waktu, jika Anda ingin menyusun proposal lengkap (tetapi sekali lagi saya skeptis bahwa tidak ada naga yang bersembunyi di sana) .

@jimmyfrasche Mengenai metode pada alias, tentu saja alias tidak mengizinkan melewati batasan definisi metode yang biasa: jika sebuah paket mendefinisikan tipe T1 = otherpkg.T2, paket tersebut tidak dapat mendefinisikan metode pada T1, sama seperti paket tersebut tidak dapat mendefinisikan metode secara langsung pada pkg.T2 lainnya. Namun, jika sebuah paket mendefinisikan tipe T1 = T2 (keduanya dalam paket yang sama), maka jawabannya kurang jelas. Kami dapat memperkenalkan pembatasan tetapi tidak (belum) kebutuhan yang jelas untuk itu.

Memperbarui ringkasan diskusi tingkat atas . Perubahan:

  • Menambahkan tautan ke video GothamGo
  • Menambahkan "menyingkat nama panjang" sebagai kemungkinan penggunaan, per @jba.
  • Menambahkan x/image/draw sebagai argumen terhadap pembatasan perpustakaan standar, per @Merovius.
  • Menambahkan lebih banyak teks tentang metode pada alias, per @jimmyfrasche.

Dokumen desain ditambahkan:

Seperti yang terjadi seminggu yang lalu, tampaknya masih ada konsensus umum untuk jenis alias. Robert dan saya menyusun dokumen desain formal, yang baru saja saya periksa (tautan di atas).

Setelah proses proposal , silakan kirim komentar substantif pada proposal _di sini_ tentang masalah ini. Ejaan/tata bahasa/dll bisa ke halaman codereview Gerrit https://go-review.googlesource.com/#/c/34592/. Terima kasih.

Saya ingin "Efek pada penyematan" dipertimbangkan kembali. Ini membatasi kegunaan alias tipe untuk perbaikan kode bertahap. Yaitu, jika p1 ingin mengganti nama jenis type T1 = T2 dan paket p2 menyematkan p1.T2 dalam sebuah struct, mereka tidak akan pernah dapat memperbarui definisi itu ke p1.T1 , karena importir p3 mungkin merujuk ke struct yang disematkan dengan nama. p2 maka tidak dapat beralih ke p1.T1 tanpa merusak p3 ; p3 tidak dapat memperbarui nama menjadi p1.T1 , tanpa memutuskan p2 .

Jalan keluar dari ini adalah, untuk a) secara umum membatasi janji kompatibilitas/periode penghentian untuk kode yang tidak merujuk ke bidang yang disematkan dengan nama, atau b) menambahkan tahap penghentian terpisah, jadi p1 menambahkan type T1 = T2 dan menghentikan T2 , kemudian p2 tidak lagi mengacu pada (katakanlah) s2.T2 berdasarkan nama, semua importir p2 akan diperbaiki untuk tidak melakukan itu, maka p2 melakukan peralihan.

Sekarang, secara teori, masalahnya bisa berulang tanpa batas; p4 mungkin mengimpor p3 , yang dengan sendirinya menyematkan tipe dari p2 ; menurut saya, p3 juga perlu memiliki periode penghentian, untuk merujuk ke bidang yang disematkan dua kali dengan nama? Dalam hal ini, periode penghentian terdalam menjadi sangat kecil atau terluar menjadi tak terbatas. Tetapi bahkan tanpa mempertimbangkan masalahnya sebagai rekursif, bagi saya tampaknya b) akan sangat sulit untuk menentukan waktu (periode penghentian p2 perlu sepenuhnya terkandung dalam periode penghentian p1 Jadi jika T adalah "periode penghentian standar", Anda harus memilih setidaknya 2T saat mengganti nama jenis, sehingga rilis akan berbaris).

a) juga tampaknya tidak praktis bagi saya; misalnya jika suatu tipe menyematkan *byte.Buffer dan saya ingin mengatur bidang itu (atau meneruskan buffer itu ke beberapa fungsi lain), tidak ada cara untuk melakukannya, tanpa merujuknya dengan nama (kecuali menggunakan inisialisasi struct tanpa nama, yang juga kehilangan jaminan kompatibilitas :) ).

Saya memahami daya tarik menjadi kompatibel dengan byte dan rune sebagai alias. Tapi, secara pribadi, saya akan menempatkan yang kedua untuk melestarikan kegunaan alias tipe untuk perbaikan bertahap. Contoh (mungkin buruk) dari ide untuk mendapatkan keduanya adalah, untuk, untuk nama yang diekspor memungkinkan untuk menggunakan alias apa pun untuk merujuk ke bidang yang disematkan dan untuk nama yang tidak diekspor (secara inheren terbatas pada paket yang sama, sehingga di bawah kendali lebih besar dari penulis ) simpan semantik yang saat ini diusulkan? Ya, saya juga tidak menyukai perbedaan ini. Mungkin seseorang memiliki ide yang lebih baik.

@rsc ulang metode pada alias

Jika Anda memiliki tipe S yang merupakan alias untuk tipe T, keduanya didefinisikan dalam paket yang sama, dan Anda mengizinkan metode pendefinisian pada S, bagaimana jika T adalah alias untuk pF yang didefinisikan dalam paket yang berbeda? Meskipun itu jelas akan gagal juga, ada seluk-beluk dalam penegakan, implementasi, dan keterbacaan sumber untuk dipertimbangkan (Jika T berada dalam file yang berbeda dari S, tidak segera jelas apakah Anda dapat mendefinisikan metode pada T dengan melihat definisi T).

Aturan—jika Anda memiliki type T = S , maka Anda tidak dapat mendeklarasikan metode pada T —mutlak dan jelas dari satu baris di sumber itu berlaku, tanpa harus menyelidiki sumber S, seperti yang Anda lakukan dalam situasi alias.

Lebih lanjut, mengizinkan metode pada alias tipe lokal memperumit perbedaan antara alias tipe dan definisi tipe. Karena metode akan didefinisikan pada S dan T, batasan bahwa metode tersebut hanya dapat ditulis pada satu metode tidak membatasi apa yang dapat diekspresikan. Itu hanya membuat segalanya lebih sederhana dan lebih seragam..

@jimmyfrasche Jika kita menulis type T1 = T2 dan T2 berada dalam paket yang sama, maka kita mungkin tidak menggunakan nama T2. Dalam hal ini, kami ingin sesedikit mungkin kemunculan T2 di godoc. Jadi kami ingin mendeklarasikan semua metode sebagai func (T1) M() .

@jba perubahan godoc untuk melaporkan metode alias yang dideklarasikan pada alias itu akan memenuhi persyaratan itu tanpa mengubah keterbacaan sumbernya. Secara umum akan lebih baik jika godoc menampilkan set metode lengkap dari suatu tipe ketika aliasing dan/atau embedding terlibat, terutama ketika tipe tersebut berasal dari paket lain. Masalahnya harus diselesaikan dengan perkakas yang lebih cerdas, bukan lebih banyak semantik bahasa.

@jba Dalam hal ini, mengapa Anda tidak membalikkan arah alias saja? type T2 = T1 sudah memungkinkan Anda untuk mendefinisikan metode pada T1 dengan struktur paket yang sama; satu-satunya perbedaan adalah nama jenis yang dilaporkan oleh paket reflect , dan Anda dapat memulai migrasi dengan memperbaiki situs panggilan yang peka terhadap nama menjadi tidak peka terhadap nama sebelum menambahkan alias.

@jimmyfrasche Dari dokumen proposal :

"Karena T1 hanyalah cara lain untuk menulis T2, ia tidak memiliki kumpulan deklarasi metodenya sendiri. Sebaliknya, kumpulan metode T1 sama dengan T2. ​​Setidaknya untuk percobaan awal, tidak ada batasan terhadap deklarasi metode yang menggunakan T1 sebagai jenis penerima, asalkan menggunakan T2 dalam deklarasi yang sama akan valid. "

Menggunakan pF sebagai jenis penerima metode tidak pernah valid.

@mdempsky Saya tidak begitu jelas, tetapi saya mengatakan itu tidak valid.

Maksud saya adalah kurang jelas apakah itu valid atau tidak hanya dengan melihat baris kode tertentu.

Mengingat type S = T , Anda juga harus melihat T untuk memastikan itu juga bukan alias yang alias tipe dalam paket lain. Satu-satunya keuntungan adalah kompleksitas.

Selalu melarang metode pada alias lebih sederhana dan lebih mudah dibaca dan Anda tidak kehilangan apa pun. Saya tidak membayangkan bahwa kasus yang membingungkan akan sering muncul, tetapi tidak perlu memperkenalkan kemungkinan ketika Anda tidak mendapatkan apa pun yang tidak dapat ditangani dengan lebih baik di tempat lain atau dengan pendekatan yang berbeda tetapi setara.

@Merovius

jika p1 ingin mengganti nama tipe tipe T1 = T2 dan paket p2 menyematkan p1.T2 dalam sebuah struct, mereka tidak akan pernah dapat memperbarui definisi itu ke p1.T1, karena importir p3 mungkin merujuk ke struct yang disematkan berdasarkan nama.

Dimungkinkan untuk mengatasi masalah ini hari ini dalam banyak kasus dengan mengubah bidang anonim ke bidang bernama dan secara eksplisit meneruskan metode. Namun, itu tidak akan berhasil untuk metode yang tidak diekspor.

Pilihan lain mungkin menambahkan fitur kedua untuk mengimbanginya. Jika Anda dapat mengadopsi kumpulan metode bidang tanpa menjadikannya anonim (atau dengan penggantian nama eksplisit), itu akan memungkinkan nama bidang tetap tidak berubah meskipun jenis dasarnya diubah.

Mempertimbangkan deklarasi dari contoh Anda:

package p2

type S struct {
  p1.T2
}

Satu fitur kompensasi mungkin adalah "alias bidang", yang akan mengikuti sintaks serupa untuk mengetikkan alias:

package p2

type S struct {
  p1.T1
  T2 = T1  // field T2 is an alias for field T1.
}

var s S  // &s.T2 == &s.T1

Fitur kompensasi lainnya mungkin "delegasi", yang secara eksplisit akan mengadopsi kumpulan metode bidang anonim:

package p2

type S struct {
  T2 p1.T1 delegated  // T2 is a field of type T1.
  // The method set of S includes the method set of T1 and forwards those calls to field T2.
}

Saya pikir saya lebih suka alias bidang sendiri, karena mereka juga akan mengaktifkan jenis lain dari perbaikan bertahap: mengganti nama bidang struct tanpa memperkenalkan pointer-aliasing atau bug konsistensi.

@Merovius Masalah utamanya adalah ketika jenisnya diganti namanya dengan alias.

Saya belum mempertimbangkan ini sepenuhnya—hampir secara sepintas, hanya pemikiran acak:

Bagaimana jika Anda memperkenalkan alias dalam paket Anda yang menamainya kembali dan menyematkannya?

Saya tidak tahu apakah itu memperbaiki apa pun, tetapi mungkin perlu waktu untuk memutus lingkaran?

@bcmils Saya tidak memikirkan solusi itu, terima kasih. Saya pikir, peringatan tentang metode yang tidak diekspor tampaknya (bagi saya) muncul cukup jarang dalam praktiknya sehingga tidak akan mempengaruhi pendapat saya secara umum (kecuali saya tidak sepenuhnya memahaminya. Jangan ragu untuk mengklarifikasi, jika menurut Anda itu berguna ). Saya tidak berpikir menumpuk lebih banyak perubahan dibenarkan (atau ide yang bagus).

@Merovius Semakin saya memikirkannya, semakin saya menyukai ide alias bidang.

Meneruskan metode eksplisit membosankan bahkan jika mereka diekspor, dan istirahat jenis lain dari refactoring (misalnya menambahkan metode untuk jenis tertanam dan mengharapkan jenis yang embeds untuk terus memenuhi antarmuka yang sama). Dan mengganti nama bidang struct juga termasuk dalam payung umum yang memungkinkan perbaikan kode bertahap.

@Merovius

jika p1 ingin mengganti nama tipe tipe T1 = T2 dan paket p2 menyematkan p1.T2 dalam sebuah struct, mereka tidak akan pernah dapat memperbarui definisi itu ke p1.T1, karena importir p3 mungkin merujuk ke struct yang disematkan berdasarkan nama. p2 maka tidak dapat beralih ke p1.T1 tanpa merusak p3; p3 tidak dapat memperbarui nama ke p1.T1, tanpa memutuskan hubungan dengan p2 saat ini.

Jika saya memahami contoh Anda, kami memiliki:

package p1

type T2 struct {}
type T1 = T2
package p2

import "p1"

type S struct {
  p1.T2
  F2 string // see below
}

Saya percaya bahwa ini hanyalah contoh spesifik dari kasus umum di mana kita ingin mengganti nama bidang struct; masalah yang sama berlaku jika kita ingin mengganti nama S.F2 menjadi S.F1.

Dalam kasus khusus ini, kami dapat memperbarui paket p2 untuk menggunakan API baru p1 dengan alias tipe lokal:

package p2

import "p1"

type T2 = p1.T1

type S struct {
  T2
}

Ini tentu saja bukan perbaikan jangka panjang yang baik. Saya tidak berpikir bahwa ada jalan keluar dari fakta bahwa p2 perlu mengubah API yang diekspor untuk menghilangkan nama T2, namun, yang akan dilanjutkan dengan cara yang sama seperti penggantian nama bidang apa pun.

Sekedar catatan tentang "memindahkan jenis antar paket". Bukankah formulasi itu sedikit bermasalah?

Sejauh yang saya mengerti, proposal memungkinkan untuk "merujuk" ke definisi objek yang terletak di paket lain melalui nama baru.

Itu tidak memindahkan definisi objek, bukan? (kecuali seseorang menulis kode menggunakan alias di tempat pertama dalam hal ini, pengguna bebas untuk mengubah di mana alias merujuk, seperti di pkg undian).

@atdiar Merujuk pada suatu tipe dalam paket yang berbeda dapat digunakan sebagai langkah untuk memindahkan tipe tersebut. Ya, alias tidak memindahkan jenisnya, tetapi bisa digunakan sebagai alat untuk melakukannya.

@Merovius Melakukan itu kemungkinan akan merusak refleksi dan plugin.

@atdiar Maaf, tapi saya tidak mengerti apa yang Anda coba katakan. Sudahkah Anda membaca komentar asli utas ini, artikel tentang perbaikan bertahap terkait di dalamnya dan diskusi sejauh ini? Jika Anda mencoba menambahkan argumen yang sejauh ini tidak dipertimbangkan ke dalam diskusi, saya yakin Anda harus lebih jelas.

Akhirnya, proposal yang bermanfaat dan ditulis dengan baik. Kami membutuhkan alias tipe, saya memiliki masalah besar dalam membuat satu API tanpa alias tipe, sejauh ini, saya harus menulis kode saya dengan cara yang tidak begitu saya sukai untuk mencapainya. Ini harus disertakan pada go v1.8 tetapi tidak pernah terlambat, jadi lanjutkan untuk 1.9.

@Merovius
Saya secara eksplisit berbicara tentang "memindahkan tipe" antar paket. Ini mengubah definisi objek. Misalnya, dalam refleksi pkg, beberapa informasi terkait dengan paket tempat objek didefinisikan.
Jika Anda memindahkan definisi, itu mungkin rusak.

@kataras ini bukan tentang dokumen dan komentar yang bagus, hanya saja definisi tipe tidak boleh dipindahkan. Betapapun saya menghargai proposal alias, saya khawatir orang berpikir bahwa mereka bisa melakukan itu.

@atdiar lagi, silakan baca artikel dari komentar asli dan diskusi selama ini. Jenis pemindahan dan cara mengatasi masalah Anda adalah perhatian utama utas ini. Jika Anda merasa artikel Russ tidak cukup menjawab kekhawatiran Anda, mohon jelaskan secara spesifik mengapa penjelasannya tidak memuaskan. :)

@kataras Sementara saya, secara pribadi, setuju, saya tidak berpikir itu sangat membantu, untuk hanya menegaskan betapa pentingnya kami menemukan fitur ini. Perlu ada argumen konstruktif yang dibuat untuk mengatasi kekhawatiran masyarakat. :)

@Merovius Saya sudah membaca dokumennya. Itu tidak menjawab pertanyaan saya. Saya pikir saya sudah cukup eksplisit. Ini terkait dengan masalah yang sama yang menghalangi kami untuk menerapkan proposal alias sebelumnya.

@atdiar Saya, setidaknya, tidak mengerti. Anda mengatakan bahwa memindahkan tipe akan merusak sesuatu; proposalnya adalah tentang bagaimana menghindari kerusakan tersebut dengan perbaikan bertahap, dengan menggunakan alias, kemudian perbarui setiap ketergantungan terbalik hingga tidak ada kode yang menggunakan tipe lama, lalu hapus tipe lama. Saya tidak mengerti, bagaimana pernyataan Anda, bahwa "refleksi dan plugin" dilanggar di bawah asumsi ini. Jika Anda ingin mempertanyakan asumsi, itu sudah dibahas.

Saya juga tidak melihat bagaimana masalah apa pun yang mencegah alias memasuki 1,8 terhubung dengan apa yang Anda katakan. Masalah masing-masing, sejauh pengetahuan saya, adalah #17746 dan #17784. Jika Anda mengacu pada masalah penyematan (yang dapat ditafsirkan sebagai berkaitan dengan kerusakan atau refleksi, meskipun saya tidak setuju), maka itu dibahas dalam proposal formal (meskipun, lihat di atas, saya yakin solusi yang diusulkan layak untuk didiskusikan lebih lanjut) dan Anda harus spesifik tentang mengapa Anda tidak mempercayainya.

Jadi, maaf, tapi tidak, Anda tidak cukup spesifik. Apakah Anda memiliki nomor masalah untuk "masalah yang sama yang menghalangi kami untuk menerapkan proposal alias sebelumnya" yang Anda maksud, yang terkait dengan apa yang Anda sebutkan sejauh ini, untuk membantu memahami? Bisakah Anda memberikan contoh spesifik dari kerusakan yang Anda bicarakan (lihat contoh untuk upthread ini; berikan urutan paket, ketik definisi dan beberapa kode dan jelaskan bagaimana kerusakannya ketika diubah seperti yang diusulkan)? Jika Anda ingin masalah Anda ditangani, Anda benar-benar perlu membantu orang lain memahaminya terlebih dahulu.

@Merovius Jadi dalam kasus dependensi transitif di mana salah satu dependensi ini melihat reflect.Type.PkgPath(), apa yang terjadi?
Itu masalah yang sama yang terjadi dalam masalah penyematan.

@atdiar Maaf, saya tidak melihat bagaimana hal ini menjadi perhatian yang dapat dimengerti, mengingat diskusi di utas ini sejauh ini dan tentang apa proposal ini. Saya akan keluar dari sub-utas ini sekarang dan memberi kesempatan kepada orang lain, yang mungkin lebih memahami keberatan Anda, untuk mengatasinya.

Biarkan saya ulangi secara singkat:

Masalahnya adalah tentang kesetaraan tipe mengingat fakta bahwa definisi tipe meng-hardcode lokasinya sendiri.
Karena kesetaraan tipe dapat dan diuji saat runtime, saya tidak melihat betapa mudahnya memindahkan tipe.

Saya hanya menyampaikan peringatan bahwa kasus penggunaan "tipe bergerak" ini berpotensi merusak banyak paket di alam liar, di kejauhan. Kekhawatiran serupa dengan plugin.

(cara yang sama mengubah jenis pointer dalam sebuah paket akan merusak banyak paket lain, jika paralel itu dapat membuat segalanya lebih jelas.)

@atdiar Sekali lagi, masalah ini adalah tentang memindahkan tipe dalam dua langkah, dengan terlebih dahulu menghentikan lokasi lama dan memperbarui dependensi terbalik, _kemudian_ pindahkan tipe. _Tentu saja_ hal-hal akan rusak jika Anda hanya memindahkan jenis, tetapi ini sama sekali bukan tentang masalah ini. Ini tentang memungkinkan solusi bertahap dan multi-langkah untuk melakukan itu. Jika Anda khawatir bahwa salah satu solusi yang diusulkan di sini tidak memungkinkan proses multi-langkah ini, harap jelaskan dengan tepat dan jelaskan situasinya, di mana tidak ada urutan yang wajar dari komitmen perbaikan bertahap yang dapat mencegah kerusakan.

@niemeyer

Ini membuat satu dan dua tipe independen, dan apakah mencerminkan atau di bawah antarmuka{}, itulah yang
kamu melihat. Anda juga dapat mengonversi antara satu dan dua. Proposal adaptor di atas hanya membuatnya bertahan
langkah otomatis untuk adaptor. Anda mungkin tidak menyukai proposal karena berbagai alasan, tetapi tidak ada apa-apa
kontradiktif tentang itu.

Anda tidak dapat mengonversi antara

 func() one

dan

func() two

@Merovius Anda tidak mungkin mempertimbangkan untuk mengubah semua importir paket yang diperbaiki kode yang ada di alam liar. Dan saya tidak terlalu tertarik untuk mempelajari versi paket di sini.

Untuk lebih jelasnya, saya tidak menentang proposal alias tetapi formulasi "berpindah jenis antar paket" yang menyiratkan kasus penggunaan yang belum terbukti aman.

@jimmyfrasche kembali prediktabilitas validitas metode-on-alias:

Sudah menjadi kasus bahwa func (t T) M() terkadang valid, terkadang tidak valid. Itu tidak muncul banyak karena orang tidak terlalu sering mendorong batas-batas ini. Artinya, itu bekerja dengan baik dalam praktiknya. https://play.golang.org/p/bci2qnldej. Bagaimanapun, ini ada dalam daftar batasan _possible_. Seperti semua batasan yang mungkin, itu menambah kompleksitas dan kami ingin melihat bukti nyata yang nyata sebelum menambahkan kompleksitas itu.

@Merovius , menyematkan kembali nama:

Saya setuju bahwa situasinya tidak sempurna. Namun, jika saya memiliki basis kode yang penuh dengan referensi ke io.ByteBuffer dan saya ingin memindahkannya ke byte.Buffer, maka saya ingin dapat memperkenalkan

package io
type ByteBuffer = bytes.Buffer

_tanpa_ memperbarui referensi yang ada ke io.ByteBuffer. Jika semua tempat di mana io.ByteBuffer disematkan secara otomatis mengubah nama bidang menjadi Buffer sebagai akibat dari penggantian definisi tipe dengan alias, maka saya telah merusak dunia dan tidak ada perbaikan bertahap. Sebaliknya, jika nama io.ByteBuffer yang disematkan masih ByteBuffer, maka penggunaannya dapat diperbarui satu per satu dalam perbaikan bertahap mereka sendiri (mungkin harus melakukan beberapa langkah; sekali lagi tidak ideal).

Kami membahas ini cukup panjang di #17746. Saya awalnya berada di sisi nama io.ByteBuffer alias yang disematkan sebagai Buffer, tetapi argumen di atas meyakinkan saya bahwa saya salah. @jimmyfrasche khususnya membuat beberapa argumen bagus tentang kode yang tidak berubah tergantung pada definisi hal yang disematkan. Saya tidak berpikir itu dapat dipertahankan untuk melarang alias tertanam sepenuhnya.

Perhatikan bahwa ada solusi di p2 dalam contoh Anda. Jika p2 benar-benar menginginkan bidang tersemat bernama ByteBuffer tanpa merujuk ke io.ByteBuffer, ia dapat mendefinisikan:

type ByteBuffer = bytes.Buffer

lalu sematkan ByteBuffer (yaitu, p2.ByteBuffer) alih-alih io.ByteBuffer. Itu juga tidak sempurna, tetapi itu berarti perbaikan dapat dilanjutkan.

Ini pasti kasus ini tidak sempurna dan penggantian nama bidang secara umum tidak ditangani oleh proposal ini. Bisa jadi penyematan tidak boleh sensitif terhadap nama yang mendasarinya, bahwa harus ada semacam sintaks untuk 'menyematkan X sebagai nama N'. Bisa juga nanti kita harus menambahkan alias field. Keduanya tampak seperti ide yang masuk akal secara apriori dan keduanya mungkin harus terpisah, proposal selanjutnya dievaluasi berdasarkan bukti nyata dari suatu kebutuhan. Jika alias tipe membantu kami mencapai titik di mana kurangnya alias bidang adalah penghalang besar berikutnya untuk refactoring skala besar, itu akan menjadi kemajuan!

(/cc @neild dan @bcmils)

@atdiar , ya, memang benar bahwa reflect akan melihat perubahan semacam ini, dan jika kode bergantung pada hasil dari reflect, itu akan rusak. Seperti situasi dengan embedding, itu tidak sempurna. Berbeda dengan situasi dengan embedding, saya tidak punya jawaban kecuali mungkin kode tidak boleh ditulis menggunakan reflect agar cukup sensitif terhadap detail itu.

@rsc Yang ada dalam pikiran saya adalah a) melarang menyematkan alias dan mendefinisikan tipe dalam struct yang sama (untuk mencegah ambiguitas dari b), b) mengizinkan untuk merujuk ke bidang dengan salah satu nama dalam kode sumber, c) pilih salah satu atau yang lain dalam jenis informasi/refleksi yang dihasilkan dan sejenisnya (tidak peduli yang mana).

Saya akan mengklaim, bahwa ini membantu menghindari jenis kerusakan yang saya coba gambarkan, sementara juga membuat pilihan yang jelas untuk kasus di mana pilihan diperlukan; dan, secara pribadi, saya tidak terlalu peduli untuk tidak melanggar kode yang bergantung pada refleksi, daripada kode yang tidak.

Saya tidak yakin sekarang apakah saya memahami argumen ByteBuffer Anda, tetapi saya juga berada di akhir hari kerja yang panjang, jadi tidak perlu menjelaskan lebih lanjut, jika saya merasa tidak meyakinkan, saya akan merespons pada akhirnya :)

@Merovius Saya pikir masuk akal untuk mencoba aturan sederhana dan melihat seberapa jauh kita mendapatkan sebelum memperkenalkan yang lebih kompleks. Kita dapat menambahkan (a) dan (b) nanti jika diperlukan; (c) diberikan apa pun yang terjadi.

Saya setuju bahwa mungkin (b) adalah ide yang baik dalam keadaan tertentu, tetapi mungkin tidak dalam keadaan lain. Jika Anda menggunakan alias tipe untuk kasus penggunaan "struktur satu paket API menjadi beberapa paket implementasi" yang disebutkan sebelumnya, maka mungkin Anda tidak ingin menyematkan alias untuk mengekspos nama lain (yang mungkin ada dalam paket internal dan jika tidak dapat diakses oleh sebagian besar pengguna). Semoga kita bisa mengumpulkan lebih banyak pengalaman.

@rsc

Mungkin menambahkan informasi tingkat paket tentang aliasabilitas ke file objek dapat membantu.
(Sambil mempertimbangkan apakah plugin go harus tetap berfungsi dengan baik atau tidak.)

@Merovius @rsc

a) melarang penyematan alias dan itu mendefinisikan tipe dalam struct yang sama

Perhatikan bahwa dalam banyak kasus ini sudah dilarang sebagai konsekuensi dari cara embedding berinteraksi dengan set metode. (Jika jenis yang disematkan memiliki kumpulan metode yang tidak kosong dan salah satu metode tersebut dipanggil, program akan gagal dikompilasi: https://play.golang.org/p/XkaB2a0_RK.)

Jadi menambahkan aturan eksplisit yang melarang penyematan ganda sepertinya hanya akan membuat perbedaan dalam sebagian kecil kasus; tampaknya tidak sebanding dengan kerumitannya bagi saya.

Mengapa tidak mendekati alias tipe sebagai tipe aljabar saja dan mendukung alias ke sekumpulan tipe sehingga kita juga mendapatkan antarmuka kosong yang setara dengan pemeriksaan tipe waktu kompilasi sebagai bonus, a la

type Stringeroonie = {string,fmt.Stringer}

@j7b

Mengapa tidak mendekati alias tipe sebagai tipe aljabar saja dan mendukung alias ke sekumpulan tipe

Alias ​​secara semantik dan struktural setara dengan tipe aslinya. Tipe data aljabar tidak: dalam kasus umum, mereka membutuhkan penyimpanan tambahan untuk tag tipe. (Jenis antarmuka Go sudah membawa informasi jenis itu, tetapi struct dan tipe non-antarmuka lainnya tidak.)

@bcmils

Ini mungkin alasan yang salah, tapi saya pikir masalahnya bisa didekati karena alias A dari tipe T sama dengan mendeklarasikan A sebagai antarmuka{} dan membiarkan compiler mengonversi variabel tipe A ke T secara transparan dalam cakupan di mana variabel tipe A dideklarasikan , yang saya pikir sebagian besar akan menjadi biaya waktu kompilasi linier, tidak ambigu, dan membuat dasar untuk pseudotipe yang dikelola kompiler termasuk aljabar menggunakan sintaks type T = , dan mungkin juga mengizinkan tipe implementasi seperti referensi yang tidak dapat diubah pada waktu kompilasi itu sebagai sejauh menyangkut kode pengguna hanya akan menjadi{}antarmuka "di bawah tenda."

Kekurangan dalam rangkaian pemikiran itu mungkin akan menjadi produk dari ketidaktahuan, dan karena saya tidak dalam posisi untuk menawarkan bukti konsep yang praktis, saya dengan senang hati menerima kekurangannya dan menundanya.

@j7b Bahkan jika ADT di mana solusi untuk satu masalah perbaikan bertahap, mereka membuat sendiri; tidak mungkin menambah atau menghapus anggota ADT tanpa memutus ketergantungan. Jadi, pada dasarnya Anda akan menciptakan lebih banyak masalah, daripada yang akan Anda pecahkan.

Ide Anda untuk menerjemahkan secara transparan ke dan dari antarmuka{} juga tidak berfungsi untuk tipe tingkat tinggi seperti []interface{} . Dan pada akhirnya Anda akan kehilangan salah satu kekuatan go, yaitu memberi pengguna kendali atas tata letak data dan alih-alih melakukan hal java untuk membungkus semuanya.

ADT bukanlah solusi di sini.

@Merovius Saya cukup yakin jika konstruksi tipe aljabar menyertakan penggantian nama (yang akan konsisten dengan definisi wajar yang sama) itu adalah solusi, antarmuka itu{} bisa berfungsi sebagai proxy untuk bentuk proyeksi dan pemilihan yang dikelola compiler dijelaskan, dan saya tidak yakin bagaimana layout data relevan atau bagaimana Anda mendefinisikan tipe "tingkat tinggi", tipe hanyalah tipe jika bisa dideklarasikan dan []antarmuka{} hanyalah tipe.

Selain itu, saya yakin type T = memiliki potensi untuk kelebihan beban dalam cara yang intuitif dan berguna di luar penggantian nama, tipe aljabar, dan referensi yang tidak dapat diubah secara publik tampaknya merupakan aplikasi yang paling jelas, jadi saya harap spesifikasi akhirnya menyatakan sintaks itu menunjukkan meta yang dikelola kompiler atau tipe semu dan pertimbangan diberikan untuk semua cara tipe yang dikelola kompiler dapat berguna dan sintaks yang paling baik mengekspresikan penggunaan tersebut. Karena sintaks baru tidak perlu memperhatikan kumpulan kata yang dicadangkan secara global saat digunakan sebagai kualifikasi, sesuatu seperti type A = alias Type akan menjadi jelas dan dapat diperluas.

@j7b

Selain itu, saya tipe positif T = memiliki potensi untuk kelebihan beban dengan cara yang intuitif dan berguna selain mengganti nama,

Saya tentu berharap tidak. Go (kebanyakan) ortogonal dengan baik hari ini, dan mempertahankan ortogonalitas itu adalah hal yang baik.

Cara, hari ini, yang mendeklarasikan tipe baru T di Go adalah type T def , di mana def adalah definisi tipe baru. Jika seseorang menerapkan tipe data aljabar (alias serikat yang ditandai), saya berharap mereka mengikuti sintaks itu daripada sintaks untuk alias tipe.

Saya suka memberikan sudut pandang yang berbeda (untuk mendukung) dari alias tipe, yang dapat memberikan beberapa wawasan tentang kasus penggunaan alternatif selain refactoring:

Mari kita mundur sejenak dan menganggap kita tidak memiliki deklarasi tipe Go lama yang teratur dalam bentuk type T <a type> , tetapi hanya tipe deklarasi alias type A = <a type> .

(Untuk melengkapi gambarannya, mari kita asumsikan juga bahwa metode dideklarasikan secara berbeda - tidak melalui asosiasi ke tipe bernama yang digunakan sebagai penerima, karena kita tidak bisa. Misalnya, orang dapat membayangkan gagasan tentang tipe kelas dengan metode secara harfiah di dalam dan jadi kita tidak perlu bergantung pada tipe bernama untuk mendeklarasikan metode. Dua tipe yang identik secara struktural tetapi memiliki metode berbeda akan menjadi tipe yang berbeda. Detailnya tidak penting di sini untuk eksperimen pemikiran ini.)

Saya mengklaim bahwa di dunia seperti itu kami dapat menulis kode yang hampir sama dengan yang kami tulis sekarang: Kami menggunakan nama tipe (alias) sehingga kami tidak perlu mengulanginya sendiri, dan tipe itu sendiri memastikan kami menggunakan data dalam suatu tipe -jalan aman.

Dengan kata lain, jika Go dirancang seperti itu, kita mungkin juga akan baik-baik saja pada umumnya.

Terlebih lagi, di dunia seperti itu, karena tipe identik jika mereka identik secara struktural (tidak peduli namanya), masalah yang kita miliki dengan refactoring sekarang tidak akan muncul di tempat pertama, dan tidak akan ada kebutuhan untuk perubahan pada bahasa.

Tetapi kami tidak akan memiliki mekanisme keamanan yang kami miliki di Go saat ini: Kami tidak akan dapat memperkenalkan nama untuk tipe dan menyatakan bahwa ini sekarang harus menjadi tipe baru yang berbeda. (Namun, penting untuk diingat bahwa pada dasarnya ini adalah mekanisme keamanan.)

Dalam bahasa pemrograman lain, gagasan membuat tipe baru yang berbeda dari tipe yang ada disebut "branding": Tipe get adalah merek yang melekat padanya yang membuatnya berbeda dari semua tipe lainnya. Misalnya, di Modula-3, ada kata kunci khusus BRANDED untuk mewujudkannya (misalnya TYPE T = BRANDED REF T0 akan membuat referensi baru yang berbeda ke T0). Di Haskell, kata new sebelum tipe memiliki efek yang sama.

Kembali ke dunia Go alternatif kami, kami mungkin menemukan kami dalam posisi di mana kami tidak memiliki masalah dengan refactoring, tetapi di mana kami ingin meningkatkan keamanan kode kami sehingga type MyBuffer = []byte dan type YourBuffer = []byte menunjukkan jenis yang berbeda sehingga kami tidak sengaja menggunakan yang salah. Kami mungkin mengusulkan untuk memperkenalkan suatu bentuk branding tipe untuk tujuan itu. Misalnya, kita mungkin ingin menulis type MyBuffer = new []byte , atau bahkan type MyBuffer = new YourBuffer dengan efek bahwa MyBuffer sekarang merupakan tipe yang berbeda dari YourBuffer.

Ini pada dasarnya adalah masalah ganda dari apa yang kita miliki sekarang. Kebetulan di Go, sejak hari pertama, kami selalu bekerja dengan tipe "bermerek" segera setelah mereka mendapat nama. Dengan kata lain, type T <a type> secara efektif type T = new <a type> .

Untuk meringkas: Di Go yang ada, tipe bernama selalu tipe "bermerek", dan kami tidak memiliki gagasan tentang hanya nama untuk suatu tipe (yang sekarang kami sebut alias tipe). Dalam beberapa bahasa lain, alias tipe adalah norma, dan seseorang harus menggunakan mekanisme "branding" untuk membuat tipe yang baru dan berbeda secara eksplisit.

Intinya adalah bahwa kedua mekanisme secara inheren berguna, dan dengan alias tipe akhirnya kita dapat mendukung keduanya.

@griesemer Ekstensi fitur itu adalah proposal alias awal yang idealnya harus membersihkan refactoring. Saya khawatir hanya tipe alias yang akan membuat kasus Edge refactoring yang sulit karena cakupannya yang terbatas.

Di kedua proposal, saya bertanya-tanya apakah kolaborasi dari tautan tidak diperlukan karena nama adalah bagian dari definisi tipe di Go seperti yang telah Anda jelaskan.

Saya sama sekali tidak terbiasa dengan kode objek, jadi itu hanya sebuah ide, tetapi tampaknya dimungkinkan untuk menambahkan bagian khusus ke file objek. Jika secara kebetulan, dimungkinkan untuk menyimpan semacam daftar tertaut yang belum dibuka, diisi pada waktu tautan dari nama jenis dan aliasnya mungkin itu bisa membantu. Runtime akan memiliki semua informasi yang dibutuhkan tanpa mengorbankan kompilasi terpisah.

Idenya adalah bahwa runtime harus dapat secara dinamis mengembalikan alias yang berbeda untuk jenis tertentu sehingga pesan kesalahan tetap jelas (karena aliasing memperkenalkan perbedaan penamaan antara kode yang berjalan dan kode tertulis).

Alternatif untuk melacak penggunaan aliasing adalah memiliki cerita versi konkret dalam jumlah besar, untuk dapat "memindahkan" definisi objek di seluruh paket seperti yang dilakukan untuk paket konteks. Tapi itu masalah lain sama sekali.

Pada akhirnya, masih merupakan ide bagus untuk meninggalkan kesetaraan struktural ke antarmuka dan kesetaraan nama dengan tipe.
Mengingat fakta bahwa suatu tipe dapat dianggap sebagai antarmuka dengan lebih banyak batasan, tampaknya mendeklarasikan alias harus/dapat diimplementasikan dengan menyimpan irisan string nama tipe irisan per-paket.

@atdiar Saya tidak yakin maksud Anda apa yang saya lakukan ketika Anda mengatakan "kompilasi terpisah". Jika paket P mengimpor io dan byte, maka ketiganya dapat dikompilasi sebagai langkah terpisah. Namun, jika io atau byte berubah, maka P harus dikompilasi ulang. Ini _bukan_ kasus bahwa Anda dapat membuat perubahan ke io atau byte dan kemudian hanya menggunakan kompilasi lama P. Bahkan dalam mode plugin, ini benar. Karena efek seperti inlining lintas-paket, bahkan perubahan yang tidak terlihat oleh API pada implementasi io atau byte mengubah ABI yang efektif, itulah sebabnya P harus dikompilasi ulang. Ketik alias tidak memperburuk masalah ini.

@j7d , pada tingkat sistem tipe, jumlah tipe atau jenis subtipe apa pun (seperti yang disarankan oleh orang lain sebelumnya dalam diskusi) hanya membantu dengan jenis penggunaan tertentu. Memang benar bahwa kita dapat menganggap byte.Buffer sebagai subtipe dari io.Reader ("a Buffer adalah Pembaca", atau dalam contoh Anda "string adalah Stringeroonie"). Masalah terjadi ketika membangun tipe yang lebih kompleks menggunakan itu. Sisa dari komentar ini berbicara tentang tipe Go tetapi berbicara tentang hubungan mendasar mereka pada tingkat subtipe, bukan apa yang sebenarnya diterapkan oleh bahasa Go. Go harus menerapkan aturan yang konsisten dengan hubungan mendasar.

Konstruktor tipe (cara yang bagus untuk mengatakan "cara menggunakan tipe") adalah kovarian jika mempertahankan hubungan subtipe, kontravarian jika membalikkan hubungan.

Menggunakan tipe dalam hasil fungsi adalah kovarian. A func() Buffer "adalah" func() Reader, karena mengembalikan Buffer berarti Anda telah mengembalikan Reader. Menggunakan tipe dalam argumen fungsi adalah kovarian _not_. Func(Buffer) bukan func(Reader), karena func membutuhkan Buffer, dan beberapa Reader bukan Buffer.

Menggunakan tipe dalam argumen fungsi adalah kontravarian. Sebuah func(Reader) adalah func(Buffer), karena func hanya membutuhkan Reader, dan Buffer adalah Reader. Menggunakan tipe dalam hasil fungsi adalah _not_ kontravarian. Pembaca func() bukan Buffer func(), karena func mengembalikan Pembaca, dan beberapa Pembaca bukan Buffer.

Menggabungkan keduanya, Pembaca func(Reader) bukanlah Buffer func(Buffer), atau sebaliknya, karena argumennya tidak berhasil atau hasilnya tidak berhasil. (Satu-satunya kombinasi di sepanjang baris ini yang berfungsi adalah Buffer func(Reader) adalah Func(Buffer) Reader.)

Secara umum, jika fungsi(X1) X2 adalah (subtipe) fungsi (X3) X4, maka pasti X3 adalah (subtipe) X1 dan demikian pula X2 adalah (subtipe) X4. Dalam kasus penggunaan alias di mana kita ingin T1 dan T2 dapat dipertukarkan, sebuah func(T1) T1 adalah subtipe dari func(T2) T2 hanya jika T1 adalah subtipe dari T2 _dan_ T2 adalah subtipe dari T1. Itu pada dasarnya berarti T1 adalah tipe _sama_ dengan T2, bukan tipe yang lebih umum.

Saya menggunakan argumen fungsi dan hasil karena itulah contoh kanonik (dan yang bagus), tetapi hal yang sama terjadi pada cara lain untuk membangun hasil yang kompleks. Secara umum Anda mendapatkan kovarians untuk output (seperti func() T, atau <-chan T, atau map[...]T) dan kontravarians untuk input (seperti func(T), atau chan<- T, atau map[T ]...) dan kesetaraan tipe paksa untuk input+output (seperti func(T) T, atau chan T, atau *T, atau [10]T, atau []T, atau struct {Field T}, atau variabel tipe T). Sebenarnya kasus yang paling umum di Go, seperti yang Anda lihat dari contoh, adalah input+output.

Secara konkret, []Buffer bukanlah []Reader (karena Anda dapat menyimpan File ke dalam []Reader tetapi tidak ke dalam []Buffer), juga bukan []Reader a []Buffer (karena mengambil dari [] Pembaca mungkin mengembalikan File, saat mengambil dari []Buffer harus mengembalikan Buffer).

Kesimpulan dari semua ini adalah, jika Anda ingin menyelesaikan masalah perbaikan kode umum sehingga kode dapat menggunakan T1 atau T2, Anda tidak dapat melakukannya dengan skema apa pun yang menjadikan T1 hanya subtipe T2 (atau sebaliknya). Masing-masing perlu menjadi subtipe dari yang lain - yaitu, mereka harus menjadi tipe yang sama - atau beberapa dari penggunaan yang terdaftar ini akan menjadi tidak valid.

Artinya, subtipe tidak cukup untuk menyelesaikan masalah perbaikan kode bertahap. Inilah sebabnya mengapa alias tipe memperkenalkan nama baru untuk tipe yang sama, sehingga T1 = T2, alih-alih mencoba subtipe.

Komentar ini juga berlaku untuk saran @iand dua minggu lalu tentang semacam "tipe yang dapat diganti" dan pada dasarnya merupakan perluasan dari balasan @griesemer sejak saat itu.

Memperbarui ringkasan diskusi tingkat atas. Perubahan:

  • Menghapus TODO untuk memperbarui ringkasan diskusi adaptor, yang tampaknya telah memudar.
  • Menambahkan ringkasan diskusi tentang penyematan dan penggantian nama bidang.
  • Memindahkan ringkasan 'metode pada alias' ke bagiannya sendiri dari daftar pertanyaan desain, diperluas untuk menyertakan komentar terbaru.
  • Ditambahkan ringkasan diskusi efek pada program menggunakan refleksi.
  • Ditambahkan ringkasan diskusi kompilasi terpisah.
  • Ditambahkan ringkasan diskusi dari berbagai pendekatan berbasis subtyping.

@rsc mengenai kompilasi terpisah, komentar saya relatif terhadap apakah definisi tipe perlu menyimpan daftar alias mereka (yang tidak dapat dilacak dalam skala besar, karena persyaratan kompilasi terpisah) atau setiap alias melibatkan pembuatan daftar nama alias secara berulang-ulang grafik impor, semua terkait dengan nama tipe awal yang diberikan dalam definisi tipe. (dan bagaimana dan di mana menyimpan informasi itu sehingga runtime memiliki akses ke sana).

@atdiar Tidak ada daftar nama alias seperti itu di mana pun di sistem. Runtime tidak memiliki akses ke sana. Alias ​​​​tidak ada saat runtime.

@rsc Hah, maaf. Saya terjebak dengan proposal alias awal di kepala dan berpikir tentang aliasing untuk func (sambil mendiskusikan aliasing untuk tipe). Dalam hal ini, akan ada perbedaan antara nama dalam kode dan nama saat runtime.
Menggunakan informasi di runtime.Frame untuk logging akan membutuhkan pemikiran ulang dalam kasus itu.
Jangan pedulikan aku.

@rsc terima kasih telah meringkas ulang. Nama bidang yang disematkan masih mengganggu saya; semua solusi yang diusulkan bergantung pada kludges permanen untuk menjaga nama-nama lama tetap ada. Meskipun poin yang lebih besar dalam komentar ini , yaitu bahwa ini adalah kasus khusus untuk mengganti nama bidang, yang juga tidak mungkin, meyakinkan saya bahwa ini memang harus dilihat (dan diselesaikan) sebagai masalah terpisah. Apakah masuk akal untuk membuka masalah terpisah untuk permintaan/proposal/diskusi untuk mendukung penggantian nama bidang untuk perbaikan bertahap (mungkin ditangani dalam rilis go yang sama)?

@Merovius , saya setuju bahwa perbaikan kode bertahap untuk

Kembali dari dua minggu lagi. Diskusi tampaknya telah bertemu. Bahkan pembaruan diskusi dua minggu lalu cukup kecil.

Saya menyarankan agar kita:

  • menerima jenis alias proposal sebagai solusi sementara untuk masalah yang diuraikan di atas,
    asalkan implementasi dapat siap untuk dicoba orang pada awal Go 1.9 (1 Februari).
  • buat cabang dev dev.typealias sehingga CL dapat ditinjau sekarang (Januari) dan digabungkan menjadi master pada awal Go 1.9.
  • membuat keputusan akhir tentang menyimpan alias tipe di dekat awal pembekuan Go 1.9 (seperti yang kami lakukan untuk alias umum dalam siklus Go 1.8).

+1

Saya menghargai sejarah diskusi di balik perubahan ini. Katakanlah itu diterapkan. Tanpa ragu, itu akan menjadi detail bahasa yang agak pinggiran, daripada fitur inti. Dengan demikian, ini menambah kerumitan pada bahasa dan perkakas yang tidak proporsional dengan frekuensi penggunaan sebenarnya. Ini juga menambahkan lebih banyak area permukaan di mana bahasa mungkin disalahgunakan secara tidak sengaja. Untuk alasan itu, menjadi terlalu berhati-hati adalah hal yang baik, dan saya senang bahwa ada banyak diskusi sejauh ini.

@Merovius : Maaf telah mengedit posting saya! Saya pikir tidak ada yang membaca. Awalnya dalam komentar ini, saya menyatakan beberapa skeptisisme bahwa perubahan bahasa ini diperlukan ketika sudah ada alat seperti alat gorename .

@ jcao219 Ini telah dibahas sebelumnya, tetapi yang mengejutkan, sepertinya saya tidak dapat menemukannya dengan cepat di sini. Ini dibahas panjang lebar di utas asli untuk alias umum #16339 dan utas kacang golang yang terkait. Singkatnya: Perkakas semacam ini hanya membahas bagaimana mempersiapkan komit perbaikan, bukan bagaimana mengurutkan perubahan untuk mencegah kerusakan. Apakah perubahan dilakukan oleh alat atau oleh manusia tidak penting untuk masalah, bahwa saat ini tidak ada urutan komit yang tidak akan merusak beberapa kode atau lainnya (komentar asli dari masalah ini dan dokumen terkait membenarkan pernyataan ini lebih dalam -kedalaman).

Untuk perkakas yang lebih otomatis (misalnya terintegrasi ke dalam alat go atau sejenisnya), komentar asli membahas ini di bawah judul "Bisakah ini menjadi perubahan perkakas atau kompiler saja alih-alih perubahan bahasa?".

Sebagai kesimpulan, katakanlah perubahan itu diimplementasikan. Tanpa ragu, itu akan menjadi detail bahasa yang agak pinggiran, daripada fitur inti.

Saya ingin mengungkapkan keraguan. :) Saya tidak menganggap ini sebagai kesimpulan terdahulu.

@Merovius

Saya ingin mengungkapkan keraguan. :) Saya tidak menganggap ini sebagai kesimpulan terdahulu.

Saya kira maksud saya adalah bahwa orang-orang yang akan menggunakan fitur ini terutama akan menjadi pengelola paket-paket penting Go dengan banyak klien yang bergantung. Dengan kata lain, ini menguntungkan mereka yang sudah menjadi ahli Go. Pada saat yang sama, ini menyajikan cara yang menggoda untuk membuat kode kurang mudah dibaca oleh programmer Go baru. Pengecualian adalah kasus penggunaan mengganti nama nama panjang, tetapi nama tipe Go alami biasanya tidak terlalu panjang atau rumit.

Seperti halnya fitur impor titik, akan lebih bijaksana jika tutorial dan dokumen menyertai penyebutan fitur ini dengan pernyataan tentang pedoman penggunaan.

Misalnya, katakan saya ingin menggunakan "github.com/gonum/graph/simple".DirectedGraph , dan saya ingin alias dengan digraph untuk menghindari mengetik simple.DirectedGraph , apakah itu bagus kasus penggunaan? Atau haruskah penggantian nama semacam ini dibatasi pada nama panjang yang tidak masuk akal yang dihasilkan oleh hal-hal seperti protobuf?

@jcao219 , ringkasan diskusi di bagian atas halaman ini menjawab pertanyaan Anda. Secara khusus, lihat bagian ini:

  • Bisakah ini menjadi perubahan perkakas atau kompiler saja alih-alih perubahan bahasa?
  • Apa kegunaan lain yang mungkin dimiliki alias?
  • Batasan (catatan umum yang memulai bagian itu)

Untuk poin Anda yang lebih umum tentang pakar Go vs pemrogram Go baru, tujuan eksplisit Go adalah untuk mempermudah pemrograman dalam basis kode besar. Apakah Anda seorang ahli agak tidak terkait dengan ukuran basis kode tempat Anda bekerja. (Mungkin Anda baru memulai proyek baru yang dimulai orang lain. Anda mungkin masih perlu melakukan pekerjaan semacam ini.)

Oke, berdasarkan kebulatan suara / ketenangan di sini, saya akan (seperti yang saya sarankan minggu lalu di https://github.com/golang/go/issues/18130#issuecomment-268614964) menandai proposal ini disetujui dan membuat cabang dev.typealias .

Ringkasan yang sangat baik memiliki bagian "Masalah lain apa yang perlu ditangani oleh proposal untuk jenis alias?" Apa rencana untuk mengatasi masalah tersebut setelah proposal dinyatakan diterima?

CL https://golang.org/cl/34986 menyebutkan masalah ini.

CL https://golang.org/cl/34987 menyebutkan masalah ini.

CL https://golang.org/cl/34988 menyebutkan masalah ini.

@ulikunitz tentang masalahnya (semua kutipan dari dokumen desain ini mengasumsikan 'tipe T1 = T2'):

  1. Penanganan di godoc. Spesifikasi dokumen desain menunjukkan perubahan minimal pada godoc. Setelah itu, kita dapat melihat apakah dukungan tambahan diperlukan. Mungkin, tapi mungkin tidak.
  2. Bisakah metode didefinisikan pada tipe yang dinamai dengan alias? Ya. Dokumen desain: "Karena T1 hanyalah cara lain untuk menulis T2, ia tidak memiliki kumpulan deklarasi metodenya sendiri. Sebaliknya, kumpulan metode T1 sama dengan T2. ​​Setidaknya untuk percobaan awal, tidak ada batasan terhadap deklarasi metode menggunakan T1 sebagai tipe penerima, asalkan menggunakan T2 dalam deklarasi yang sama akan valid."
  3. Jika alias ke alias diperbolehkan, bagaimana kita menangani siklus alias? Tidak ada siklus. Dokumen desain: "Dalam deklarasi alias tipe, berbeda dengan deklarasi tipe, T2 tidak boleh merujuk, secara langsung atau tidak langsung, ke T1."
  4. Haruskah alias dapat mengekspor pengidentifikasi yang tidak diekspor? Ya. Dokumen desain: "Tidak ada batasan dalam bentuk T2: boleh jenis apa saja, termasuk tetapi tidak terbatas pada jenis yang diimpor dari paket lain."
  5. Apa yang terjadi ketika Anda menyematkan alias (bagaimana Anda mengakses bidang yang disematkan)? Nama diambil dari alias (nama yang terlihat di program). Dokumen desain: https://golang.org/design/18130-type-alias#effect -on-embedding.
  6. Apakah alias tersedia sebagai simbol dalam program yang dibangun? Tidak. Dokumen desain: "Ketik alias sebagian besar tidak terlihat saat runtime." (Jawaban mengikuti dari ini tetapi tidak dipanggil secara eksplisit.)
  7. Injeksi string Ldflags: bagaimana jika kita merujuk ke alias? Tidak ada var alias, jadi ini tidak muncul.

CL https://golang.org/cl/35091 menyebutkan masalah ini.

CL https://golang.org/cl/35092 menyebutkan masalah ini.

CL https://golang.org/cl/35093 menyebutkan masalah ini.

@rsc Terima kasih banyak atas klarifikasinya.

Mari kita asumsikan:

package a

import "b"

type T1 = b.T2

Sejauh yang saya mengerti T1 sangat penting identik dengan b.T2 dan karenanya merupakan tipe non-lokal dan tidak ada metode baru yang dapat didefinisikan. Namun pengidentifikasi T1 diekspor kembali dalam paket a. Apakah ini interpretasi yang benar?

@ulikunitz itu benar

T1 menunjukkan tipe yang sama persis dengan b.T2. Ini hanya nama yang berbeda. Apakah sesuatu diekspor atau tidak didasarkan pada namanya saja (tidak ada hubungannya dengan jenis yang ditunjukkannya).

Untuk membuat balasan @griesemer eksplisit: ya, T1 diekspor dari paket a (karena T1, bukan t1).

CL https://golang.org/cl/35099 menyebutkan masalah ini.

CL https://golang.org/cl/35100 menyebutkan masalah ini.

CL https://golang.org/cl/35101 menyebutkan masalah ini.

CL https://golang.org/cl/35102 menyebutkan masalah ini.

CL https://golang.org/cl/35104 menyebutkan masalah ini.

CL https://golang.org/cl/35106 menyebutkan masalah ini.

CL https://golang.org/cl/35108 menyebutkan masalah ini.

CL https://golang.org/cl/35120 menyebutkan masalah ini.

CL https://golang.org/cl/35121 menyebutkan masalah ini.

CL https://golang.org/cl/35129 menyebutkan masalah ini.

CL https://golang.org/cl/35191 menyebutkan masalah ini.

CL https://golang.org/cl/35233 menyebutkan masalah ini.

CL https://golang.org/cl/35268 menyebutkan masalah ini.

CL https://golang.org/cl/35269 menyebutkan masalah ini.

CL https://golang.org/cl/35670 menyebutkan masalah ini.

CL https://golang.org/cl/35671 menyebutkan masalah ini.

CL https://golang.org/cl/35575 menyebutkan masalah ini.

CL https://golang.org/cl/35732 menyebutkan masalah ini.

CL https://golang.org/cl/35733 menyebutkan masalah ini.

CL https://golang.org/cl/35831 menyebutkan masalah ini.

CL https://golang.org/cl/36014 menyebutkan masalah ini.

Ini sekarang dalam master, sebelum pembukaan Go 1.9. Silakan menyinkronkan pada master dan mencoba berbagai hal. Terima kasih.

Dialihkan dari #18893

package main

import (
        "fmt"
        "q"
)

func main() {
        var a q.A
        var b q.B // i'm a named unnamed type !!!

        fmt.Printf("%T\t%T\n", a, b)
}

Apa yang Anda harapkan untuk dilihat?

deadwood(~/src) % go run main.go
q.A     q.B

Apa yang Anda lihat sebagai gantinya?

deadwood(~/src) % go run main.go
q.A     []int

Diskusi

Alias ​​​​tidak boleh berlaku untuk jenis yang tidak disebutkan namanya. Tidak ada cerita "perbaikan kode" dalam berpindah dari satu jenis yang tidak disebutkan namanya ke yang lain. Mengizinkan alias pada tipe yang tidak disebutkan namanya berarti saya tidak bisa lagi mengajar Go hanya sebagai tipe bernama dan tidak bernama. Sebaliknya saya harus mengatakan

oh, kecuali itu alias, dalam hal ini Anda harus ingat bahwa itu _bisa jadi_ tipe yang tidak disebutkan namanya, bahkan ketika Anda mengimpor ke dari paket lain.

Dan lebih buruk lagi, itu akan memungkinkan orang untuk menyebarkan pola anti keterbacaan seperti

type Any = interface{}

Harap jangan izinkan jenis yang tidak disebutkan namanya menjadi alias.

@davecheney

Tidak ada cerita "perbaikan kode" dalam berpindah dari satu jenis yang tidak disebutkan namanya ke yang lain.

Tidak benar. Bagaimana jika Anda ingin mengubah tipe parameter metode dari tipe bernama menjadi tipe tanpa nama atau sebaliknya? Langkah 1 adalah menambahkan alias; langkah 2 adalah memperbarui tipe yang mengimplementasikan metode tersebut untuk menggunakan tipe baru; langkah 3 adalah menghapus alias.

(Memang benar bahwa Anda dapat melakukannya hari ini dengan mengganti nama metode dua kali. Mengganti nama ganda paling membosankan.)

Dan lebih buruk lagi, itu akan memungkinkan orang untuk menyebarkan pola anti keterbacaan seperti
type Any = interface{}

Orang sudah dapat menulis type Any interface{} hari ini. Kerugian tambahan apa yang diperkenalkan alias dalam kasus ini?

Orang sudah bisa menulis antarmuka tipe Any{} hari ini. Kerugian tambahan apa yang diperkenalkan alias dalam kasus ini?

Saya menyebutnya anti pola karena memang begitulah adanya. type Any interface{} , karena orang yang _menulis_ kode mengetik sesuatu yang sedikit lebih pendek, itu lebih masuk akal bagi mereka.

Di sisi lain, _semua_ pembaca, yang berpengalaman membaca kode Go dan mengenali interface{} secara naluriah seperti wajah mereka di cermin, harus mempelajari, dan mempelajari kembali, setiap varian Any , Object , T , dan memetakannya ke hal-hal seperti type Any interface{} , type Any map[interface{}]interface{} , type Any struct{} per paket.

Tentunya Anda setuju bahwa nama khusus paket untuk idiom Go biasa adalah negatif bersih untuk keterbacaan?

Tentunya Anda setuju bahwa nama khusus paket untuk idiom Go biasa adalah negatif bersih untuk keterbacaan?

Saya setuju, tetapi karena contoh yang dimaksud (sejauh ini kemunculan paling umum dari antipatterrn yang saya temui) dapat dilakukan tanpa alias, saya tidak mengerti bagaimana contoh itu berhubungan dengan proposal untuk tipe alias.

Fakta bahwa anti-pola dimungkinkan tanpa alias tipe berarti bahwa kita harus sudah mendidik programmer Go untuk menghindarinya, terlepas dari apakah alias untuk tipe yang tidak disebutkan namanya bisa ada.

Dan, pada kenyataannya, jenis alias memungkinkan untuk _penghapusan bertahap_ antipattern itu dari basis kode yang sudah ada.

Mempertimbangkan:

package antipattern

type Any interface{}  // not an alias

type Widget interface{
  Frozzle(Any) error
}

func Bozzle(w Widget) error {
  …
}

Saat ini, pengguna antipattern.Bozzle akan terjebak menggunakan antipattern.Any dalam implementasi Widget , dan tidak ada cara untuk menghapus antipattern.Any dengan perbaikan bertahap. Tetapi dengan alias tipe, pemilik paket antipattern dapat mendefinisikan ulang seperti ini:

// Any is deprecated; please use interface{} directly.
type Any = interface{}

Dan sekarang pemanggil dapat bermigrasi dari Any ke interface{} secara bertahap, memungkinkan pengelola antipattern untuk menghapusnya pada akhirnya.

Maksud saya adalah tidak ada pembenaran untuk menyebut tipe yang tidak disebutkan namanya, jadi
melarang opsi ini akan terus menggarisbawahi ketidaksesuaian
Latihan.

Kebalikannya, untuk mengizinkan aliasing dari tipe yang tidak disebutkan namanya memungkinkan bukan hanya satu, tetapi dua
bentuk pola anti ini.

Pada Kamis, 2 Februari 2017, 16:34 Bryan C. Mills [email protected] menulis:

Tentunya Anda setuju bahwa nama khusus paket untuk idiom Go yang biasa adalah
negatif bersih untuk keterbacaan?

Saya setuju, tetapi karena contoh yang dimaksud (sejauh ini yang paling umum
terjadinya antipattern yang saya temui) dapat dilakukan tanpa
alias, saya tidak mengerti bagaimana contoh itu berhubungan dengan proposal untuk
ketik alias.

Fakta bahwa anti-pola dimungkinkan tanpa alias tipe berarti bahwa
kita harus sudah mendidik programmer Go untuk menghindarinya, terlepas dari apakah
alias untuk tipe yang tidak disebutkan namanya bisa ada.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/golang/go/issues/18130#issuecomment-276872714 , atau bisukan
benang
https://github.com/notifications/unsubscribe-auth/AAAcA6BGrFjjTi7eW1BPp7o81XIekbGXks5rYWr-gaJpZM4LBBEL
.

@davecheney Saya rasa kami belum memiliki bukti bahwa dapat memberikan nama tipe arbitrer itu berbahaya. Ini juga bukan fitur "kejutan" yang tidak terduga - telah dibahas secara rinci dalam dokumen desain . Pada titik ini masuk akal untuk menggunakan ini untuk beberapa waktu dan melihat di mana itu membawa kita.

Sebagai contoh tandingan, ada API publik yang menggunakan literal tipe hanya karena API tidak ingin membatasi klien ke tipe tertentu (lihat https://golang.org/pkg/go/types/#Info misalnya ). Memiliki literal tipe eksplisit itu mungkin merupakan dokumentasi yang berguna. Tetapi pada saat yang sama bisa sangat menjengkelkan untuk mengulang jenis literal yang sama di semua tempat; dan pada kenyataannya menjadi penghalang untuk keterbacaan. Mampu berbicara dengan nyaman tentang IntSet daripada map[int]struct{} terkunci di dalamnya dan hanya definisi IntSet adalah nilai tambah dalam pikiran saya. Di situlah type IntSet = map[int]struct{} tepat.

Akhirnya, saya ingin merujuk kembali ke https://github.com/golang/go/issues/18130#issuecomment -268411811 jika Anda melewatkannya. Deklarasi tipe tak terbatas menggunakan = sebenarnya adalah deklarasi tipe "dasar", dan saya senang akhirnya kita memilikinya di Go.

Mungkin type intSet = map[int]struct{} (tidak diekspor) akan menjadi cara yang lebih baik untuk menggunakan alias tipe yang tidak disebutkan namanya, tetapi ini terdengar seperti domain CodeReviewComments dan praktik pemrograman yang disarankan, daripada membatasi fitur.

Yang mengatakan, %T adalah alat yang berguna untuk melihat tipe ketika men-debug atau menjelajahi sistem tipe. Saya ingin tahu apakah harus ada kata kerja format serupa yang menyertakan alias? q.B = []int dalam contoh @davecheney .

@nathany Bagaimana Anda menerapkan kata kerja itu? Informasi alias tidak ada saat runtime. (Sejauh menyangkut paket reflect , aliasnya adalah _tipe yang sama_ dengan aliasnya.)

@bcmils Saya pikir itu mungkin masalahnya...

Saya membayangkan alat analisis statis dan plugin editor masih ada dalam gambar untuk membantu bekerja dengan alias, jadi tidak apa-apa.

Pada 2 Februari 2017 17:01, "Nathan Youngman" [email protected] menulis:

Yang mengatakan, %T adalah alat yang berguna untuk melihat tipe saat men-debug atau menjelajahi
sistem tipe. Saya ingin tahu apakah harus ada kata kerja format serupa itu
termasuk alias? qB = []int di @davecheney
https://github.com/davecheney contoh.

Saya pikir solusi yang lebih baik adalah menambahkan mode kueri ke guru untuk menjawab ini
pertanyaan:

yang merupakan alias yang dideklarasikan di seluruh GOPATH (atau paket yang diberikan) untuk
jenis yang diberikan ini pada baris perintah?

Saya tidak khawatir tentang penyalahgunaan alias tipe yang tidak disebutkan namanya, tetapi potensi
alias yang digandakan ke jenis tanpa nama yang sama.

@davecheney Saya menambahkan saran Anda ke bagian "Pembatasan" dari ringkasan diskusi di atas. Seperti semua pembatasan, posisi umum kami adalah bahwa pembatasan menambah kerumitan (lihat catatan di atas) dan kami mungkin perlu melihat bukti nyata dari bahaya yang meluas untuk memperkenalkan pembatasan. Harus mengubah cara Anda mengajar Go tidak cukup: setiap perubahan yang kami buat pada bahasa akan memerlukan perubahan cara Anda mengajar Go.

Seperti disebutkan dalam dokumen desain dan milis, kami sedang mengerjakan terminologi yang lebih baik untuk membuat penjelasan lebih mudah.

@minux , seperti yang ditunjukkan @bcmils , alias informasi tidak ada saat runtime (sepenuhnya mendasar untuk desain). Tidak ada cara untuk menerapkan "%T yang menyertakan alias".

Pada 2 Februari 2017 20:33, "Russ Cox" [email protected] menulis:

@minux https://github.com/minux , seperti @bcmills
https://github.com/bcmils menunjukkan, informasi alias tidak ada
saat runtime (sepenuhnya mendasar untuk desain). Tidak ada cara untuk
mengimplementasikan "%T yang menyertakan alias".

Saya menyarankan mode kueri Go guru (https://golang.org/x/tools/cmd/guru)
untuk pemetaan alias terbalik, yang didasarkan pada analisis kode statis. Dia
tidak masalah apakah informasi alias tersedia saat runtime atau tidak.

@minux , oh begitu, Anda membalas melalui email dan Github membuat teks yang dikutip terlihat seperti teks yang Anda tulis sendiri. Saya membalas teks yang Anda kutip dari Nathan Youngman, mengira itu milik Anda. Maaf bila membingungkan.

Mengenai terminologi dan pengajaran, saya menemukan latar belakang tipe bermerek yang diposting @griesemer cukup informatif. Terima kasih untuk itu.

Saat menjelaskan tipe dan konversi tipe, baby gophers awalnya mengira saya sedang berbicara tentang alias tipe, kemungkinan karena keakraban dengan bahasa lain.

Apa pun terminologi terakhirnya, saya dapat membayangkan memperkenalkan alias tipe sebelum tipe bernama (bermerek), terutama karena mendeklarasikan tipe bernama baru kemungkinan akan muncul setelah memperkenalkan byte dan rune dalam buku atau kurikulum apa pun. Namun, saya ingin memperhatikan perhatian @davecheney untuk tidak mendorong anti-pola.

Untuk type intSet map[int]struct{} kita katakan map[int]struct{} adalah tipe _underlying_. Apa yang kita sebut kedua sisi type intSet = map[int]struct{} ? Alias ​​​​dan tipe alias?

Adapun %T , saya sudah perlu menjelaskan bahwa byte dan rune menghasilkan uint8 dan int32 , jadi ini bukan berbeda.

Jika ada, saya pikir alias tipe akan membuat byte dan rune lebih mudah untuk dijelaskan. IMO, tantangannya adalah mengetahui kapan harus menggunakan tipe bernama vs. tipe alias, dan kemudian dapat mengomunikasikannya.

@nathany Saya pikir masuk akal untuk memperkenalkan "tipe alias" terlebih dahulu - meskipun saya tidak akan menggunakan istilah itu. Deklarasi "alias" yang baru diperkenalkan hanyalah deklarasi biasa yang tidak melakukan sesuatu yang istimewa. Pengidentifikasi di sebelah kiri dan tipe di sebelah kanan adalah satu dan sama, mereka menunjukkan tipe yang identik. Saya bahkan tidak yakin kita memerlukan istilah alias atau tipe alias (kita tidak menyebut nama konstan sebagai alias, dan nilai konstannya konstan alias).

Deklarasi tipe tradisional (non-alias) berfungsi lebih baik: Pertama-tama membuat tipe baru dari tipe di sebelah kanan sebelum mengikat pengidentifikasi di sebelah kiri ke sana. Jadi pengenal dan tipe di sebelah kanan tidak sama (mereka hanya berbagi tipe dasar yang sama). Ini jelas merupakan konsep yang lebih rumit.

Kami memang membutuhkan istilah baru untuk jenis yang baru dibuat ini karena jenis apa pun sekarang dapat memiliki nama. Dan kita harus dapat merujuknya karena ada aturan spesifikasi yang merujuknya (identitas tipe, penugasan, tipe basis penerima).

Berikut cara lain untuk menggambarkan ini, yang mungkin berguna dalam lingkungan pengajaran: Suatu tipe dapat berwarna atau tidak berwarna. Semua tipe yang dideklarasikan sebelumnya, dan semua literal tipe tidak berwarna. Satu-satunya cara untuk membuat tipe berwarna baru adalah melalui deklarasi tipe tradisional (non-alias) yang pertama-tama mengecat (salinan) tipe di sebelah kanan dengan warna baru yang belum pernah digunakan sebelumnya (menghapus warna lama, jika ada, seluruhnya dalam proses) sebelum mengikat pengidentifikasi di sebelah kiri. Sekali lagi, pengidentifikasi dan jenis berwarna (dibuat secara implisit dan tidak terlihat) adalah identik, tetapi mereka berbeda dari jenis (berwarna berbeda atau tidak berwarna) yang tertulis di sebelah kanan.

Dengan menggunakan analogi ini, kita juga dapat merumuskan kembali berbagai aturan lain yang ada:

  • Tipe berwarna selalu berbeda dari tipe lainnya (karena setiap deklarasi tipe menggunakan warna baru yang belum pernah digunakan sebelumnya).
  • Metode hanya dapat dikaitkan dengan tipe dasar penerima yang diwarnai.
  • Jenis yang mendasari suatu jenis adalah jenis yang dilucuti semua warnanya.
    dll.

kami tidak menyebut nama konstan sebagai alias, dan nilai konstanta sebagai konstanta alias

poin bagus 👍

Saya tidak yakin apakah analogi berwarna vs. tidak berwarna lebih mudah dipahami, tetapi ini menunjukkan bahwa ada lebih dari satu cara untuk menjelaskan konsepnya.

Jenis tradisional bernama/bermerek/berwarna tentu membutuhkan penjelasan lebih lanjut. Terutama ketika tipe bernama dapat dideklarasikan menggunakan tipe bernama yang ada. Ada perbedaan yang cukup halus yang perlu diingat.

type intSet map[int]struct{} // a new type with an underlying type map[int]struct{}

type myIntSet intSet // a new type with an underlying type map[int]struct{}

type otherIntSet = intSet // just another name (alias) for intSet, add methods to intSet (only in the same package)

type literalIntSet = map[int]struct{} // just another name for map[int]struct{}, no adding methods

Ini tidak dapat diatasi. Dengan asumsi ini mendarat di Go 1.9, saya menduga kita akan melihat edisi ke-2 dari beberapa buku Go. 😉

Saya secara teratur merujuk ke spesifikasi Go untuk terminologi yang diterima, jadi saya sangat ingin tahu istilah apa yang dipilih pada akhirnya.

Kami memang membutuhkan istilah baru untuk jenis yang baru dibuat ini karena jenis apa pun sekarang dapat memiliki nama.

Beberapa ide:

  • "dibedakan" atau "berbeda" (seperti dalam, dapat dibedakan dari jenis lain)
  • "unik" (seperti, ini adalah tipe yang berbeda dari semua tipe lainnya)
  • "beton" (seperti, itu adalah entitas yang ada di runtime)
  • "dapat diidentifikasi" (seperti dalam, tipe memiliki identitas)

@bcmils Kami telah memikirkan tentang jenis yang dibedakan, unik, berbeda, bermerek, berwarna, ditentukan, non-alias, dll. "Beton" menyesatkan karena antarmuka dapat diwarnai juga, dan antarmuka adalah inkarnasi dari tipe abstrak. "Dapat diidentifikasi" juga tampaknya menyesatkan karena "struct{int}" dapat diidentifikasi sama seperti jenis yang disebutkan secara eksplisit (non-alias).

Saya akan merekomendasikan terhadap:

  • "berwarna" (dalam konteks non-pemrograman, frasa "tipe berwarna" membawa konotasi bias rasial yang kuat)
  • "non-alias" (ini membingungkan, karena target alias mungkin atau mungkin bukan yang sebelumnya disebut "tipe bernama")
  • "didefinisikan" (alias juga didefinisikan, mereka hanya didefinisikan sebagai alias)

"bermerek" bisa berhasil: itu membawa konotasi "tipe sebagai ternak" tetapi itu tidak menurut saya buruk secara intrinsik.

Unik dan berbeda tampak seperti pilihan yang menonjol sejauh ini.

Mereka sederhana dan dapat dimengerti tanpa banyak konteks atau pengetahuan tambahan. Jika saya tidak tahu perbedaannya, saya pikir saya setidaknya memiliki pengertian umum tentang apa yang mereka maksudkan. Saya tidak bisa mengatakan itu tentang pilihan lain.

Setelah Anda mempelajari istilah itu, itu tidak masalah, tetapi nama konotatif menghindari hambatan yang tidak perlu untuk menginternalisasi perbedaan.

Ini adalah definisi dari argumen bikeshed. Robert memiliki CL yang tertunda di https://go-review.googlesource.com/#/c/36213/ yang tampaknya baik-baik saja.

CL https://golang.org/cl/36213 menyebutkan masalah ini.

Saya ingin mengangkat masalah go fix lagi.

Agar jelas bahwa saya tidak menyarankan 'menghapus' alias. Mungkin itu sesuatu yang berguna dan cocok untuk pekerjaan lain, itu cerita lain.

Ini adalah sesuatu yang sangat penting di IMO bahwa judulnya tentang tipe bergerak. Saya tidak ingin memperumit masalah ini. Tujuan kami adalah untuk menangani semacam perubahan antarmuka dalam sebuah proyek. Ketika kami sampai pada perubahan pada antarmuka, tidak benar bahwa kami berharap semua pengguna menggunakan dua antarmuka ini (lama & baru) sebagai sama pada akhirnya , dan itulah mengapa kami mengatakan 'perbaikan kode bertahap'. Kami berharap pengguna menghapus/mengubah penggunaan yang lama.

Saya masih menganggap alat sebagai metode terbaik untuk memperbaiki kode, seperti ide yang disarankan @ tux21b . Sebagai contoh:

$ cat "$GOROOT"/RENAME
# This file could be used for `go fix`
[package]
x/net/context=context
[type]
io.ByteBuffer=bytes.Buffer

$ go fix -rename "$GOROOT"/RENAME [packages]
# -- or --
# use a standard libraries rename table as default
$ go fix -rename [packages]
# -- or --
# include this fix as default
$ go fix [packages]

Satu-satunya alasan @rsc mengatakan tidak di sini adalah bahwa perubahan akan memengaruhi alat lain. Tapi saya pikir itu tidak benar dalam alur kerja ini : jika ada paket yang kedaluwarsa (misalnya ketergantungan) menggunakan nama/jalur paket yang tidak digunakan lagi, misalnya x/net/context , kita dapat memperbaiki kodenya terlebih dahulu , seperti yang dikatakan doc cara memigrasikan kode ke versi baru, tetapi bukan hard-coding, melalui tabel yang dapat dikonfigurasi dalam format teks. Kemudian Anda dapat menggunakan alat apa pun kapan pun Anda suka sama seperti Go versi baru. Ada efek samping: itu akan mengubah kode.

@LionNatsu , saya pikir Anda benar, tapi saya pikir itu masalah terpisah: haruskah kita mengadopsi konvensi untuk paket untuk menjelaskan kepada calon klien cara memperbarui kode mereka sebagai respons terhadap perubahan API secara mekanis? Mungkin, tapi kita harus mencari tahu apa konvensi itu. Bisakah Anda membuka edisi terpisah untuk topik ini, dengan merujuk kembali ke percakapan ini? Terima kasih.

CL https://golang.org/cl/36691 menyebutkan masalah ini.

Dengan proposal ini di ujung, saya sekarang dapat membuat paket ini:

package safe

import "unsafe"

type Pointer = unsafe.Pointer

yang memungkinkan program untuk membuat nilai unsafe.Pointer tanpa mengimpor unsafe secara langsung:

package main

import "safe"

func main() {
    x := []int{4, 9}
    y := *(*int)(safe.Pointer(uintptr(safe.Pointer(&x[0])) + 8))
    println(y)
}

Dokumen desain deklarasi alias asli menyebut ini sebagai didukung secara eksplisit. Ini tidak eksplisit dalam proposal alias jenis yang lebih baru ini, tetapi berfungsi.

Pada masalah deklarasi alias alasan untuk ini adalah: _"Alasan kami mengizinkan aliasing untuk unsafe.Pointer adalah bahwa sudah mungkin untuk mendefinisikan tipe yang memiliki unsafe.Pointer sebagai tipe dasar."_ https://github.com/ golang/go/issues/16339#issuecomment -232435361

Meskipun itu benar, saya pikir mengizinkan alias unsafe.Pointer memperkenalkan sesuatu yang baru: program sekarang dapat membuat nilai unsafe.Pointer tanpa secara eksplisit mengimpor tidak aman.

Untuk menulis program di atas sebelum proposal ini, saya harus memindahkan safe.Pointer cast ke dalam paket yang mengimpor unsafe. Ini mungkin membuat sedikit lebih sulit untuk mengaudit program untuk penggunaan yang tidak aman.

@crawshaw , tidak bisakah Anda melakukan ini sebelumnya?

package safe

import (
  "reflect"
  "unsafe"
)

func Pointer(p interface {}) unsafe.Pointer {
  switch v := reflect.ValueOf(p); v.Kind() {
  case reflect.Uintptr:
    return unsafe.Pointer(uintptr(v.Uint()))
  default:
    return unsafe.Pointer(v.Pointer())
  }
}

Saya percaya itu akan memungkinkan program yang persis sama untuk dikompilasi, dengan kurangnya impor yang sama dalam paket main .

(Itu belum tentu merupakan program yang valid: konversi uintptr -to- Pointer menyertakan panggilan fungsi, sehingga tidak memenuhi batasan paket unsafe yang " kedua konversi harus muncul dalam ekspresi yang sama, dengan hanya aritmatika intervensi di antara mereka". Namun, saya menduga akan mungkin untuk membuat program yang setara dan valid tanpa mengimpor unsafe dari main dengan membuat penggunaan hal-hal seperti reflect.SliceHeader .)

Sepertinya mengekspor jenis tidak aman yang tersembunyi hanyalah aturan lain untuk ditambahkan ke audit.

Ya, saya ingin menunjukkan bahwa aliasing secara langsung tidak aman. Pointer membuat kode lebih sulit untuk diaudit, cukup sehingga saya berharap tidak ada yang akhirnya melakukannya.

@crawshaw Per komentar saya, ini juga benar sebelum kami mengetik aliasing. Berikut ini adalah valid:

package a

import "unsafe"

type P unsafe.Pointer
package main

import "./a"
import "fmt"

var x uint64 = 0xfedcba9876543210
var h = *(*uint32)(a.P(uintptr(a.P(&x)) + 4))

func main() {
    fmt.Printf("%x\n", h)
}

Artinya, di package main, saya bisa melakukan unsafe arithmetic menggunakan a.P meskipun tidak ada paket unsafe dan a.P bukan alias. Ini selalu mungkin.

Apakah ada hal lain yang Anda maksud?

Kesalahanku. Saya pikir itu tidak berhasil. (Saya mendapat kesan bahwa aturan khusus yang diterapkan pada unsafe.Pointer tidak akan menyebar ke tipe baru yang ditentukan darinya.)

Spesifikasi sebenarnya tidak jelas tentang ini. Melihat implementasi go/types, ternyata implementasi awal saya membutuhkan unsafe.Pointer persis, bukan hanya beberapa tipe yang kebetulan memiliki tipe dasar unsafe.Pointer . Saya baru saja menemukan #6326 yaitu ketika saya mengubah go/types menjadi gc compliant.

Mungkin kita harus melarang ini untuk definisi tipe reguler dan juga melarang alias unsafe.Pointer . Saya tidak dapat melihat alasan bagus untuk mengizinkannya dan itu membahayakan ketegasan karena harus mengimpor unsafe untuk kode yang tidak aman.

Ini telah terjadi. Saya rasa tidak ada yang tersisa di sini.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat