Go: proposal: spesifikasi: tambahkan dukungan enum yang diketik

Dibuat pada 1 Apr 2017  ·  180Komentar  ·  Sumber: golang/go

Saya ingin mengusulkan agar enum ditambahkan ke Go sebagai jenis khusus type . Contoh di bawah ini dipinjam dari contoh protobuf.

Enum di Go hari ini

type SearchRequest int
var (
    SearchRequestUNIVERSAL SearchRequest = 0 // UNIVERSAL
    SearchRequestWEB       SearchRequest = 1 // WEB
    SearchRequestIMAGES    SearchRequest = 2 // IMAGES
    SearchRequestLOCAL     SearchRequest = 3 // LOCAL
    SearchRequestNEWS      SearchRequest = 4 // NEWS
    SearchRequestPRODUCTS  SearchRequest = 5 // PRODUCTS
    SearchRequestVIDEO     SearchRequest = 6 // VIDEO
)

type SearchRequest string
var (
    SearchRequestUNIVERSAL SearchRequest = "UNIVERSAL"
    SearchRequestWEB       SearchRequest = "WEB"
    SearchRequestIMAGES    SearchRequest = "IMAGES"
    SearchRequestLOCAL     SearchRequest = "LOCAL"
    SearchRequestNEWS      SearchRequest = "NEWS"
    SearchRequestPRODUCTS  SearchRequest = "PRODUCTS"
    SearchRequestVIDEO     SearchRequest = "VIDEO"
)

// IsValid has to be called everywhere input happens, or you risk bad data - no guarantees
func (sr SearchRequest) IsValid() bool {
    switch sr {
        case SearchRequestUNIVERSAL, SearchRequestWEB...:
            return true
    }
    return false
}

Bagaimana tampilannya dengan dukungan bahasa

enum SearchRequest int {
    0 // UNIVERSAL
    1 // WEB
    2 // IMAGES
    3 // LOCAL
    4 // NEWS
    5 // PRODUCTS
    6 // VIDEO
}

enum SearchRequest string {
    "UNIVERSAL"
    "WEB"
    "IMAGES"
    "LOCAL"
    "NEWS"
    "PRODUCTS"
    "VIDEO"
}

Polanya cukup umum sehingga saya pikir itu memerlukan casing khusus, dan saya yakin itu membuat kode lebih mudah dibaca. Pada lapisan implementasi, saya akan membayangkan bahwa sebagian besar kasus dapat diperiksa pada waktu kompilasi, beberapa di antaranya sudah terjadi hari ini, sementara yang lain hampir tidak mungkin atau memerlukan pengorbanan yang signifikan.

  • Keamanan untuk jenis yang diekspor : tidak ada yang mencegah seseorang melakukan SearchRequest(99) atau SearchRequest("MOBILEAPP") . Solusi saat ini termasuk membuat jenis yang tidak diekspor dengan opsi, tetapi itu sering membuat kode yang dihasilkan lebih sulit untuk digunakan / didokumentasikan.
  • Keamanan runtime : Sama seperti protobuf akan memeriksa validitas saat unmarshaling, ini menyediakan validasi bahasa yang luas, kapan pun enum dipakai.
  • Perkakas / Dokumentasi : banyak paket saat ini memasukkan opsi yang valid ke dalam kolom komentar, tetapi tidak semua orang melakukannya dan tidak ada jaminan bahwa komentar tersebut tidak ketinggalan zaman.

Hal yang Perlu Dipertimbangkan

  • Nil : dengan menerapkan enum di atas sistem tipe, saya tidak percaya ini harus memerlukan casing khusus. Jika seseorang ingin nil valid, maka enum harus didefinisikan sebagai pointer.
  • Nilai default / tugas runtime : Ini adalah salah satu keputusan yang lebih sulit untuk dibuat. Bagaimana jika nilai default Go tidak didefinisikan sebagai enum yang valid? Analisis statis dapat mengurangi sebagian dari ini pada waktu kompilasi, tetapi perlu ada cara untuk menangani input dari luar.

Saya tidak memiliki pendapat yang kuat tentang sintaks. Saya yakin ini bisa dilakukan dengan baik dan akan memberikan dampak positif bagi ekosistem.

Go2 LanguageChange NeedsInvestigation Proposal

Komentar yang paling membantu

@md2perpe itu bukan enum.

  1. Mereka tidak dapat dihitung, diulang.
  2. Mereka tidak memiliki representasi string yang berguna.
  3. Mereka tidak memiliki identitas:

``` pergi
paket utama

impor (
"fmt"
)

fungsi utama() {
ketik SearchRequest ke dalam
konstan (
Permintaan Pencarian Universal = sedikit pun
Web
)

const (
    Another SearchRequest = iota
    Foo
)

fmt.Println("Should be false: ", (Web == Foo))
    // Prints: "Should be false:  true"

}
````

Saya sangat setuju dengan @derekperkins bahwa Go membutuhkan enum sebagai warga negara kelas satu. Seperti apa bentuknya, saya tidak yakin, tapi saya menduga itu bisa dilakukan tanpa merusak rumah kaca Go 1.

Semua 180 komentar

@derekparker ada diskusi untuk membuat proposal Go2 di #19412

Saya membacanya sebelumnya hari ini, tetapi itu tampaknya lebih fokus pada tipe yang valid, di mana ini difokuskan pada nilai tipe yang valid. Mungkin ini adalah bagian dari proposal itu, tetapi juga merupakan perubahan yang tidak terlalu luas pada sistem tipe yang dapat dimasukkan ke dalam Go hari ini.

enum adalah kasus khusus dari tipe jumlah di mana semua tipenya sama dan ada nilai yang terkait dengan masing-masing dengan metode. Lebih banyak mengetik, tentu saja, tetapi efeknya sama. Terlepas dari itu, itu akan menjadi satu atau yang lain, jenis jumlah mencakup lebih banyak dasar, dan bahkan jenis jumlah tidak mungkin. Tidak ada yang terjadi sampai Go2 karena perjanjian kompatibilitas Go1, dalam hal apa pun, karena proposal ini, setidaknya, memerlukan kata kunci baru, jika salah satu dari mereka diterima

Cukup adil, tetapi tidak satu pun dari proposal ini yang melanggar perjanjian kompatibilitas. Ada pendapat yang menyatakan bahwa jenis jumlah "terlalu besar" untuk ditambahkan ke Go1. Jika itu masalahnya, maka proposal ini adalah jalan tengah yang berharga yang bisa menjadi batu loncatan untuk tipe full sum di Go2.

Keduanya memerlukan kata kunci baru yang akan memecahkan kode Go1 yang valid dengan menggunakannya sebagai pengenal

Saya pikir itu bisa diatasi

Fitur bahasa baru membutuhkan kasus penggunaan yang menarik. Semua fitur bahasa berguna, atau tidak ada yang akan mengusulkannya; pertanyaannya adalah: apakah mereka cukup berguna untuk membenarkan kerumitan bahasa dan mengharuskan setiap orang untuk mempelajari konsep-konsep baru? Apa kasus penggunaan yang menarik di sini? Bagaimana orang akan menggunakan ini? Misalnya, apakah orang berharap dapat mengulangi set nilai enum yang valid, dan jika demikian, bagaimana mereka akan melakukannya? Apakah proposal ini lebih dari sekadar memungkinkan Anda menghindari penambahan kasus default ke beberapa sakelar?

Inilah cara idiomatik menulis enumerasi di Go saat ini:

type SearchRequest int

const (
    Universal SearchRequest = iota
    Web
    Images
    Local
    News
    Products
    Video
)

Ini memiliki keuntungan bahwa mudah untuk membuat flag yang dapat OR:ed (menggunakan operator | ):

type SearchRequest int

const (
    Universal SearchRequest = 1 << iota
    Web
    Images
    Local
    News
    Products
    Video
)

Saya tidak melihat bahwa memperkenalkan kata kunci enum akan membuatnya lebih pendek.

@md2perpe itu bukan enum.

  1. Mereka tidak dapat dihitung, diulang.
  2. Mereka tidak memiliki representasi string yang berguna.
  3. Mereka tidak memiliki identitas:

``` pergi
paket utama

impor (
"fmt"
)

fungsi utama() {
ketik SearchRequest ke dalam
konstan (
Permintaan Pencarian Universal = sedikit pun
Web
)

const (
    Another SearchRequest = iota
    Foo
)

fmt.Println("Should be false: ", (Web == Foo))
    // Prints: "Should be false:  true"

}
````

Saya sangat setuju dengan @derekperkins bahwa Go membutuhkan enum sebagai warga negara kelas satu. Seperti apa bentuknya, saya tidak yakin, tapi saya menduga itu bisa dilakukan tanpa merusak rumah kaca Go 1.

@md2perpe iota adalah cara yang sangat terbatas untuk mendekati enum, yang bekerja sangat baik untuk keadaan terbatas.

  1. Anda membutuhkan int
  2. Anda hanya perlu konsisten di dalam paket Anda, tidak mewakili keadaan eksternal

Segera setelah Anda perlu merepresentasikan string atau tipe lain, yang sangat umum untuk flag eksternal, iota tidak bekerja untuk Anda. Jika Anda ingin mencocokkan dengan representasi eksternal/database, saya tidak akan menggunakan iota , karena kemudian memesan dalam kode sumber penting dan menyusun ulang akan menyebabkan masalah integritas data.

Ini bukan hanya masalah kenyamanan untuk membuat kode lebih pendek. Ini adalah proposal yang memungkinkan integritas data dengan cara yang tidak dapat diterapkan oleh bahasa saat ini.

@ianlancetaylor

Misalnya, apakah orang berharap dapat mengulangi set nilai enum yang valid, dan jika demikian, bagaimana mereka akan melakukannya?

Saya pikir itu adalah kasus penggunaan yang solid, seperti yang disebutkan oleh @bep. Saya pikir iterasi akan terlihat seperti loop Go standar, dan saya pikir mereka akan mengulang dalam urutan yang ditentukan.

for i, val := range SearchRequest {
...
}

Jika Go menambahkan sesuatu yang lebih dari sedikit, maka mengapa tidak menggunakan tipe data aljabar?

Dengan perpanjangan pemesanan menurut urutan definisi, dan mengikuti contoh protobuf, saya pikir nilai default bidang akan menjadi bidang yang ditentukan pertama.

@bep Tidak nyaman, tetapi Anda bisa mendapatkan semua properti ini:

package main

var SearchRequests []SearchRequest
type SearchRequest struct{ name string }
func (req SearchRequest) String() string { return req.name }

func Request(name string) SearchRequest {
    req := SearchRequest{name}
    SearchRequests = append(SearchRequests, req)
    return req
}

var (
    Universal = Request("Universal")
    Web       = Request("Web")

    Another = Request("Another")
    Foo     = Request("Foo")
)

func main() {
    fmt.Println("Should be false: ", (Web == Foo))
    fmt.Println("Should be true: ", (Web == Web))
    for i, req := range SearchRequests {
        fmt.Println(i, req)
    }
}

Saya tidak berpikir enum yang diperiksa waktu kompilasi adalah ide yang bagus. Saya percaya pergi cukup banyak memiliki hak ini sekarang . Alasan saya adalah

  • enum yang diperiksa waktu kompilasi tidak kompatibel ke belakang atau ke depan untuk kasus penambahan atau penghapusan. #18130 menghabiskan upaya yang signifikan untuk bergerak menuju memungkinkan perbaikan kode bertahap; enums akan menghancurkan upaya itu; paket apa pun yang ingin mengubah satu set enum, akan secara otomatis dan paksa menghancurkan semua importir mereka.
  • Bertentangan dengan apa yang diklaim oleh komentar asli, protobuf (untuk alasan khusus itu) sebenarnya tidak memeriksa validitas bidang enum. proto2 menentukan bahwa nilai yang tidak diketahui untuk enum harus diperlakukan seperti bidang yang tidak dikenal dan proto3 bahkan menentukan, bahwa kode yang dihasilkan harus memiliki cara untuk mewakilinya dengan nilai yang disandikan (persis seperti yang dilakukan saat ini dengan enum palsu)
  • Pada akhirnya, itu tidak benar-benar menambah banyak. Anda bisa mendapatkan stringifikasi dengan menggunakan alat stringer. Anda bisa mendapatkan iterasi, dengan menambahkan const MaxValidFoo sentinel (tetapi lihat peringatan di atas. Anda seharusnya tidak memiliki persyaratan). Anda seharusnya tidak memiliki dua const-decls di tempat pertama. Cukup integrasikan alat ke CI Anda yang memeriksanya.
  • Saya tidak percaya tipe lain selain int sebenarnya diperlukan. Alat stringer seharusnya sudah mencakup konversi ke dan dari string; pada akhirnya, kode yang dihasilkan akan setara dengan apa yang akan dihasilkan oleh kompiler (kecuali jika Anda benar-benar menyarankan bahwa perbandingan apa pun pada "string-enum" akan mengulangi byte ...)

Secara keseluruhan, hanya -1 besar bagi saya. Tidak hanya tidak menambahkan apa-apa; sakit secara aktif.

Saya pikir implementasi enum saat ini di Go sangat mudah dan menyediakan pemeriksaan waktu kompilasi yang cukup. Saya sebenarnya mengharapkan semacam enum Rust dengan pencocokan pola dasar, tetapi mungkin merusak jaminan Go1.

Karena enum adalah kasus khusus dari tipe penjumlahan dan kebijaksanaan umum adalah bahwa kita harus menggunakan antarmuka untuk mensimulasikan tipe penjumlahan, jawabannya jelas https://play.golang.org/p/1BvOakvbj2

(jika tidak jelas: ya, itu hanya lelucon—dengan cara programmer klasik, saya akan membahasnya satu per satu).

Dalam semua keseriusan, untuk fitur yang dibahas di utas ini, beberapa alat tambahan akan berguna.

Seperti alat stringer, alat "ranger" dapat menghasilkan yang setara dengan fungsi Iter dalam kode yang saya tautkan di atas.

Sesuatu dapat menghasilkan implementasi {Binary,Text}{Marshaler,Unmarshaler} untuk membuatnya lebih mudah dikirim melalui kabel.

Saya yakin ada banyak hal kecil seperti ini yang akan sangat berguna pada kesempatan tertentu.

Ada beberapa alat pemeriksaan/linter untuk pemeriksaan kelengkapan jenis jumlah yang disimulasikan dengan antarmuka. Tidak ada alasan mengapa tidak ada enum iota yang memberi tahu Anda ketika kasus terlewatkan atau konstanta tidak diketik yang tidak valid digunakan (mungkin seharusnya hanya melaporkan apa pun selain 0?).

Pasti ada ruang untuk perbaikan di bagian depan itu bahkan tanpa perubahan bahasa.

Enum akan melengkapi sistem tipe yang sudah ada. Seperti yang ditunjukkan oleh banyak contoh dalam masalah ini, blok penyusun untuk enum sudah ada. Sama seperti saluran adalah abstraksi tingkat tinggi yang dibangun di atas tipe yang lebih primitif, enum harus dibangun dengan cara yang sama. Manusia arogan, canggung, dan pelupa, mekanisme seperti enum membantu programmer manusia membuat lebih sedikit kesalahan pemrograman.

@bep Saya harus tidak setuju dengan ketiga poin Anda. Enum idiomatik Go sangat mirip dengan enum C, yang tidak memiliki iterasi nilai yang valid, tidak memiliki konversi otomatis ke string, dan tidak harus memiliki identitas yang berbeda.

Iterasi bagus untuk dimiliki, tetapi dalam banyak kasus jika Anda menginginkan iterasi, boleh saja mendefinisikan konstanta untuk nilai pertama dan terakhir. Anda bahkan dapat melakukannya dengan cara yang tidak memerlukan pembaruan saat Anda menambahkan nilai baru, karena iota akan secara otomatis menjadikannya selesai. Situasi di mana dukungan bahasa akan membuat perbedaan yang berarti adalah ketika nilai enum tidak bersebelahan.

Konversi otomatis ke string hanya bernilai kecil: terutama dalam proposal ini, nilai string harus ditulis agar sesuai dengan nilai int, jadi tidak banyak yang bisa diperoleh jika Anda menulis sendiri array nilai string secara eksplisit. Dalam proposal alternatif, itu bisa lebih berharga, tetapi ada kerugian untuk memaksa nama variabel agar sesuai dengan representasi string juga.

Akhirnya, identitas yang berbeda yang saya bahkan tidak yakin adalah fitur yang berguna sama sekali. Enum bukan tipe penjumlahan seperti pada, katakanlah, Haskell. Mereka diberi nama nomor. Menggunakan enum sebagai nilai flag, misalnya, adalah hal biasa. Misalnya, Anda dapat memiliki ReadWriteMode = ReadMode | WriteMode dan ini adalah hal yang berguna. Sangat mungkin untuk juga memiliki nilai lain, misalnya Anda mungkin memiliki DefaultMode = ReadMode . Ini tidak seperti metode apa pun yang dapat menghentikan seseorang untuk menulis const DefaultMode = ReadMode dalam hal apa pun; apa tujuannya untuk mengharuskannya terjadi dalam deklarasi terpisah?

@bep Saya harus tidak setuju dengan ketiga poin Anda. Enum idiomatik Go sangat mirip dengan enum C, yang tidak memiliki iterasi nilai yang valid, tidak memiliki konversi otomatis ke string, dan tidak harus memiliki identitas yang berbeda.

@alercah , tolong jangan tarik idomatic Go ini ke dalam diskusi apa pun sebagai "argumen yang menang"; Go tidak memiliki Enum bawaan, jadi berbicara tentang beberapa idom yang tidak ada, tidak masuk akal.

Go dibangun untuk menjadi C/C++ yang lebih baik atau Java yang kurang bertele -tele, jadi membandingkannya dengan yang terakhir akan lebih masuk akal. Dan Java memang memiliki Enum type ("Jenis enum bahasa pemrograman Java jauh lebih kuat daripada rekan-rekan mereka dalam bahasa lain. "): https://docs.Oracle.com/javase/tutorial/java /javaOO/enum.html

Dan, meskipun Anda mungkin tidak setuju dengan "bagian yang jauh lebih kuat", tipe Java Enum memiliki ketiga fitur yang saya sebutkan.

Saya dapat menghargai argumen bahwa Go lebih ramping, lebih sederhana, dll., dan bahwa beberapa kompromi harus diambil agar tetap seperti ini, dan saya telah melihat beberapa solusi peretasan di utas ini yang berfungsi, tetapi satu set iota int tidak sendirian membuat enum.

Enumerasi dan konversi string otomatis adalah kandidat yang baik untuk fitur 'hasilkan'. Kami sudah memiliki beberapa solusi. Enum Java adalah sesuatu di tengah enum klasik dan tipe sum. Jadi itu adalah desain bahasa yang buruk menurut saya.
Hal tentang Go idiomatik adalah kuncinya, dan saya tidak melihat alasan kuat untuk menyalin semua fitur dari bahasa X ke bahasa Y, hanya karena seseorang mengenalnya.

Jenis enum bahasa pemrograman Java jauh lebih kuat daripada rekan-rekan mereka dalam bahasa lain

Itu benar satu dekade lalu. Lihat implementasi Option in Rust tanpa biaya modern yang didukung oleh jumlah jenis dan pencocokan pola.

Hal tentang Go idiomatik adalah kuncinya, dan saya tidak melihat alasan kuat untuk menyalin semua fitur dari bahasa X ke bahasa Y, hanya karena seseorang mengenalnya.

Perhatikan bahwa saya tidak terlalu setuju dengan kesimpulan yang diberikan di sini, tetapi penggunaan _ idiomatik Go_ menempatkan Go di atas alas yang artistik. Kebanyakan pemrograman perangkat lunak cukup membosankan dan praktis. Dan seringkali Anda hanya perlu mengisi kotak drop-down dengan enum ...

//go:generate enumerator Foo,Bar
Ditulis sekali, tersedia di mana-mana. Perhatikan bahwa contohnya adalah abstrak.

@bep Saya pikir Anda salah membaca komentar asli. "Go idiomatic enums" seharusnya merujuk pada konstruksi saat ini menggunakan tipe Foo int + const-decl + iota, saya percaya, bukan untuk mengatakan "apa pun yang Anda usulkan tidak idiomatik".

@rsc Mengenai label Go2 , itu bertentangan dengan alasan saya mengajukan proposal ini. #19412 adalah proposal tipe jumlah penuh, yang merupakan superset yang lebih kuat daripada proposal enum sederhana saya di sini, dan saya lebih suka melihatnya di Go2. Dari sudut pandang saya, kemungkinan terjadinya Go2 dalam 5 tahun ke depan sangat kecil, dan saya lebih suka melihat sesuatu terjadi dalam jangka waktu yang lebih singkat.

Jika proposal saya tentang kata kunci cadangan baru enum tidak mungkin untuk BC, masih ada cara lain untuk mengimplementasikannya, apakah itu integrasi bahasa lengkap atau alat yang dibangun ke dalam go vet . Seperti yang saya nyatakan sebelumnya, saya tidak khusus tentang sintaks, tetapi saya sangat percaya bahwa itu akan menjadi tambahan yang berharga untuk Go hari ini tanpa menambahkan beban kognitif yang signifikan bagi pengguna baru.

Kata kunci baru tidak dimungkinkan sebelum Go 2. Ini jelas merupakan pelanggaran terhadap jaminan kompatibilitas Go 1.

Secara pribadi, saya belum melihat argumen yang meyakinkan untuk enum, atau, dalam hal ini, untuk tipe jumlah, bahkan untuk Go 2. Saya tidak mengatakan itu tidak mungkin terjadi. Tetapi salah satu tujuan dari bahasa Go adalah kesederhanaan bahasa. Tidak cukup hanya fitur bahasa yang berguna; semua fitur bahasa berguna--jika tidak berguna, tidak ada yang akan mengusulkannya. Untuk menambahkan fitur ke Go, fitur tersebut harus memiliki kasus penggunaan yang cukup menarik untuk membuatnya layak untuk memperumit bahasa. Kasus penggunaan yang paling menarik adalah kode yang tidak dapat ditulis tanpa fitur, setidaknya sekarang tanpa kecanggungan yang besar.

Saya ingin melihat enum di Go. Saya terus-menerus menemukan diri saya ingin membatasi API saya yang terbuka (atau bekerja dengan API terbatas di luar aplikasi saya) di mana ada sejumlah input yang valid. Bagi saya, ini adalah tempat yang sempurna untuk enum.

Misalnya, saya bisa membuat aplikasi klien yang terhubung ke semacam API gaya RPC, dan memiliki serangkaian tindakan/opcode tertentu. Saya dapat menggunakan const s untuk ini, tetapi tidak ada yang mencegah siapa pun (termasuk saya sendiri!) untuk mengirimkan kode yang tidak valid.

Di sisi lain, jika saya menulis sisi server untuk API yang sama, alangkah baiknya jika saya dapat menulis pernyataan sakelar di enum, yang akan menimbulkan kesalahan kompiler (atau setidaknya beberapa go vet warnings) jika semua kemungkinan nilai enum tidak dicentang (atau setidaknya ada default: ).

Saya pikir ini (enums) adalah area yang benar-benar dimiliki Swift.

Saya bisa membuat aplikasi klien yang terhubung ke semacam API gaya RPC, dan memiliki serangkaian tindakan/opcode tertentu. Saya dapat menggunakan consts untuk ini, tetapi tidak ada yang mencegah siapa pun (termasuk saya sendiri!) untuk hanya mengirim kode yang tidak valid.

Ini adalah ide yang mengerikan untuk dipecahkan dengan enum. Ini berarti Anda sekarang tidak akan pernah dapat menambahkan nilai enum baru, karena tiba-tiba RPC mungkin gagal atau data Anda menjadi tidak dapat dibaca saat rollback. Alasan proto3 mengharuskan enum-code yang dihasilkan mendukung nilai "kode tidak dikenal" adalah karena ini adalah pelajaran yang dipelajari dengan susah payah (bandingkan dengan bagaimana proto2 menyelesaikan ini, mana yang lebih baik, tetapi masih sangat buruk). Anda ingin aplikasi dapat menangani kasus ini dengan anggun.

@Merovius Saya menghargai pendapat Anda, tetapi dengan sopan tidak setuju. Memastikan hanya nilai valid yang digunakan adalah salah satu kegunaan utama enum.

Enum tidak tepat untuk setiap situasi, tetapi sangat bagus untuk beberapa situasi! Pembuatan versi dan penanganan kesalahan yang tepat harus dapat menangani nilai-nilai baru di sebagian besar situasi.

Untuk menangani proses eksternal yang memiliki status uh-oh adalah suatu keharusan, tentu saja.

Dengan enums (atau tipe penjumlahan yang lebih umum dan berguna), Anda dapat menambahkan kode "tidak diketahui" eksplisit ke jumlah/enum yang dipaksa oleh kompiler untuk Anda tangani (atau hanya menangani situasi itu sepenuhnya di titik akhir jika yang dapat Anda lakukan hanyalah log dan lanjutkan ke permintaan berikutnya).

Saya menemukan tipe jumlah lebih berguna untuk di dalam suatu proses ketika saya tahu memiliki X kasus yang saya tahu harus saya tangani. Untuk X kecil itu tidak sulit untuk dikelola, tetapi, untuk X besar, saya menghargai kompiler yang meneriaki saya, terutama ketika refactoring.

Di seberang batas API, kasus penggunaan lebih sedikit, dan orang harus selalu berbuat salah di sisi ekstensibilitas, tetapi kadang-kadang Anda memiliki sesuatu yang benar-benar hanya dapat menjadi salah satu dari X hal, seperti dengan AST atau lebih banyak contoh sepele seperti "hari minggu" nilai di mana kisaran cukup banyak diselesaikan pada saat ini (hingga pilihan sistem kalender).

@jimmyfrasche Saya mungkin memberi Anda Hari dalam Seminggu, tetapi tidak AST. Tata bahasa berkembang. Apa yang mungkin tidak valid hari ini, bisa sepenuhnya valid besok dan itu mungkin melibatkan penambahan tipe simpul baru ke AST. Dengan tipe sum yang diperiksa oleh kompiler, ini tidak akan mungkin tanpa kerusakan.

Dan saya tidak mengerti mengapa ini tidak bisa diselesaikan hanya dengan pemeriksaan dokter hewan; memberi Anda pemeriksaan statis yang sangat cocok untuk kasus yang lengkap dan memberi saya kemungkinan perbaikan bertahap.

Saya bermain-main dengan mengimplementasikan klien untuk API server. Beberapa argumen dan nilai kembalian adalah enum di API. Ada total 45 jenis enum.

Menggunakan konstanta enumerasi di Go tidak layak dalam kasus saya karena beberapa nilai untuk tipe enum yang berbeda memiliki nama yang sama. Pada contoh di bawah ini, Destroy muncul dua kali sehingga compiler akan mengeluarkan error Destroy redeclared in this block .

type TaskAllowedOperations int
const (
    _ TaskAllowedOperations = iota
    Cancel
    Destroy
)

type OnNormalExit int
const (
    _ OnNormalExit = iota
    Destroy
    Restart
)

Oleh karena itu saya perlu datang dengan representasi yang berbeda. Idealnya salah satu yang memungkinkan IDE untuk menunjukkan nilai yang mungkin untuk jenis tertentu sehingga pengguna klien akan lebih mudah menggunakannya. Memiliki enum sebagai warga negara kelas satu di Go akan memuaskan itu.

@kongslund Saya tahu ini bukan implementasi yang sempurna, tetapi saya baru saja membuat pembuat kode yang mungkin menarik bagi Anda. Itu hanya mengharuskan Anda mendeklarasikan enum Anda dalam komentar di atas deklarasi tipe dan akan menghasilkan sisanya untuk Anda.

// ENUM(_, Cancel, Destroy)
type TaskAllowedOperations int

// ENUM(_, Destroy, Restart)
type OnNormalExit int

Akan menghasilkan

const(
  _ TaskAllowedOperations = iota
  TaskAllowedOperationsCancel
  TaskAllowedOperationsDestroy
)

const(
  _ OnNormalExit = iota
  OnNormalExitDestroy
  OnNormalExitRestart
)

Bagian yang lebih baik adalah itu akan menghasilkan String() metode yang mengecualikan awalan di dalamnya, memungkinkan Anda untuk mengurai "Destroy" sebagai TaskAllowedOperations atau OnNormalExit .

https://github.com/abice/go-enum

Sekarang stekernya sudah lepas...

Saya pribadi tidak keberatan bahwa enum tidak dimasukkan sebagai bagian dari bahasa go, yang bukan perasaan asli saya terhadap masalah tersebut. Ketika pertama kali datang untuk pergi, saya sering mengalami reaksi bingung mengapa begitu banyak pilihan dibuat. Tetapi setelah menggunakan bahasa, senang memiliki kesederhanaan yang dipatuhi, dan jika sesuatu yang ekstra diperlukan, kemungkinan besar orang lain juga membutuhkannya dan membuat paket yang luar biasa untuk membantu mengatasi masalah khusus itu. Menjaga jumlah cruft untuk kebijaksanaan saya.

Banyak poin yang valid telah diangkat dalam diskusi ini, beberapa mendukung enum dan juga banyak yang menentangnya (setidaknya sejauh proposal mengatakan sesuatu tentang apa itu "enum" di tempat pertama). Beberapa hal yang mengganjal bagi saya:

  • Contoh pengantar (Enums in Go hari ini) menyesatkan: Kode itu dibuat dan hampir tidak ada yang akan menulis kode Go seperti itu dengan tangan. Faktanya, saran (Seperti apa tampilannya dengan dukungan bahasa) jauh lebih dekat dengan apa yang sebenarnya sudah kami lakukan di Go.

  • @jediorange menyebutkan bahwa Swift "benar-benar mendapatkan (enums) benar": Bagaimanapun, tetapi Swift enum adalah binatang yang sangat rumit, mencampur semua jenis konsep bersama-sama. Di Go kami sengaja menghindari mekanisme yang tumpang tindih dengan fitur bahasa lain dan sebagai imbalannya mendapatkan lebih banyak ortogonalitas. Konsekuensi bagi seorang programmer adalah dia tidak harus membuat keputusan fitur mana yang akan digunakan: enum atau kelas, atau tipe jumlah (jika kita memilikinya), atau antarmuka.

  • Poin @ianlancetaylor tentang kegunaan fitur bahasa tidak boleh dianggap enteng. Ada trilyun fitur yang berguna; pertanyaannya adalah mana yang benar-benar menarik dan sepadan dengan biayanya (dengan kompleksitas ekstra bahasa dan dengan demikian keterbacaan, dan implementasi).

  • Sebagai poin minor, konstanta yang didefinisikan iota di Go tentu saja tidak terbatas pada int. Selama konstanta, mereka dibatasi untuk (mungkin bernama) tipe dasar (termasuk float, boolean, string: https://play.golang.org/p/lhd3jqqg5z).

  • @merovius membuat poin bagus tentang batasan pemeriksaan waktu kompilasi (statis!). Saya sangat ragu bahwa enumerasi yang tidak dapat diperpanjang cocok dalam rangkaian di mana ekstensi diinginkan atau diharapkan (setiap permukaan API yang berumur panjang berkembang dari waktu ke waktu).

Yang membawa saya ke beberapa pertanyaan tentang proposal ini yang saya yakini perlu dijawab sebelum ada kemajuan yang berarti:

1) Apa harapan aktual untuk enum seperti yang diusulkan? @bep menyebutkan enumerabilitas, iterabilitas, representasi string, identitas. Apakah ada lagi? Apakah ada yang kurang?

2) Dengan asumsi daftar di 1), dapatkah enum diperpanjang? Jika demikian, bagaimana? (dalam paket yang sama? paket lain?) Kalau tidak bisa diperpanjang kenapa tidak? Mengapa itu tidak menjadi masalah dalam praktik?

3) Namespace: Di Swift, tipe enum memperkenalkan namespace baru. Ada mesin yang signifikan (gula sintaksis, pengurangan jenis) sehingga nama namespace tidak harus diulang di mana-mana. Misalnya, untuk nilai enum dari bulan enum, dalam konteks yang tepat, seseorang dapat menulis .Januari daripada Bulan.Januari (atau lebih buruk, Paket Saya.Bulan.Januari). Apakah ruang nama enum diperlukan? Jika demikian, bagaimana namespace enum diperpanjang? Jenis gula sintaksis apa yang diperlukan untuk membuat ini berfungsi dalam praktik?

4) Apakah nilai enum konstan? Nilai yang tidak berubah?

5) Jenis operasi apa yang mungkin dilakukan pada nilai enum (katakanlah, selain iterasi): Bisakah saya memindahkan satu ke depan, satu ke belakang? Apakah ini memerlukan fungsi atau operator bawaan tambahan? (Tidak semua iterasi mungkin berurutan). Apa yang terjadi jika seseorang bergerak maju melewati nilai enum terakhir? Apakah itu kesalahan runtime?

(Saya telah mengoreksi ungkapan saya untuk paragraf berikutnya di https://github.com/golang/go/issues/19814#issuecomment-322771922. Mohon maaf atas pilihan kata yang ceroboh di bawah ini.)

Tanpa mencoba menjawab pertanyaan-pertanyaan ini, proposal ini tidak ada artinya ("Saya ingin enum yang melakukan apa yang saya inginkan" bukanlah proposal).

Tanpa mencoba menjawab pertanyaan-pertanyaan ini, proposal ini tidak ada artinya

@griesemer Anda memiliki serangkaian poin/pertanyaan yang bagus -- tetapi memberi label proposal ini tidak berarti karena tidak menjawab pertanyaan-pertanyaan ini tidak masuk akal. Batas kontribusi ditetapkan tinggi dalam proyek ini, tetapi harus diizinkan untuk _mengusulkan sesuatu_ tanpa memiliki gelar PhD dalam kompiler, dan proposal tidak harus berupa desain _siap untuk diimplementasikan_.

  • Pergi membutuhkan proposal ini karena memulai diskusi yang sangat dibutuhkan => nilai dan makna
  • Jika juga mengarah pada proposal #21473 => nilai dan makna

Contoh pengantar (Enums in Go hari ini) menyesatkan: Kode itu dibuat dan hampir tidak ada yang akan menulis kode Go seperti itu dengan tangan. Faktanya, saran (Seperti apa tampilannya dengan dukungan bahasa) jauh lebih dekat dengan apa yang sebenarnya sudah kami lakukan di Go.

@griesemer saya harus tidak setuju. Saya seharusnya tidak meninggalkan huruf besar penuh dalam nama variabel Go, tetapi ada banyak tempat di mana kode tulisan tangan terlihat hampir identik dengan saran saya, yang ditulis oleh Googler yang saya hormati di komunitas Go. Kami cukup sering mengikuti pola yang sama dalam basis kode kami. Berikut ini contoh yang diambil dari perpustakaan Google Cloud Go.

// ACLRole is the level of access to grant.
type ACLRole string

const (
    RoleOwner  ACLRole = "OWNER"
    RoleReader ACLRole = "READER"
    RoleWriter ACLRole = "WRITER"
)

Mereka menggunakan konstruksi yang sama di banyak tempat.
https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/bigquery/table.go#L78 -L116
https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/storage/acl.go#L27 -L49

Ada beberapa diskusi kemudian tentang bagaimana Anda dapat membuat segalanya lebih singkat jika Anda baik-baik saja menggunakan iota , yang dapat berguna dengan sendirinya, tetapi untuk kasus penggunaan terbatas. Lihat komentar saya sebelumnya untuk lebih jelasnya. https://github.com/golang/go/issues/19814#issuecomment -290948187

@bep Titik wajar; Saya minta maaf atas pilihan kata-kata saya yang ceroboh. Mari saya coba lagi, semoga ungkapan paragraf terakhir saya di atas lebih sopan dan lebih jelas kali ini:

Agar dapat membuat kemajuan yang berarti, saya yakin para pendukung proposal ini harus mencoba sedikit lebih tepat tentang apa yang mereka yakini sebagai fitur penting enum (misalnya dengan menjawab beberapa pertanyaan di https://github. com/golang/go/issues/19814#issuecomment-322752526). Dari pembahasan sejauh ini fitur-fitur yang diinginkan hanya dijelaskan dengan cukup samar.

Mungkin sebagai langkah pertama, akan sangat berguna untuk memiliki studi kasus yang menunjukkan bagaimana Go yang ada menjadi pendek (secara signifikan) dan bagaimana enum akan menyelesaikan masalah dengan lebih baik/lebih cepat/lebih jelas, dll. Lihat juga pembicaraan luar biasa @rsc di Gophercon tentang perubahan bahasa Go2.

@derekperkins Saya akan menyebut definisi konstan (diketik) itu, bukan enum. Saya menduga ketidaksepakatan kami disebabkan oleh pemahaman yang berbeda tentang apa yang seharusnya menjadi "enum", maka pertanyaan saya di atas.

(https://github.com/golang/go/issues/19814#issuecomment-322774830 saya sebelumnya tentu saja harus pergi ke @derekperkins , bukan @ derekparker. Autocomplete mengalahkan saya.)

Dilihat dari komentar @derekperkins , dan sebagian menjawab pertanyaan saya sendiri, saya menyimpulkan bahwa "enum" Go setidaknya harus memiliki kualitas berikut:

  • kemampuan untuk mengelompokkan sekumpulan nilai di bawah tipe (baru)
  • membuatnya mudah untuk mendeklarasikan nama (dan nilai yang sesuai, jika ada) dengan tipe itu, dengan overhead sintaksis atau boilerplate yang minimal
  • kemampuan untuk beralih melalui nilai-nilai ini dalam urutan deklarasi menaik

Apakah itu terdengar benar? Jika demikian, apa lagi yang perlu ditambahkan ke daftar ini?

Pertanyaan Anda semuanya bagus.

Apa harapan sebenarnya untuk enum seperti yang diusulkan? @bep menyebutkan enumerabilitas, iterabilitas, representasi string, identitas. Apakah ada lagi? Apakah ada yang kurang?

Dengan asumsi daftar di 1), dapatkah enum diperpanjang? Jika demikian, bagaimana? (dalam paket yang sama? paket lain?) Kalau tidak bisa diperpanjang kenapa tidak? Mengapa itu tidak menjadi masalah dalam praktik?

Saya tidak berpikir enum dapat diperpanjang karena dua alasan:

  1. Enum harus mewakili rentang penuh nilai yang dapat diterima, jadi memperluasnya tidak masuk akal.
  2. Sama seperti tipe Go normal yang tidak dapat diperluas dalam paket eksternal, ini mempertahankan mekanisme dan harapan pengembang yang sama

Namespace: Di Swift, tipe enum memperkenalkan namespace baru. Ada mesin yang signifikan (gula sintaksis, pengurangan jenis) sehingga nama namespace tidak harus diulang di mana-mana. Misalnya, untuk nilai enum dari bulan enum, dalam konteks yang tepat, seseorang dapat menulis .Januari daripada Bulan.Januari (atau lebih buruk, Paket Saya.Bulan.Januari). Apakah ruang nama enum diperlukan? Jika demikian, bagaimana namespace enum diperpanjang? Jenis gula sintaksis apa yang diperlukan untuk membuat ini berfungsi dalam praktik?

Saya mengerti bagaimana penspasian nama muncul, karena semua contoh yang saya sebutkan awalan dengan nama tipe. Meskipun saya tidak akan menentang jika seseorang sangat ingin menambahkan penspasian nama, saya pikir itu di luar cakupan proposal ini. Awalan cocok dengan sistem saat ini dengan baik.

Apakah nilai enum konstan? Nilai yang tidak berubah?

Saya akan berpikir konstanta.

Jenis operasi apa yang mungkin dilakukan pada nilai enum (katakanlah, selain iterasi): Bisakah saya memindahkan satu ke depan, satu ke belakang? Apakah ini memerlukan fungsi atau operator bawaan tambahan? (Tidak semua iterasi mungkin berurutan). Apa yang terjadi jika seseorang bergerak maju melewati nilai enum terakhir? Apakah itu kesalahan runtime?

Saya akan default ke praktik Go standar untuk irisan/array (bukan peta). Nilai enum akan dapat diubah berdasarkan urutan deklarasi. Minimal, akan ada dukungan jangkauan. Saya condong dari membiarkan enum diakses melalui indeks, tetapi tidak terlalu memikirkannya. Tidak mendukung yang seharusnya menghilangkan potensi kesalahan runtime.

Akan ada kesalahan runtime baru (panik?) yang disebabkan oleh penetapan nilai yang tidak valid ke enum, apakah itu melalui penugasan langsung atau casting tipe.

Jika saya meringkas ini dengan benar maka nilai enum seperti yang Anda usulkan seperti konstanta yang diketik (dan seperti konstanta mereka mungkin memiliki nilai konstanta yang ditentukan pengguna) tetapi:

  • mereka juga mendefinisikan tipe enum yang terkait dengan nilai enum (jika tidak, mereka hanya konstanta) dalam deklarasi yang sama
  • tidak mungkin untuk memberikan/membuat nilai enum dari tipe enum yang ada di luar deklarasinya
  • mungkin untuk mengulanginya

Apakah itu terdengar benar? (Ini akan cocok dengan pendekatan klasik yang diambil bahasa terhadap enum, dipelopori sekitar 45 tahun yang lalu oleh Pascal).

Ya, itulah yang saya usulkan.

Bagaimana dengan pernyataan-switch? AIUI yang menjadi salah satu pendorong utama proposal tersebut.

Mampu mengaktifkan enumerasi tersirat, saya pikir, karena pada dasarnya Anda dapat mengaktifkan apa saja. Saya suka bahwa Swift memiliki kesalahan jika Anda belum sepenuhnya memenuhi enum di sakelar Anda, tetapi itu bisa ditangani oleh dokter hewan

@jediorange Saya secara khusus mengacu pada pertanyaan bagian terakhir itu, apakah harus ada pemeriksaan kelengkapan atau tidak (untuk menjaga proposal tetap lengkap). "Tidak", tentu saja, adalah jawaban yang sangat bagus.

Pesan asli edisi ini menyebutkan protobufs sebagai motivator. Saya ingin secara eksplisit menyebutkan bahwa dengan semantik seperti yang diberikan sekarang, kompiler protobuf perlu membuat kasus tambahan "tidak dikenal" untuk enum apa pun (menyiratkan beberapa skema pengurai nama untuk mencegah tabrakan). Itu juga perlu menambahkan bidang tambahan ke setiap struct yang dihasilkan menggunakan enum (sekali lagi, mengubah nama dalam beberapa cara), jika nilai enum yang didekodekan tidak dalam rentang yang dikompilasi. Sama seperti yang saat ini dilakukan untuk Java . Atau, mungkin lebih mungkin, terus menggunakan int s.

@Merovius Proposal asli saya menyebutkan protobuf sebagai contoh, bukan sebagai motivator utama untuk proposal tersebut. Anda mengemukakan poin bagus tentang integrasi itu. Saya pikir itu mungkin harus diperlakukan sebagai perhatian ortogonal. Sebagian besar kode yang saya lihat dikonversi dari tipe protobuf yang dihasilkan menjadi struct level aplikasi, lebih memilih untuk menggunakannya secara internal. Masuk akal bagi saya bahwa protobuf dapat terus tidak berubah, dan jika pembuat aplikasi ingin mengubahnya menjadi enum Go, mereka dapat menangani kasus tepi yang Anda kemukakan dalam proses konversi.

@derekperkins Beberapa pertanyaan lagi:

  • Berapa nilai nol untuk variabel tipe enum yang tidak diinisialisasi secara eksplisit? Saya berasumsi itu tidak boleh nol secara umum (yang memperumit alokasi/inisialisasi memori).

  • Bisakah kita melakukan aritmatika terbatas dengan nilai enum? Misalnya, dalam Pascal (di mana saya memprogram sekali, jauh di masa lalu), secara mengejutkan sering kali diperlukan untuk mengulangi langkah > 1. Dan terkadang seseorang ingin menghitung nilai enum.

  • Mengenai iterasi, mengapa dukungan go generate iteration (dan stringify) yang dihasilkan tidak cukup baik?

Berapa nilai nol untuk variabel tipe enum yang tidak diinisialisasi secara eksplisit? Saya berasumsi itu tidak boleh nol secara umum (yang memperumit alokasi/inisialisasi memori).

Seperti yang saya sebutkan di proposal awal, ini adalah salah satu keputusan yang lebih sulit untuk dibuat. Jika urutan definisi penting untuk iterasi, maka saya pikir juga masuk akal jika nilai yang ditentukan pertama menjadi default.

Bisakah kita melakukan aritmatika terbatas dengan nilai enum? Misalnya, dalam Pascal (di mana saya memprogram sekali, jauh di masa lalu), secara mengejutkan sering kali diperlukan untuk mengulangi langkah > 1. Dan terkadang seseorang ingin menghitung nilai enum.

Apakah Anda menggunakan enum berbasis numerik atau string, apakah itu berarti bahwa semua enum memiliki indeks berbasis nol implisit? Alasan yang saya sebutkan sebelumnya bahwa saya condong ke arah hanya mendukung range iterasi dan bukan berbasis indeks, adalah karena tidak mengekspos implementasi yang mendasarinya, yang dapat menggunakan array atau peta atau apa pun di bawahnya. Saya tidak mengantisipasi perlu mengakses enum melalui indeks, tetapi jika Anda memiliki alasan mengapa itu akan bermanfaat, saya rasa tidak ada alasan untuk melarangnya.

Mengenai iterasi, mengapa dukungan go generate iteration (dan stringify) yang dihasilkan tidak cukup baik?

Iterasi bukan kasus penggunaan utama saya secara pribadi, meskipun saya pikir itu menambah nilai pada proposal. Jika itu adalah faktor pendorongnya, mungkin go generate sudah cukup. Itu tidak membantu menjamin keamanan nilai. Argumen Stringer() mengasumsikan bahwa nilai mentahnya adalah iota atau int atau jenis lain yang mewakili nilai "nyata". Anda juga harus membuat (Un)MarshalJSON , (Un)MarshalBinary , Scanner/Valuer dan metode serialisasi lainnya yang mungkin Anda gunakan untuk memastikan bahwa nilai Stringer digunakan untuk berkomunikasi vs apa pun yang digunakan Go secara internal.

@griesemer Saya pikir saya mungkin belum sepenuhnya menjawab pertanyaan Anda tentang ekstensibilitas enum, setidaknya dalam hal menambah/menghapus nilai. Memiliki kemampuan untuk mengeditnya adalah bagian penting dari proposal ini.

Dari @Merovius https://github.com/golang/go/issues/19814#issuecomment -290969864

paket apa pun yang ingin mengubah satu set enum, akan secara otomatis dan paksa menghancurkan semua importirnya

Saya tidak melihat bagaimana ini berbeda dari perubahan API yang melanggar lainnya. Terserah pembuat paket untuk menangani BC dengan hormat, sama seperti jika jenis, fungsi, atau tanda tangan fungsi berubah.

Dari perspektif implementasi, akan cukup rumit untuk mendukung tipe yang nilai defaultnya tidak semuanya nol. Tidak ada tipe seperti itu hari ini. Membutuhkan fitur seperti itu harus dihitung sebagai tanda terhadap ide ini.

Satu-satunya alasan bahasa memerlukan make untuk membuat saluran adalah untuk mempertahankan fitur ini untuk jenis saluran. Jika tidak, make dapat menjadi opsional, hanya digunakan untuk mengatur ukuran buffer saluran atau untuk menetapkan saluran baru ke variabel yang ada.

@derekperkins Sebagian besar perubahan API lainnya dapat diatur untuk perbaikan bertahap. Saya sangat merekomendasikan untuk membaca deskripsi Russ Cox , itu membuat banyak hal menjadi sangat jelas.

Enum terbuka (seperti konstruksi const+iota saat ini) memungkinkan perbaikan bertahap, dengan (misalnya) a) mendefinisikan nilai baru tanpa menggunakannya, b) memperbarui dependensi terbalik untuk menangani nilai baru, c) mulai menggunakan nilai. Atau, jika Anda ingin menghapus nilai, a) hentikan penggunaan nilai, b) perbarui dependensi terbalik untuk tidak menyebutkan nilai yang akan dihapus, c) hapus nilai.

Dengan enum tertutup (pemeriksaan kompiler untuk kelengkapan) ini tidak mungkin. Jika Anda menghapus penanganan nilai atau mendefinisikan yang baru, kompiler akan segera mengeluh tentang kasus sakelar yang hilang. Dan Anda tidak dapat menambahkan penanganan nilai sebelum mendefinisikannya.

Pertanyaannya bukan tentang apakah perubahan individu dapat dianggap melanggar (mereka dapat, secara terpisah), tetapi tentang apakah ada urutan komit yang tidak melanggar atas basis kode terdistribusi yang tidak rusak.

Dari perspektif implementasi, akan cukup rumit untuk mendukung tipe yang nilai defaultnya tidak semuanya nol. Tidak ada tipe seperti itu hari ini. Membutuhkan fitur seperti itu harus dihitung sebagai tanda terhadap ide ini.

@ianlancetaylor Saya pasti tidak akan dapat berbicara dengan implementasi penuh, tetapi jika enum diimplementasikan sebagai array berbasis 0 (yang sepertinya disukai @griesemer ), maka 0 seperti indeks itu bisa berfungsi ganda sebagai "semua-bit-nol".

Dengan enum tertutup (pemeriksaan kompiler untuk kelengkapan) ini tidak mungkin.

@Merovius Jika kelengkapan diperiksa oleh go vet atau alat serupa seperti yang disarankan oleh @jediorange vs ditegakkan oleh kompiler, apakah itu akan meringankan kekhawatiran Anda?

@derekperkins Tentang bahayanya, ya. Bukan tentang kurangnya kegunaan mereka. Masalah version-skew yang sama juga terjadi untuk sebagian besar kasus penggunaan yang biasanya dipertimbangkan (syscalls, protokol jaringan, format file, objek bersama…). Ada alasan mengapa proto3 membutuhkan enum terbuka dan proto2 tidak - ini adalah pelajaran dari banyak pemadaman dan insiden korupsi data. Padahal Google sudah cukup berhati-hati untuk menghindari versi miring. Dari sudut pandang saya, enum terbuka dengan kasing default adalah solusi yang tepat. Dan terlepas dari dugaan keamanan terhadap nilai-nilai yang tidak valid, mereka tidak benar-benar membawa banyak hal, dari apa yang saya tahu.

Semua itu dikatakan, saya bukan penentu.

@derekperkins Di https://github.com/golang/go/issues/19814#issuecomment -322818206 Anda mengonfirmasi bahwa (dari sudut pandang Anda):

  • deklarasi enum mendeklarasikan tipe enum bersama dengan nilai enum bernama (konstanta)
  • mungkin untuk mengulanginya
  • tidak ada nilai yang dapat ditambahkan ke enum di luar deklarasinya
    Dan kemudian: Peralihan enum harus (atau mungkin tidak) lengkap (tampaknya kurang penting)

Di https://github.com/golang/go/issues/19814#issuecomment -322895247 Anda mengatakan bahwa:

  • nilai yang ditentukan pertama mungkin harus menjadi nilai default (nol) (perhatikan bahwa ini tidak masalah untuk iterasi, itu penting untuk inisialisasi variabel enum)
  • iterasi bukanlah motivasi utama Anda

Dan di https://github.com/golang/go/issues/19814#issuecomment -322903714 Anda mengatakan "kemampuan untuk mengeditnya adalah bagian penting dari proposal ini".

Saya bingung: Jadi iterasi bukan motivator utama, oke. Itu meninggalkan minimal deklarasi enum dari nilai enum yang merupakan konstanta, dan mereka tidak dapat diperpanjang di luar deklarasi. Tetapi sekarang Anda mengatakan bahwa kemampuan untuk mengeditnya itu penting. Apa artinya? Tentunya bukan karena mereka dapat diperpanjang (itu akan menjadi kontradiksi). Apakah mereka variabel? (Tapi kemudian mereka bukan konstanta).

Di https://github.com/golang/go/issues/19814#issuecomment -322903714 Anda mengatakan bahwa enum dapat diimplementasikan sebagai array berbasis 0. Ini menunjukkan bahwa deklarasi enum memperkenalkan tipe baru bersama dengan daftar nama enum yang diurutkan yang merupakan indeks konstan berbasis 0 ke dalam larik nilai enum (yang ruangnya dicadangkan secara otomatis). Apakah itu yang Anda maksud? Jika demikian, mengapa tidak cukup mendeklarasikan array ukuran tetap dan daftar indeks konstanta yang menyertainya? Pemeriksaan batas array akan secara otomatis memastikan bahwa Anda tidak dapat "memperpanjang" rentang enum, dan iterasi sudah dimungkinkan.

Apa yang saya lewatkan?

Saya bingung: Jadi iterasi bukan motivator utama, oke.

Saya punya alasan sendiri mengapa saya menginginkan enum, sementara juga mencoba mempertimbangkan apa yang orang lain di utas ini, termasuk @bep dan lainnya, telah nyatakan sebagai bagian penting dari proposal.

Itu meninggalkan minimal deklarasi enum dari nilai enum yang merupakan konstanta, dan mereka tidak dapat diperpanjang di luar deklarasi. Tetapi sekarang Anda mengatakan bahwa kemampuan untuk mengeditnya itu penting. Apa artinya? Tentunya bukan karena mereka dapat diperpanjang (itu akan menjadi kontradiksi). Apakah mereka variabel? (Tapi kemudian mereka bukan konstanta).

Ketika saya mengatakan untuk mengeditnya, menurut @Merovius mereka adalah enum terbuka. Konstanta pada waktu pembuatan, tetapi tidak dikunci selamanya.

Di #19814 (komentar) Anda mengatakan bahwa enum dapat diimplementasikan sebagai array berbasis 0.

Ini hanya saya yang berspekulasi di luar gaji saya tentang bagaimana saya membayangkan itu dapat diterapkan di belakang layar, berdasarkan https://github.com/golang/go/issues/19814#issuecomment -322884746 dan https://github.com/golang/go/issues/19814#issuecomment Anda -322884746 dan https @ianlancetaylor Anda : //github.com/golang/go/issues/19814#issuecomment -322899668

"Bisakah kita melakukan aritmatika terbatas dengan nilai enum? Misalnya, dalam Pascal (di mana saya memprogram sekali, jauh ketika), secara mengejutkan sering diperlukan untuk mengulangi dalam langkah > 1. Dan terkadang seseorang ingin menghitung nilai enum."

Saya tidak tahu bagaimana Anda berencana melakukan itu untuk enum non-integer, maka pertanyaan saya tentang apakah aritmatika itu akan mengharuskan setiap anggota enum untuk secara implisit diberi indeks berdasarkan urutan deklarasi.

Dari perspektif implementasi, akan cukup rumit untuk mendukung tipe yang nilai defaultnya tidak semuanya nol. Tidak ada tipe seperti itu hari ini. Membutuhkan fitur seperti itu harus dihitung sebagai tanda terhadap ide ini.

Sekali lagi, saya tidak tahu cara kerja kompiler, jadi saya hanya mencoba melanjutkan percakapan. Pada akhirnya, saya tidak mencoba mengusulkan sesuatu yang radikal. Seperti yang Anda sebutkan sebelumnya, "Ini akan cocok dengan pendekatan klasik yang diambil bahasa terhadap enum, dipelopori sekitar 45 tahun yang lalu oleh Pascal", dan itu sesuai dengan tagihan.

Kepada siapa pun yang telah menyatakan minatnya, jangan ragu untuk ikut serta.

Pertanyaan lain adalah apakah seseorang dapat menggunakan enum ini untuk mengindeks ke dalam array atau irisan. Irisan seringkali merupakan cara yang sangat efisien dan ringkas untuk mewakili pemetaan nilai enum-> dan membutuhkan peta akan sangat disayangkan, saya pikir.

@derekperkins Oke, saya khawatir itu membuat kita (atau setidaknya saya) kembali ke titik awal: Apa masalah yang Anda coba selesaikan? Apakah Anda hanya menginginkan cara yang lebih baik untuk menulis apa yang saat ini kami lakukan dengan konstanta dan mungkin sedikit saja (dan untuk itu kami menggunakan go generate untuk mendapatkan representasi string)? Artinya, beberapa gula sintaksis untuk notasi yang Anda (mungkin) anggap terlalu memberatkan? (Itu jawaban yang bagus, hanya mencoba untuk mengerti.)

Anda menyebutkan bahwa Anda memiliki alasan sendiri untuk menginginkannya, mungkin Anda bisa menjelaskan sedikit lebih banyak apa alasan itu. Contoh yang Anda berikan di awal tidak masuk akal bagi saya, tetapi saya mungkin melewatkan sesuatu.

Seperti yang terjadi, setiap orang memiliki pemahaman yang sedikit berbeda tentang apa yang dibutuhkan oleh proposal ("enums") ini, seperti yang telah menjadi jelas dari berbagai tanggapan: Ada banyak kemungkinan antara enum Pascal dan Swift enum. Kecuali Anda (atau orang lain) menjelaskan dengan sangat jelas apa yang diusulkan (saya tidak meminta implementasi, ingatlah), akan sulit untuk membuat kemajuan yang berarti atau bahkan hanya memperdebatkan manfaat dari proposal ini.

Apakah itu masuk akal?

@griesemer Ini benar-benar masuk akal dan saya mengerti bilah yang harus dilewati yang dibicarakan @rsc di Gophercon. Anda jelas memiliki pemahaman yang jauh lebih dalam daripada yang pernah saya lakukan. Di #21473, Anda menyebutkan bahwa iota for vars tidak diterapkan karena tidak ada kasus penggunaan yang menarik pada saat itu. Apakah itu alasan yang sama mengapa enum tidak disertakan sejak awal? Saya akan sangat tertarik untuk mengetahui pendapat Anda tentang apakah itu akan menambah nilai Go, dan jika ya, di mana Anda akan memulai prosesnya?

@derekperkins Mengenai pertanyaan Anda di https://github.com/golang/go/issues/19814#issuecomment -323144075: Pada saat itu (dalam desain Go) kami hanya mempertimbangkan enum yang relatif sederhana (katakanlah Pascal atau C-style). Saya tidak ingat semua detailnya tetapi pasti ada perasaan bahwa tidak ada cukup manfaat untuk mesin tambahan yang diperlukan untuk enum. Kami merasa bahwa itu pada dasarnya adalah deklarasi konstan yang dimuliakan.

Ada juga masalah dengan enum tradisional ini: Dimungkinkan untuk melakukan aritmatika dengan mereka (mereka hanya bilangan bulat), tetapi apa artinya jika mereka "di luar jangkauan (enum)"? Di Go mereka hanya konstanta dan "di luar jangkauan" tidak ada. Satu lagi adalah iterasi: Dalam Pascal ada fungsi bawaan khusus (saya pikir SUCC dan PRED) untuk memajukan nilai variabel tipe enum maju dan mundur (dalam C hanya melakukan ++ atau --). Tetapi masalah yang sama juga muncul di sini: Apa yang terjadi jika seseorang melewati akhir (masalah yang sangat umum dalam perulangan for yang berkisar pada nilai enum menggunakan ++ atau SUCC setara Pascal). Akhirnya, deklarasi enum memperkenalkan tipe baru, yang elemennya adalah nilai enum. Nilai-nilai ini memiliki nama (yang didefinisikan dalam deklarasi enum), tetapi nama-nama itu (dalam Pascal, C) dalam lingkup yang sama dengan tipenya. Ini agak tidak memuaskan: Ketika mendeklarasikan dua enum yang berbeda, orang akan berharap bahwa seseorang dapat menggunakan nama nilai enum yang sama untuk setiap jenis enum tanpa konflik, yang tidak mungkin. Tentu saja Go juga tidak menyelesaikannya, tetapi deklarasi konstan juga tidak terlihat seperti memperkenalkan ruang nama baru. Solusi yang lebih baik adalah memperkenalkan ruang nama dengan setiap enumerasi, tetapi kemudian setiap kali nilai enum digunakan, itu perlu dikualifikasikan dengan nama tipe enum, yang menjengkelkan. Swift memecahkan ini dengan menyimpulkan jenis enum jika memungkinkan dan kemudian seseorang dapat menggunakan nama nilai enum yang diawali dengan titik. Tapi itu sedikit mesin. Dan akhirnya, terkadang (seringkali, di API publik), seseorang perlu memperluas deklarasi enum. Jika itu tidak mungkin (Anda tidak memiliki kodenya), ada masalah. Dengan konstanta, masalah itu tidak ada.

Mungkin ada lebih dari itu; ini hanya apa yang datang ke pikiran. Pada akhirnya kami memutuskan bahwa lebih baik untuk meniru enum di Go menggunakan alat ortogonal yang sudah kami miliki: tipe integer khusus yang membuat penugasan yang salah lebih kecil kemungkinannya, dan mekanisme iota (dan kemampuan meninggalkan ekspresi inisialisasi berulang) untuk gula sintaksis.

Karenanya pertanyaan saya: Apa yang ingin Anda peroleh dari deklarasi enum khusus yang tidak dapat kami tiru secara memadai di Go dengan sedikit overhead sintaksis? Saya dapat memikirkan enumerasi, dan tipe enum yang tidak dapat diperpanjang di luar deklarasi. Saya bisa memikirkan lebih banyak kemampuan untuk nilai enum, seperti di Swift.

Pencacahan dapat diselesaikan dengan mudah dengan generator go di Go. Kami sudah memiliki stringer. Membatasi ekstensi bermasalah di seluruh batas API. Lebih banyak kemampuan untuk nilai enum (katakanlah seperti dalam Swift) tampaknya sangat berbeda karena menggabungkan banyak konsep ortogonal. Di Go, kita mungkin akan mencapainya dengan menggunakan blok penyusun dasar.

@griesemer Terima kasih atas balasan Anda yang bijaksana. Saya tidak setuju bahwa mereka pada dasarnya adalah deklarasi konstanta yang dimuliakan. Memiliki keamanan tipe di Go sangat bagus, dan nilai utama yang akan diberikan enum kepada saya secara pribadi adalah nilai keamanan. Cara untuk menirunya di Go hari ini adalah dengan menjalankan fungsi validasi di setiap titik masuk untuk variabel itu. Ini bertele-tele dan membuatnya mudah untuk membuat kesalahan, tetapi dimungkinkan dengan bahasa seperti sekarang ini. Saya sudah namespace dengan mengawali nama tipe di depan enum, yang meskipun bertele-tele, bukan masalah besar.

Saya pribadi tidak menyukai sebagian besar kegunaan untuk iota . Meskipun keren, sebagian besar nilai saya enum -seperti memetakan ke sumber daya eksternal seperti db atau api eksternal, dan saya lebih suka untuk lebih eksplisit bahwa nilainya tidak boleh diubah jika Anda memesan ulang. iota juga tidak membantu untuk sebagian besar tempat saya akan menggunakan enum karena saya akan menggunakan daftar nilai string.

Pada akhirnya, saya tidak tahu berapa banyak lagi saya bisa mengklarifikasi proposal ini. Saya akan senang jika mereka didukung dengan cara apa pun yang masuk akal untuk Go. Terlepas dari implementasi yang tepat, saya masih dapat menggunakannya dan mereka akan membuat kode saya lebih aman.

Saya pikir cara kanonik Go melakukan enum hari ini (seperti yang terlihat di https://github.com/golang/go/issues/19814#issuecomment-290909885 ) cukup dekat dengan benar.
Ada beberapa kelemahan:

  1. Mereka tidak dapat diulangi
  2. Mereka tidak memiliki representasi string
  3. Klien dapat memperkenalkan nilai enum palsu

Aku baik-baik saja tanpa #1.
go:generate + stringer dapat digunakan untuk #2. Jika itu tidak menangani kasus penggunaan Anda, jadikan tipe dasar "enum" Anda sebagai string alih-alih int, dan gunakan nilai konstanta string.

3 adalah yang paling sulit untuk ditangani dengan Go hari ini. Saya punya proposal konyol yang mungkin menangani ini dengan baik.

Tambahkan kata kunci explicit ke definisi tipe. Kata kunci ini melarang konversi ke jenis ini kecuali untuk konversi di blok const dalam paket di mana jenis itu ditentukan. (Atau restricted ? Atau mungkin enum artinya explicit type ?)

Menggunakan kembali contoh yang saya rujuk di atas,

//go:generate stringer -type=SearchRequest
explicit type SearchRequest int

const (
    Universal SearchRequest = iota
    Web
    Images
    Local
    News
    Products
    Video
)

Ada konversi dari int ke SearchRequest di dalam blok const . Tetapi hanya pembuat paket yang dapat memperkenalkan nilai SearchRequest baru, dan kemungkinan tidak akan memperkenalkannya secara tidak sengaja (dengan meneruskan int ke fungsi yang mengharapkan SearchRequest , misalnya).

Saya tidak benar-benar aktif mengusulkan solusi ini, tetapi saya pikir jangan-tidak sengaja-konstruk-an-tidak valid-satu adalah properti yang menonjol dari enum yang tidak dapat ditangkap di Go hari ini (kecuali jika Anda menggunakan struct dengan rute lapangan yang tidak diekspor).

Saya pikir risiko yang menarik dengan enum adalah dengan konstanta yang tidak diketik. Orang yang menulis konversi tipe eksplisit tahu apa yang mereka lakukan. Saya bersedia mempertimbangkan cara untuk Go melarang konversi tipe eksplisit dalam keadaan tertentu, tetapi berpikir itu sepenuhnya ortogonal dengan gagasan tipe enum. Ini adalah ide yang berlaku untuk jenis apa pun.

Tetapi konstanta yang tidak diketik dapat menyebabkan pembuatan nilai tipe secara tidak sengaja dan tidak terduga, dengan cara yang tidak benar untuk konversi tipe eksplisit. Jadi saya pikir saran @randall77 tentang explicit dapat disederhanakan menjadi hanya berarti bahwa konstanta yang tidak diketik mungkin tidak secara implisit dikonversi ke tipe. Konversi tipe eksplisit selalu diperlukan.

Saya bersedia mempertimbangkan cara untuk Go melarang konversi tipe eksplisit dalam keadaan tertentu

@ianlancetaylor Secara opsional melarang konversi jenis, baik eksplisit maupun implisit, akan menyelesaikan masalah yang menyebabkan saya membuat proposal ini sejak awal. Hanya paket asal yang dapat membuat dan dengan demikian memenuhi semua jenis. Itu bahkan lebih baik daripada solusi enum dalam beberapa hal karena tidak hanya mendukung deklarasi const , tetapi semua jenis.

@randall77 , @ianlancetaylor Bagaimana cara membuat saran explicit bekerja sama dengan nilai nol dari jenis itu?

@derekperkins Melarang konversi tipe sama sekali akan membuat tidak mungkin menggunakan tipe ini di encoder/decoder generik, seperti paket encoding/* .

@Merovius Saya pikir @ianlancetaylor menyarankan pembatasan hanya untuk konversi implisit (misalnya, penetapan konstanta yang tidak diketik ke tipe terbatas). Konversi eksplisit masih dimungkinkan.

@griesemer saya tahu :) Tapi saya mengerti @derekperkins menyarankan berbeda.

Tidakkah mengizinkan konversi eksplisit merusak alasan utama kami memikirkan kualifikasi "eksplisit" ini? Jika seseorang dapat memutuskan untuk mengonversi nilai arbitrer menjadi tipe "eksplisit", maka kami tidak memiliki jaminan lagi bahwa nilai yang diberikan adalah salah satu konstanta yang disebutkan daripada yang kami lakukan sekarang.

Saya kira itu membantu untuk penggunaan konstanta yang tidak diketik secara kasual atau tidak disengaja, yang mungkin merupakan hal yang paling penting.

Saya kira saya mempertanyakan apakah melarang konversi eksplisit adalah dalam "semangat Go." Melarang konversi eksplisit mengambil langkah besar menuju pemrograman berdasarkan jenis daripada pemrograman berdasarkan penulisan kode. Saya pikir Go mengambil posisi yang jelas mendukung yang terakhir.

@griesemer @Merovius Saya akan memposting ulang kutipan dari @ianlancetaylor lagi, karena itu sarannya, bukan saran saya.

Saya bersedia mempertimbangkan cara untuk Go melarang konversi tipe eksplisit dalam keadaan tertentu

Baik @rogpeppe dan @Merovius mengemukakan poin bagus tentang konsekuensinya. Mengizinkan konversi eksplisit tetapi bukan konversi implisit tidak menyelesaikan masalah menjamin jenis yang valid, tetapi kehilangan pengkodean umum akan menjadi kerugian yang cukup besar.

Ada banyak bolak-balik di sini, tapi saya pikir ada beberapa ide bagus. Berikut adalah ringkasan yang ingin saya lihat (atau yang serupa), yang tampaknya selaras dengan apa yang dikatakan beberapa orang lain. Saya secara terbuka mengakui bahwa saya bukan perancang bahasa atau programmer kompiler, jadi saya tidak tahu seberapa baik itu akan bekerja.

  1. Enum hanya berakar pada tipe dasar (string, uint, int, rune, dll). Jika tidak ada tipe dasar yang diperlukan, itu bisa default ke uint?
  2. Semua nilai enum yang valid harus dideklarasikan dengan deklarasi tipe -- Konstanta. Nilai yang tidak valid (tidak dideklarasikan dalam deklarasi tipe) tidak dapat diubah menjadi tipe enum.
  3. Representasi string otomatis untuk debugging (bagus untuk dimiliki).
  4. Pemeriksaan waktu kompilasi untuk kelengkapan dalam pernyataan sakelar di enum. Opsional merekomendasikan (melalui go vet ?) kasus default , bahkan ketika sudah lengkap (kemungkinan kesalahan) untuk perubahan di masa mendatang.
  5. Nilai nol pada dasarnya harus tidak valid (bukan sesuatu dalam deklarasi enum). Saya pribadi ingin menjadi nil , seperti halnya irisan.

Yang terakhir _mungkin_ agak kontroversial. Dan saya tidak tahu pasti apakah itu akan berhasil, tetapi saya pikir itu akan cocok secara semantik -- sama seperti seseorang akan memeriksa irisan nil , seseorang dapat memasukkan cek untuk nil nilai enum.

Adapun iterasi, saya tidak berpikir saya akan pernah menggunakannya, tetapi saya tidak melihat bahayanya.

Sebagai contoh bagaimana itu bisa dideklarasikan:

type MetadataBlockType enum[uint] {
    StreamInfo:    0
    Padding:       1
    Application:   2
    SeekTable:     3
    VorbisComment: 4
    CueSheet:      5
    Picture:       6
}

Juga, gaya Swift dalam menyimpulkan jenis dan menggunakan "sintaks titik" akan menjadi _nice_, tetapi jelas tidak perlu.

ketik EnumA ke dalam
konstan (
EnumA Tidak Diketahui = sedikit pun
AAA
)


ketik EnumB ke dalam
konstan (
EnumB Tidak Diketahui = sedikit pun
BB
)

Ada 2 potongan kode yang tidak dapat ada dalam satu file Go, atau paket yang sama, atau bahkan satu diimpor dari paket lain.

Harap terapkan cara C# dalam mengimplementasikan Enum:
ketik Days enum {Sab, Min, Sen, Sel, Rab, Kam, Jum}
type Days enum[int] { Sab:1 , Min, Sel, Rab, Kam, Jum}
type Days enum[string] { Sab: "Saturay" , Sun:"Sunday" dll}

@KamyarM Bagaimana itu lebih baik dari

type Days int
const (
  Sat Days = 1+iota
  Sun
  ...
)

dan

type Days string
const (
  Sat Days = "Saturday"
  Sun      = "Sunday"
  ...
)

Saya ingin meminta dengan hormat untuk membatasi komentar pada pendekatan/argumen baru. Banyak orang berlangganan utas ini dan menambahkan kebisingan/pengulangan dapat dianggap tidak menghargai waktu dan perhatian mereka. Ada banyak diskusi di sana , termasuk jawaban terperinci untuk kedua komentar sebelumnya. Anda tidak akan setuju dengan semua yang dikatakan dan tidak ada pihak yang mungkin menyukai hasil diskusi sejauh ini - tetapi mengabaikannya saja tidak akan membantu memindahkannya ke arah yang produktif juga.

Lebih baik karena tidak memiliki masalah konflik penamaan. Juga mendukung pemeriksaan jenis kompiler. Pendekatan yang Anda sebutkan mengaturnya lebih baik daripada tidak sama sekali tetapi kompiler tidak membatasi Anda pada apa yang dapat Anda tetapkan. Anda dapat menetapkan bilangan bulat yang bukan hari apa pun ke objek jenis itu:
hari
a = 10
compiler sebenarnya tidak melakukan apa-apa tentang hal itu. Jadi tidak ada gunanya enum semacam ini. selain itu lebih baik diatur dalam IDE seperti GoLand

Saya ingin melihat sesuatu seperti itu

type WeekDay enum string {
  Monday "mon"
  Tuesday "tue"
  // etc...
}

Atau dengan penggunaan iota otomatis:

// this assumes that iota automatically assigned to the first declared enum key
// and values have unsigned integer type
// value is positional, so if you decide to rename your key 
// you don't have to change everything in db
// also it is easy to grow your lists
type WeekDay enum {
  Monday
  Tuesday
}

Ini akan memberikan kesederhanaan dan kemudahan dalam penggunaan:

func makeItWorkOn(day WeekDay) {
  // your implementation
}

Juga, enum harus memiliki metode bawaan untuk memvalidasi nilai sehingga kami dapat memvalidasi sesuatu dari input pengguna:

if day in WeekDay {
  makeItWorkOn(day)
}

Dan hal-hal sederhana seperti:

if day == WeekDay.Monday {
 // whatever
}

Sejujurnya, sintaks favorit saya adalah seperti ini (KISS):

// type automatically inferred from values or `iota`
enum WeekDay {
  Monday "mon"
  Tuesday "tue"
}

@zoonman Contoh terakhir tidak mengikuti prinsip Go berikut: deklarasi fungsi dimulai dengan func , deklarasi tipe dimulai dengan type , deklarasi variabel dimulai dengan var , ...

@ md2perpe Saya tidak mencoba mengikuti prinsip "ketik", saya menulis kode setiap hari dan satu-satunya prinsip yang saya ikuti - buat semuanya tetap sederhana.
Daripada lebih banyak kode yang harus Anda tulis _untuk mengikuti prinsip_ maka lebih banyak waktu yang terbuang.
TBH Saya pemula, tetapi ada banyak hal yang dapat saya kritik.
Sebagai contoh:

struct User {
  Id uint
  Email string
}

Lebih mudah untuk menulis dan memahami daripada

type User struct {
  Id uint
  Email string
}

Saya dapat memberi Anda contoh di mana tipe harus digunakan:

// this is terrible because it blows your mind off
// especially if you Go newbie
// this should not be allowed by compiler/linter:
w := map[string]interface{}{"type": 0, "connected": true}

// it has to be splitted into type definition
type WeirdJSON map[string]interface{}

// and used like
w := WeirdJSON{"type": 0, "connected": true}

Saya dulu menulis kode dalam Asm, C, C++, Pascal, Perl, PHP, Ruby, Python, JavaScript, TypeScript, sekarang Go. Saya melihat semua itu. Pengalaman ini memberi tahu saya bahwa kode harus singkat, mudah dibaca dan dipahami .

Saya membuat beberapa proyek pembelajaran mesin dan perlu mengurai file MIDI.
Di sana saya perlu mengurai kode waktu SMPTE. Saya merasa cukup sulit untuk menggunakan cara idiomatik dengan sedikit pun, tetapi itu tidak menghentikan saya)

const (
        SMTPE0 int8 = ((-24 - (1 + (iota - 1) * 3) % 6 * (iota - 1) / ((iota - 1) | 0x01)) - 10 * ((iota - 1) % 2) - 5 * (iota / 3 - iota / 4) ) * iota / (iota | 0x01)
    SMTPE24 
    SMTPE25
    SMTPE29
    SMTPE30
)
const (
   _SMTPE0 int8 = 0 
   _SMTPE24 int8 = -24
   _SMTPE25 int8 = -25
   _SMTPE29 int8 = -29
   _SMTPE30 int8 = -30
)

Tentu saja saya mungkin perlu beberapa pemeriksaan runtime dengan pemrograman defensif ...

func IsSMTPE(status int8) bool {
    j := 4
    for i:= 0; i >= -30; i -= j % 6{
        if i == int(status){ 
            return true
        }
        j+=3
    }

    return status == 0
}

PlayGroundRef

Enum membuat hidup programmer dalam beberapa kasus lebih sederhana. Enums hanyalah sebuah instrumen maka Anda menggunakannya dengan benar sehingga dapat menghemat waktu dan meningkatkan produktivitas. Saya pikir tidak ada masalah untuk mengimplementasikan ini di Go 2 seperti di c++, c# atau bahasa lainnya. Contoh ini hanya lelucon, tetapi jelas menunjukkan masalahnya.

@streeter12 Saya tidak melihat bagaimana contoh Anda "dengan jelas menunjukkan masalahnya". Bagaimana enum membuat kode ini lebih baik atau lebih aman?

Ada kelas C# dengan implementasi logika enum yang sama.

 public enum SMTPE : sbyte
   {
        SMTPE0 = 0,
        SMTPE24 = -24,
        SMTPE25 = -25,
        SMTPE29 = -29,
        SMTPE30 = -30
   }

   public class TestClass
   {
        public readonly SMTPE smtpe;

        public TestClass(SMTPE smtpe)
        {
            this.smtpe = smtpe;
        }
   } 

Dengan waktu kompilasi enum saya dapat:

  1. Tidak memiliki pemeriksaan runtime.
  2. Secara signifikan mengurangi kemungkinan kesalahan oleh tim (Anda tidak dapat melewatkan nilai yang salah dalam waktu kompilasi).
  3. Itu tidak bertentangan dengan konsep sedikit pun.
  4. Lebih mudah untuk memahami logika daripada Anda memiliki satu nama untuk konstanta (penting maka konstanta Anda mewakili beberapa nilai protokol tingkat rendah).
  5. Anda dapat membuat analog metode ToString() untuk membuat representasi nilai yang sederhana. (CONNECTION_ERROR.NO_INTERNET lebih baik dari 0x12). Saya tahu tentang stringer, tetapi tidak ada pembuatan kode eksplisit dengan enum.
  6. Dalam beberapa bahasa Anda bisa mendapatkan array nilai, rentang, dan lain-lain.
  7. Ini mudah dimengerti saat membaca kode (tidak perlu perhitungan di kepala).

Bagaimanapun itu hanya alat untuk mencegah beberapa kesalahan manusia yang umum dan menghemat kinerja.

@streeter12 Terima kasih telah menjelaskan apa yang Anda maksud. Satu-satunya keuntungan dari konstanta Go di sini adalah bahwa seseorang tidak dapat memasukkan nilai yang tidak valid karena sistem tipe tidak akan menerima nilai lain selain salah satu nilai enum. Itu tentu bagus untuk dimiliki tetapi juga ada harganya: Tidak ada cara untuk memperluas enum ini di luar kode itu. Ekstensi enum eksternal adalah salah satu alasan utama mengapa di Go kami memutuskan untuk tidak menggunakan enum standar.

Jawab hanya kebutuhan sederhana untuk membuat beberapa ekstensi tidak menggunakan enum.
FE perlu membuat mesin negara menggunakan pola status alih-alih enum.

Enum memiliki cakupannya sendiri. Saya menyelesaikan beberapa proyek besar tanpa enum. Saya pikir itu keputusan arsitektur yang buruk untuk memperluas enum di luar kode definisi. Anda tidak memiliki kendali atas apa yang dilakukan rekan Anda dan itu membuat beberapa kesalahan lucu)

Dan Anda lupa enum faktor manusia dalam banyak kasus mengurangi kesalahan secara signifikan dalam proyek-proyek besar.

@streeter12 Sayangnya, kenyataannya sering kali enum perlu diperpanjang.

@griesemer memperluas tipe enum/sum membuat tipe terpisah dan terkadang tidak kompatibel.

Ini masih berlaku di Go meskipun tidak ada tipe eksplisit untuk enum/sum. Jika Anda memiliki "tipe enum" dalam paket yang mengharapkan nilai dalam {1, 2, 3} dan Anda memberikannya 4 dari "tipe enum yang diperluas", Anda masih melanggar kontrak "tipe" implisit.

Jika Anda perlu memperluas enum/sum, Anda juga perlu membuat fungsi konversi Ke/Dari eksplisit yang secara eksplisit menangani kasus yang terkadang tidak kompatibel.

Saya pikir keterputusan antara argumen itu dan orang-orang untuk proposal ini atau proposal serupa seperti #19412 adalah bahwa menurut kami aneh bahwa trade off-nya adalah "selalu tulis kode validasi dasar yang dapat ditangani oleh kompiler" alih-alih "terkadang tulis fungsi konversi yang Anda' re mungkin juga harus menulis".

Itu tidak berarti salah satu pihak benar atau salah atau itu adalah satu-satunya trade off yang perlu dipertimbangkan, tetapi saya ingin mengidentifikasi hambatan dalam komunikasi antara kedua pihak yang saya perhatikan.

Saya pikir keterputusan antara argumen itu dan orang-orang untuk proposal ini atau proposal serupa seperti #19412 adalah bahwa menurut kami aneh bahwa trade off-nya adalah "selalu tulis kode validasi dasar yang dapat ditangani oleh kompiler" alih-alih "terkadang tulis fungsi konversi yang Anda' re mungkin juga harus menulis".

Dinyatakan dengan sangat baik

@jimmyfrasche Bukan itu cara saya secara pribadi menggambarkan pengorbanannya. Saya akan mengatakan itu "selalu tulis kode validasi dasar yang dapat ditangani oleh kompiler" vs. "tambahkan seluruh konsep baru ke sistem tipe yang perlu dipelajari dan dipahami oleh semua orang yang menggunakan Go."

Atau, izinkan saya mengatakannya dengan cara lain. Sejauh yang saya tahu, satu-satunya fitur signifikan yang hilang dari versi tipe enumerasi Go adalah bahwa tidak ada validasi penetapan dari konstanta yang tidak diketik, tidak ada pemeriksaan pada konversi eksplisit, dan tidak ada pemeriksaan bahwa semua nilai ditangani dalam a mengalihkan. Tampaknya bagi saya bahwa semua fitur itu tidak tergantung pada gagasan tentang tipe yang disebutkan. Kita tidak boleh membiarkan fakta bahwa bahasa lain memiliki tipe enumerasi membawa kita pada kesimpulan bahwa Go juga membutuhkan tipe enumerasi. Ya, tipe yang disebutkan akan memberi kita fitur yang hilang itu. Tetapi apakah benar-benar perlu menambahkan jenis yang sama sekali baru untuk mendapatkannya? Dan apakah peningkatan kompleksitas bahasa sepadan dengan manfaatnya?

@ianlancetaylor Menambahkan kompleksitas pada bahasa tentu saja merupakan hal yang valid untuk dipertimbangkan, dan "karena bahasa lain memilikinya" tentu saja bukan argumen. Saya, secara pribadi, tidak berpikir tipe enum layak untuk mereka sendiri. (Namun, generalisasi mereka, jenis penjumlahan, tentu saja mencentang banyak kotak untuk saya).

Cara umum untuk suatu tipe untuk memilih keluar dari penugasan akan menyenangkan, meskipun saya tidak yakin seberapa berguna itu di luar primitif.

Saya tidak yakin seberapa dapat digeneralisasikan konsep "periksa semua nilai yang ditangani dalam sakelar", tanpa memberi tahu kompiler daftar lengkap nilai hukum. Selain tipe enum dan sum, satu-satunya hal yang dapat saya pikirkan adalah sesuatu seperti tipe rentang Ada tetapi itu tidak secara alami kompatibel dengan nilai nol kecuali 0 harus dalam rentang atau kode yang dihasilkan untuk menangani offset setiap kali mereka dikonversi atau direfleksikan pada. (Bahasa lain memiliki jenis keluarga yang serupa, beberapa di keluarga pascal, tetapi hanya Ada yang terlintas dalam pikiran saat ini)

Bagaimanapun, saya secara khusus mengacu pada:

Satu-satunya keuntungan dari konstanta Go di sini adalah bahwa seseorang tidak dapat memasukkan nilai yang tidak valid karena sistem tipe tidak akan menerima nilai lain selain salah satu nilai enum. Itu tentu bagus untuk dimiliki tetapi juga ada harganya: Tidak ada cara untuk memperluas enum ini di luar kode itu. Ekstensi enum eksternal adalah salah satu alasan utama mengapa di Go kami memutuskan untuk tidak menggunakan enum standar.

dan

Sayangnya, kenyataannya sering kali enum perlu diperpanjang.

Argumen itu tidak cocok untuk saya karena alasan yang saya nyatakan.

@jimmyfrasche Mengerti; itu masalah yang sulit. Itulah sebabnya di Go kami tidak mencoba menyelesaikannya tetapi hanya menyediakan mekanisme untuk membuat urutan konstanta dengan mudah tanpa perlu mengulang nilai konstanta.

(Dikirim tertunda - dimaksudkan sebagai tanggapan terhadap https://github.com/golang/go/issues/19814#issuecomment-349158748)

@griesemer memang dan itu pasti panggilan yang tepat untuk Go 1 tetapi beberapa di antaranya layak untuk dievaluasi kembali untuk Go 2.

Ada cukup dalam bahasa untuk mendapatkan _hampir_ semua yang diinginkan dari tipe enum. Ini membutuhkan lebih banyak kode daripada definisi tipe, tetapi generator dapat menangani sebagian besar dan memungkinkan Anda menentukan sebanyak atau sesedikit yang sesuai dengan situasi alih-alih hanya mendapatkan kekuatan apa pun yang datang dengan tipe enum.

Pendekatan ini https://play.golang.org/p/7ud_3lrGfx memberi Anda segalanya kecuali

  1. keamanan dalam paket yang ditentukan
  2. kemampuan untuk menyambungkan sakelar untuk kelengkapan

Pendekatan itu juga dapat digunakan untuk tipe penjumlahan kecil dan sederhana† tetapi lebih canggung untuk digunakan, itulah sebabnya saya pikir sesuatu seperti https://github.com/golang/go/issues/19412#issuecomment -323208336 akan menambah bahasa dan dapat digunakan oleh pembuat kode untuk membuat tipe enum yang menghindari masalah 1 dan 2.

lihat https://play.golang.org/p/YFffpsvx5e untuk sketsa json.Token dengan konstruksi ini

kami pikir itu aneh bahwa trade off adalah "selalu tulis kode validasi dasar yang dapat ditangani oleh kompiler" alih-alih "terkadang tulis fungsi konversi yang mungkin juga harus Anda tulis".

Bagi saya - perwakilan dari kubu pendukung sengit perbaikan bertahap - ini sepertinya tradeoff yang benar. Sejujurnya, bahkan jika kita tidak berbicara tentang perbaikan bertahap, saya akan menganggap itu sebagai model mental yang lebih baik.

Pertama, enum yang dicentang tipe hanya akan dapat memeriksa nilai yang dimasukkan ke dalam kode sumber. Jika enum berjalan melalui jaringan, disimpan ke disk atau dipertukarkan di antara proses, semua taruhan dibatalkan (dan sebagian besar penggunaan enum yang diusulkan termasuk dalam kategori ini). Jadi, Anda tidak akan menyia-nyiakan masalah penanganan inkompatibilitas saat runtime. Dan tidak ada perilaku default umum satu ukuran untuk semua ketika Anda menemukan nilai enum yang tidak valid. Seringkali Anda mungkin ingin melakukan kesalahan. Terkadang Anda mungkin ingin memaksanya menjadi nilai default. Sebagian besar waktu Anda ingin melestarikannya dan menyebarkannya, sehingga tidak hilang saat serialisasi ulang.

Tentu saja Anda mungkin berpendapat bahwa harus tetap ada batas kepercayaan, di mana validitas diperiksa dan perilaku yang diperlukan diimplementasikan - dan segala sesuatu di dalam batas itu harus dapat memercayai perilaku itu. Dan model mental tampaknya, bahwa batas kepercayaan ini harus menjadi sebuah proses. Karena semua kode dalam biner akan diubah secara atomik dan tetap konsisten secara internal. Tetapi model mental itu terkikis oleh gagasan perbaikan bertahap; tiba-tiba, batas kepercayaan alami menjadi sebuah paket (atau mungkin repositori) sebagai unit tempat Anda menerapkan perbaikan atomik dan unit yang Anda percayai sebagai unit yang konsisten.

Dan, secara pribadi, saya menemukan bahwa unit konsistensi diri yang sangat alami dan hebat. Sebuah paket harus cukup besar untuk menyimpan semantik, aturan, dan konvensi di kepala Anda. Itulah juga mengapa ekspor bekerja di tingkat paket, bukan tingkat tipe dan mengapa deklarasi tingkat atas dicakup di tingkat paket, bukan tingkat program. Tampaknya baik-baik saja dan cukup hemat bagi saya, untuk memutuskan penanganan yang benar dari nilai enum yang tidak diketahui di tingkat paket juga. Memiliki fungsi yang tidak diekspor, yang memeriksanya dan mempertahankan perilaku yang diinginkan secara internal.

Saya akan lebih setuju dengan proposal bahwa setiap sakelar memerlukan kasus default, daripada dengan proposal untuk memiliki enum yang diperiksa tipenya termasuk pemeriksaan kelengkapan.

@Merovius Proses sistem operasi dan paket keduanya merupakan batas kepercayaan, seperti yang Anda katakan.

Informasi yang berasal dari luar proses harus divalidasi pada saat masuk dan diuraikan menjadi representasi yang tepat untuk proses dan perawatan yang tepat dilakukan ketika gagal. Itu tidak pernah hilang. Saya tidak benar-benar melihat sesuatu yang spesifik untuk tipe sum/enum di sana. Anda dapat mengatakan hal yang sama tentang struct—terkadang Anda mendapatkan bidang tambahan atau terlalu sedikit bidang. Struct masih berguna.

Karena itu, dengan tipe enum Anda tentu saja dapat memasukkan kasus khusus untuk memodelkan kesalahan ini. Sebagai contoh

type FromTheNetwork enum {
  // pretend all the "valid" values are listed here
  Missing // the value was not included in the message
  Unknown // the value was not in the set of the valid values
  Error // there was an error attempting to read the value
}

dan dengan tipe sum Anda dapat melangkah lebih jauh:

type FromTheNetwork pick {
  Missing struct{} // Not included in message
  Valid somepkg.TheSumBeingReceived // Include valid states with composition
  Unknown []byte // Raw bytes of unknown value received
  Error error // The error from attempting to read the value
}

(Yang pertama tidak berguna kecuali disimpan dalam struct dengan bidang khusus untuk kasus kesalahan, tetapi kemudian validitas bidang tersebut bergantung pada nilai enum. Jenis jumlah menangani itu karena pada dasarnya adalah a struct yang hanya dapat memiliki satu set bidang pada satu waktu.)

Pada tingkat paket, Anda masih perlu menangani validasi tingkat tinggi, tetapi validasi tingkat rendah dilengkapi dengan jenisnya. Saya akan mengatakan mengurangi domain jenis membantu menjaga paket tetap kecil dan di kepala Anda. Itu juga membuat maksud lebih jelas untuk perkakas sehingga editor Anda dapat menulis semua baris case X: dan membiarkan Anda mengisi kode yang sebenarnya atau linter dapat digunakan untuk memastikan semua kode memeriksa semua kasus (Anda membujuk saya untuk tidak memiliki kelengkapan dalam kompiler sebelumnya).

Saya tidak benar-benar melihat sesuatu yang spesifik untuk tipe sum/enum di sana. Anda dapat mengatakan hal yang sama tentang struct—terkadang Anda mendapatkan bidang tambahan atau terlalu sedikit bidang. Struct masih berguna.

Jika kita berbicara tentang enum terbuka (seperti yang saat ini dibuat oleh sedikit pun), tentu saja. Jika kita berbicara tentang enum tertutup (yang biasanya dibicarakan orang, ketika mereka berbicara tentang enum) atau enum dengan pemeriksaan lengkap, maka mereka pasti istimewa. Karena mereka tidak dapat diperpanjang.

Analogi dengan struct menjelaskan hal ini dengan cukup sempurna: Janji kompatibilitas Go 1 mengecualikan literal struct yang tidak dikunci dari janji apa pun - dan dengan demikian, menggunakan literal struct yang dikunci telah menjadi praktik yang dianggap sangat kuat sebagai "terbaik", sehingga dokter hewan memeriksanya. Alasannya persis sama: Jika Anda menggunakan literal struct yang tidak dikunci, struct tidak lagi dapat diperluas.

Jadi iya. Structs persis seperti enum dalam hal ini. Dan kami telah sepakat sebagai komunitas bahwa lebih baik menggunakannya dengan cara yang dapat diperluas.

Karena itu, dengan tipe enum Anda tentu saja dapat memasukkan kasus khusus untuk memodelkan kesalahan ini.

Contoh Anda hanya mencakup batas-proses (dengan berbicara tentang kesalahan jaringan), bukan batas-paket. Bagaimana paket akan berperilaku, jika saya menambahkan "InvalidInternalState" (untuk membuat sesuatu) ke FromTheNetwork ? Apakah saya harus memperbaiki sakelarnya sebelum dikompilasi lagi? Maka itu tidak dapat diperluas dalam model perbaikan bertahap. Apakah mereka memerlukan kasus default untuk dikompilasi? Maka sepertinya tidak ada gunanya enum.

Sekali lagi, memiliki enum terbuka adalah pertanyaan yang berbeda. Saya akan bergabung dengan hal-hal seperti

Saya akan mengatakan mengurangi domain jenis membantu menjaga paket tetap kecil dan di kepala Anda. Itu juga membuat maksud lebih jelas untuk perkakas sehingga editor Anda bisa menulis semua kasus X: baris dan membiarkan Anda mengisi kode yang sebenarnya atau linter dapat digunakan untuk memastikan semua kode memeriksa semua kasus

Tetapi untuk itu kita tidak memerlukan enum yang sebenarnya, sebagai tipe. Alat linting seperti itu juga dapat memeriksa secara heuristik untuk const -declarations menggunakan iota , di mana setiap kasing memiliki tipe tertentu dan menganggapnya sebagai "enum" dan melakukan pemeriksaan yang Anda inginkan. Saya akan sepenuhnya menggunakan alat yang menggunakan "enum menurut konvensi" ini untuk membantu pelengkapan otomatis atau linting bahwa setiap sakelar harus memiliki default atau bahkan bahwa setiap kasing (dikenal) harus diperiksa. Saya bahkan tidak akan menentang untuk menambahkan kata kunci enum yang berperilaku seperti itu; yaitu enum terbuka (dapat mengambil nilai integer apa pun), memberi Anda pelingkupan ekstra dan mengharuskan memiliki default di sakelar apa pun (saya tidak berpikir mereka akan menambahkan cukup iota-enum untuk biaya tambahan, tetapi setidaknya mereka tidak akan merusak agenda saya). Jika itu yang diusulkan - baiklah. Tapi sepertinya bukan itu maksud mayoritas pendukung proposal ini (tentu saja bukan teks awal).

Kita bisa tidak setuju tentang pentingnya menjaga perbaikan bertahap dan kemungkinan diperpanjang - misalnya, banyak orang percaya bahwa versi semantik adalah solusi yang lebih baik untuk masalah yang dipecahkannya. Tetapi jika Anda menganggapnya penting, sangat valid dan masuk akal untuk melihat enum sebagai berbahaya atau tidak berguna. Dan itulah pertanyaan yang saya balas: Bagaimana orang dapat secara wajar melakukan tradeoff dengan membutuhkan cek di mana-mana, alih-alih memasukkannya ke dalam kompiler. Jawaban: Dengan menilai ekstensibilitas dan evolusi API, yang membuat pemeriksaan ini tetap diperlukan di situs penggunaan.

Dari waktu ke waktu penentang enum mengatakan bahwa mereka tidak dapat diperluas, kami masih memerlukan pemeriksaan setelah serialisasi/transisi, kami dapat mematahkan kembali kompatibilitas, dll.

Masalah utama bahwa ini bukan masalah enum itu adalah masalah pengembangan dan arsitektur Anda.
Anda mencoba memberikan contoh di mana penggunaan enum itu konyol, tetapi mari kita pertimbangkan beberapa situasi secara lebih rinci.

Contoh 1. Saya adalah pengembang tingkat rendah dan, saya memerlukan const untuk beberapa alamat register, menetapkan nilai protokol tingkat rendah, dll. Saat ini di Go saya hanya mendapat satu solusi: adalah menggunakan consts tanpa sedikit pun, karena dalam banyak kasus itu akan jelek . Saya bisa mendapatkan beberapa blok konstanta untuk satu paket dan setelah tekan . saya mendapatkan semua 20 konstanta dan jika mereka memiliki tipe yang sama dan nama yang mirip saya dapat membuat kesalahan. Jika proyek besar, Anda akan mendapatkan kesalahan ini. Untuk mencegah hal ini dengan pemrograman defensif, TDD kita harus melanggar kode cek duplikat (kode duplikat = kesalahan duplikat/tes dalam hal apapun). Dengan penggunaan transfer, kami tidak memiliki masalah serupa dan nilai tidak akan pernah berubah dalam kasus ini (coba cari situasi ketika alamat register akan diubah dalam produksi :)). Kami kadang-kadang masih memeriksa apakah nilai yang kami dapatkan dari file/net dll berada dalam jangkauan, tetapi tidak ada masalah untuk membuat ini terpusat (lihat c# Enum.TryParseMisalnya). Dengan enum, saya menghemat waktu dan kinerja pengembangan dalam kasus ini.

Contoh 2. Saya mengembangkan beberapa modul kecil dengan logika status/kesalahan. Jika saya menjadikan enum pribadi, tidak ada yang pernah tahu tentang enum ini, dan Anda dapat mengubah/memperluasnya tanpa masalah dengan semua manfaat dari 1. Jika Anda mendasarkan kode Anda pada beberapa logika pribadi, ada yang tidak beres pada pengembangan Anda.

Contoh 3. Saya mengembangkan modul yang sering diubah dan dapat diperluas untuk berbagai aplikasi. Ini adalah solusi aneh untuk menggunakan enum, atau konstanta lain untuk menentukan logika/antarmuka publik. Jika Anda menambahkan nomor enum baru pada arsitektur client-server, Anda dapat crash, tetapi dengan konstanta Anda bisa mendapatkan status model yang tidak dapat diprediksi, dan bahkan menyimpannya ke disk. Saya lebih suka crash daripada keadaan yang tidak terduga. Ini menunjukkan kepada kita, bahwa masalah back copability/extension adalah masalah pengembangan kita bukan enum. Jika Anda memahami enum apa yang tidak cocok dalam hal ini, jangan gunakan itu. Saya pikir kami memiliki kompetensi yang cukup untuk memilih.

Perbedaan utama antara consts dan waktu kompilasi enum menurut saya adalah bahwa enum memiliki dua kontrak utama.

  1. Kontrak penamaan.
  2. Kontrak nilai.
    Semua argumen yang mendukung dan menentang paragraf ini telah dipertimbangkan sebelumnya.
    Jika Anda menggunakan pemrograman kontrak, Anda dengan mudah dapat memahami manfaat dari ini.

Enums berapa banyak hal lain yang memiliki kekurangan.
Fe itu tidak dapat diubah tanpa kemampuan rem. Tetapi jika Anda tahu O dari prinsip SOLID, ini tidak hanya berlaku untuk enum tetapi juga untuk pengembangan secara umum. Seseorang dapat mengatakan, saya membuat program saya dengan logika paralel dan struct yang bisa berubah jelek. Mari kita larang struktur yang bisa berubah? Alih-alih ini, kita dapat menambahkan struct yang dapat berubah/tidak dapat diubah dan membiarkan pengembang untuk memilih.

Setelah semua yang telah dikatakan, saya ingin mencatat bahwa Iota juga memiliki kekurangan.

  1. Itu selalu memiliki tipe int,
  2. Anda perlu menghitung nilai di kepala. Anda dapat kehilangan banyak waktu mencoba menghitung nilai, dan periksa apakah itu baik-baik saja.
    Dengan enums/const saya cukup menekan F12 dan melihat semua nilai.
  3. Ekspresi Iota adalah ekspresi kode, Anda juga perlu menguji ini.
    Dalam beberapa proyek saya sepenuhnya menolak menggunakan sedikit pun karena alasan ini.

Anda mencoba memberikan contoh di mana penggunaan enum itu konyol

Maafkan keterusterangan saya, tetapi setelah komentar ini saya rasa Anda tidak memiliki banyak alasan untuk berdiri di sini.

Dan saya bahkan tidak melakukan apa yang Anda katakan - yaitu, memberikan contoh di mana menggunakan enum itu konyol. Saya mengambil contoh yang seharusnya menunjukkan betapa mereka diperlukan, dan menggambarkan bagaimana mereka menyakitkan.

Kita bisa saja tidak setuju, tapi setidaknya kita semua harus berdebat dengan itikad baik.

Contoh 1

Saya mungkin memberi Anda "daftar nama" sebagai sesuatu yang benar-benar tidak dapat diubah, tetapi sehubungan dengan nilai protokol, saya bersikeras bahwa posisi meminta mereka mengambil nilai sewenang-wenang untuk ekstensibilitas dan kompatibilitas masuk akal. Sekali lagi, proto2 -> proto3 berisi persis perubahan itu dan ia melakukannya dari pengalaman yang dipelajari.

Dan bagaimanapun juga, saya tidak mengerti mengapa linter tidak dapat menangkap ini.

saya mendapatkan semua 20 konstanta dan jika mereka memiliki tipe yang sama dan nama yang mirip saya dapat membuat kesalahan. Jika proyek besar, Anda akan mendapatkan kesalahan ini.

Jika Anda salah mengetik nama, menutup enum tidak akan membantu Anda. Hanya jika Anda tidak menggunakan nama simbolik dan menggunakan int/string-literal sebagai gantinya.

Contoh 2

Secara pribadi, saya cenderung menempatkan "satu paket" dengan tegas pada baris "bukan proyek besar". Jadi, saya menganggap jauh lebih kecil kemungkinannya Anda melupakan kasus atau mengubah lokasi kode, saat memperluas enum.

Dan bagaimanapun juga, saya tidak mengerti mengapa linter tidak dapat menangkap ini.

Contoh 3

Itu adalah kasus penggunaan paling umum yang disajikan untuk enum. Contoh kasus: Masalah khusus ini menggunakannya sebagai pembenaran. Kasus lain yang sering disebutkan adalah syscalls - arsitektur client-server yang menyamar. Generalisasi dari contoh ini adalah "kode apa pun di mana dua atau lebih komponen yang dikembangkan secara independen bertukar nilai seperti itu", yang sangat luas, mencakup sebagian besar kasus penggunaan untuk mereka dan, di bawah model perbaikan bertahap, juga semua API yang diekspor .

FTR, saya masih belum mencoba meyakinkan siapa pun bahwa enum berbahaya (saya yakin tidak). Hanya untuk menjelaskan bagaimana saya sampai pada kesimpulan bahwa mereka dan mengapa saya menemukan argumen yang mendukung mereka tidak meyakinkan.

Itu selalu memiliki tipe int,

iota mungkin (tidak harus, tetapi apa pun), tetapi blok const tidak, mereka dapat memiliki berbagai tipe konstan - pada kenyataannya, superset dari implementasi enum yang paling umum diusulkan.

Anda perlu menghitung nilai di kepala.

Sekali lagi, Anda tidak dapat menggunakan ini sebagai argumen yang mendukung enum; anda dapat menulis konstanta seperti yang Anda bisa dalam deklarasi enum.

Ekspresi Iota adalah ekspresi kode, Anda juga perlu menguji ini.

Tidak setiap ekspresi harus diuji. Jika segera terlihat jelas, pengujian itu berlebihan. Jika tidak, tuliskan konstanta, Anda akan tetap melakukannya dalam pengujian.

iota saat ini bukan cara yang disarankan untuk melakukan enum di Go - deklarasi const adalah. iota hanya berfungsi sebagai cara yang lebih umum untuk menyimpan pengetikan saat menuliskan deklarasi const yang berurutan atau formula.

Dan ya, enum terbuka Go memiliki kekurangan, jelas. Mereka telah disebutkan di atas, secara ekstensif: Anda mungkin melupakan kasing di sakelar, yang mengarah ke bug. Mereka tidak namespace. Anda mungkin secara tidak sengaja menggunakan konstanta non-simbolis yang akhirnya menjadi nilai yang tidak valid (mengakibatkan bug).
Tetapi tampaknya lebih produktif bagi saya, untuk membicarakan kerugian ini dan mengukurnya terhadap kerugian dari setiap solusi yang diusulkan, daripada mengambil solusi tetap (tipe enum) dan berdebat tentang pengorbanan spesifiknya untuk menyelesaikan masalah.

Bagi saya, sebagian besar kerugian sebagian besar dapat diselesaikan secara pragmatis dalam bahasa saat ini, dengan alat linter yang mendeteksi deklarasi-konst dari jenis tertentu dan memeriksa penggunaannya. Penspasian nama tidak dapat diselesaikan dengan cara ini, yang tidak bagus. Tetapi mungkin ada solusi berbeda untuk masalah itu , selain enum juga.

Saya mungkin memberi Anda "daftar nama" sebagai sesuatu yang benar-benar tidak dapat diubah, tetapi sehubungan dengan nilai protokol, saya bersikeras bahwa posisi meminta mereka mengambil nilai sewenang-wenang untuk ekstensibilitas dan kompatibilitas masuk akal. Sekali lagi, proto2 -> proto3 berisi persis perubahan itu dan ia melakukannya dari pengalaman yang dipelajari.

Inilah mengapa saya mengatakan nilai-nilai yang mapan. Basis format Fe wav tidak berubah selama bertahun-tahun dan mendapatkan kemampuan kembali yang hebat. Jika di mana nilai baru Anda dapat tetap menggunakan enum dan menambahkan beberapa nilai.

Jika Anda salah mengetik nama, menutup enum tidak akan membantu Anda. Hanya jika Anda tidak menggunakan nama simbolik dan menggunakan int/string-literal sebagai gantinya.

Ya itu tidak membantu saya untuk membuat nama baik, tetapi mereka dapat membantu untuk mengatur beberapa nilai dengan satu nama. Itu membuat proses pengembangan lebih cepat dalam beberapa kasus. Itu dapat mengurangi jumlah varian dengan pengetikan otomatis menjadi satu.

Itu adalah kasus penggunaan paling umum yang disajikan untuk enum. Contoh kasus: Masalah khusus ini menggunakannya sebagai pembenaran. Kasus lain yang sering disebutkan adalah syscalls - arsitektur client-server yang menyamar. Generalisasi dari contoh ini adalah "kode apa pun di mana dua atau lebih komponen yang dikembangkan secara independen bertukar nilai seperti itu", yang sangat luas, mencakup sebagian besar kasus penggunaan untuk mereka dan, di bawah model perbaikan bertahap, juga semua API yang diekspor .

Tetapi menggunakan/tidak menggunakan konstanta/enum tidak menghapus inti masalah, Anda masih perlu memikirkan kemampuan kembali. Saya ingin mengatakan bahwa masalahnya bukan di enums/consts tetapi dalam kasus penggunaan kami.

Secara pribadi, saya cenderung menempatkan "satu paket" dengan tegas pada baris "bukan proyek besar". Jadi, saya menganggap jauh lebih kecil kemungkinannya Anda melupakan kasus atau mengubah lokasi kode, saat memperluas enum.

Dalam hal ini Anda masih memiliki manfaat dari konversi nama dan pemeriksaan waktu kompilasi,

Tidak setiap ekspresi harus diuji. Jika segera terlihat jelas, pengujian itu berlebihan. Jika tidak, tuliskan konstanta, Anda akan tetap melakukannya dalam pengujian.

Tentu saya mengerti bahwa tidak semua baris kode harus diuji, tetapi jika Anda memiliki preseden, Anda harus menguji ini atau menulis ulang. Saya tahu cara membuatnya tanpa sedikit pun, tetapi contoh lama saya hanyalah lelucon.

Sekali lagi, Anda tidak dapat menggunakan ini sebagai argumen yang mendukung enum; anda dapat menulis konstanta seperti yang Anda bisa dalam deklarasi enum.

Ini bukan argumen untuk enum.

@Merovius

Jika kita berbicara tentang enum tertutup (yang biasanya dibicarakan orang, ketika mereka berbicara tentang enum) atau enum dengan pemeriksaan lengkap, maka mereka pasti istimewa. Karena mereka tidak dapat diperpanjang.

Mereka juga tidak dapat diperpanjang dengan aman.

Jika Anda memiliki

package p
type Enum int
const (
  A Enum = iota
  B
  C
)
func Make() Enum {...}
func Take(Enum) {...}

dan

package q
import "p"
const D enum = p.C + 1

dalam q aman untuk menggunakan D (kecuali versi berikutnya dari p menambahkan labelnya sendiri untuk Enum(3) ) tetapi hanya selama Anda tidak pernah berikan kembali ke p : Anda dapat mengambil hasil dari p.Make dan mentransisikan statusnya ke D tetapi jika Anda memanggil p.Take Anda harus memastikannya tidak diteruskan q.D DAN harus memastikan itu hanya mendapat salah satu dari A , B , C atau Anda memiliki bug. Anda dapat menyiasatinya dengan melakukan

package q
import "p"
type Enum int
const (
    A = p.A
    B = p.B
    C = p.C
    D = C + 1
)
// needs to return an error if passed D or an unknown state of Enum
func To(Enum) (p.Enum, error) {...}
// needs to return an error if p.Enum has a value not known to the author
// at the time this package was written.
func From(p.Enum) (Enum, error) {...}

Dengan atau tanpa tipe tertutup dalam bahasa Anda memiliki semua masalah memiliki tipe tertutup tetapi tanpa kompiler mengawasi Anda.

Contoh Anda hanya mencakup batas-proses (dengan berbicara tentang kesalahan jaringan), bukan batas-paket. Bagaimana perilaku paket, jika saya menambahkan "InvalidInternalState" (untuk membuat sesuatu) ke FromTheNetwork? Apakah saya harus memperbaiki sakelarnya sebelum dikompilasi lagi? Maka itu tidak dapat diperluas dalam model perbaikan bertahap. Apakah mereka memerlukan kasus default untuk dikompilasi? Maka sepertinya tidak ada gunanya enum.

Hanya dengan tipe enum, Anda masih harus melakukan seperti di atas dan menentukan versi Anda sendiri dengan status ekstra dan menulis fungsi konversi.

Namun, tipe sum dapat dikomposisi bahkan ketika digunakan sebagai enum, sehingga Anda dapat "memperpanjang" satu secara alami dan aman dengan cara ini. Saya memberi contoh, tetapi untuk lebih eksplisit, diberikan

package p
type Enum pick {
  A, B, C struct{}
}

Enum dapat "diperpanjang" dengan

package q
import "p"
type Enum pick {
  P p.Enum
  D struct{}
}

dan kali ini benar-benar aman untuk menambahkan p versi baru p D . Satu-satunya downside adalah bahwa Anda harus beralih dua kali untuk mencapai status p.Enum dari dalam q.Enum tetapi eksplisit dan jelas dan, seperti yang saya sebutkan, editor Anda dapat meludahkan kerangka dari switch keluar secara otomatis.

Tetapi untuk itu kita tidak memerlukan enum yang sebenarnya, sebagai tipe. Alat linting semacam itu juga dapat secara heuristik memeriksa deklarasi const menggunakan sedikit pun, di mana setiap case memiliki tipe tertentu dan menganggapnya sebagai "enum" dan melakukan pemeriksaan yang Anda inginkan. Saya akan sepenuhnya menggunakan alat yang menggunakan "enum menurut konvensi" ini untuk membantu pelengkapan otomatis atau linting bahwa setiap sakelar harus memiliki default atau bahkan bahwa setiap kasing (dikenal) harus diperiksa.

Ada dua masalah dengan ini.

Jika Anda memiliki tipe integral yang ditentukan dengan label yang diberikan ke subset domainnya melalui const/iota:

Satu, itu bisa mewakili enum tertutup atau terbuka. Meskipun sebagian besar digunakan untuk mensimulasikan tipe tertutup, itu juga dapat digunakan hanya untuk memberi nama pada nilai yang umum digunakan. Pertimbangkan enum terbuka untuk format file imajiner:

const (
  //Name is the ID of a record field
  Name Record = iota
  //EmpID is the ID of an employee ID field
  EmpID

  //Intermediate values are reserved for future versions

  //Custom is the base of custom fields. Any custom field must have a unique ID greater than Custom.
  Custom Record = 42
)

Ini tidak berarti bahwa 0, 1, dan 42 adalah domain dari tipe Record. Kontrak jauh lebih halus dan akan membutuhkan tipe dependen untuk dimodelkan. (Itu pasti akan terlalu jauh!)

Kedua, kita dapat mengasumsikan secara heuristik bahwa tipe integral terdefinisi dengan label konstan berarti domain dibatasi. Itu akan mendapatkan positif palsu dari atas, tapi tidak ada yang sempurna. Kita dapat menggunakan go/types untuk mengekstrak pseudo-type ini dari definisi dan kemudian menelusuri dan menemukan semua nilai switch over dari tipe tersebut dan memastikan bahwa semuanya berisi label yang diperlukan. Ini mungkin membantu, tetapi kami belum menunjukkan kelengkapannya pada saat ini. Kami telah memastikan cakupan semua nilai yang valid tetapi tidak membuktikan bahwa tidak ada nilai yang tidak valid telah dibuat. Melakukannya tidak mungkin. Bahkan jika kami dapat menemukan setiap sumber, sink, dan transformasi nilai dan secara abstrak menafsirkannya untuk menjamin secara statis bahwa tidak ada nilai yang tidak valid yang dibuat, kami masih tidak akan dapat mengatakan apa pun tentang nilai saat runtime karena reflect tidak mengetahui nilai yang sebenarnya. domain tipe karena tidak dikodekan dalam sistem tipe.

Ada alternatif untuk tipe enum dan sum di sini yang mengatasi ini, meskipun memiliki masalah sendiri.

Katakanlah tipe literal range m n membuat tipe integral yang paling sedikit m dan paling banyak n (Untuk semua v, m v n). Dengan itu kita bisa membatasi domain enum, seperti

package p
type Enum range 0 2
const (
  A Enum = iota
  B
  C
)

Karena ukuran domain = jumlah label, lint dapat dilakukan dengan keyakinan 100% apakah pernyataan switch menghabiskan semua kemungkinan. Untuk memperluas enum itu secara eksternal, Anda benar-benar perlu membuat fungsi konversi tipe untuk menangani pemetaan, tetapi saya tetap berpendapat bahwa Anda tetap harus melakukannya.

Tentu saja, itu sebenarnya keluarga tipe yang sangat halus untuk diterapkan dan tidak akan bermain dengan baik dengan sisa Go. Itu juga tidak memiliki banyak kegunaan di luar ini dan beberapa kasus penggunaan khusus.

Kita bisa tidak setuju tentang pentingnya menjaga perbaikan bertahap dan kemungkinan diperpanjang - misalnya, banyak orang percaya bahwa versi semantik adalah solusi yang lebih baik untuk masalah yang dipecahkannya. Tetapi jika Anda menganggapnya penting, sangat valid dan masuk akal untuk melihat enum sebagai berbahaya atau tidak berguna. Dan itulah pertanyaan yang saya balas: Bagaimana orang dapat secara wajar melakukan tradeoff dengan membutuhkan cek di mana-mana, alih-alih memasukkannya ke dalam kompiler. Jawaban: Dengan menilai ekstensibilitas dan evolusi API, yang membuat pemeriksaan ini tetap diperlukan di situs penggunaan.

Untuk tipe enum dasar, saya setuju. Pada awal diskusi ini, saya hanya akan tidak senang jika mereka dipilih daripada jenis jumlah, tetapi sekarang saya mengerti mengapa mereka berbahaya. Terima kasih kepada Anda dan @griesemer karena telah mengklarifikasi itu untuk saya.

Untuk tipe penjumlahan, saya pikir apa yang Anda katakan adalah alasan yang sah untuk tidak mengharuskan sakelar menjadi lengkap pada waktu kompilasi. Saya masih berpikir bahwa tipe tertutup memiliki sejumlah manfaat dan dari tiga yang dibahas di sini, tipe penjumlahan adalah yang paling fleksibel tanpa kekurangan yang lain. Mereka mengizinkan suatu tipe untuk ditutup tanpa menghambat ekstensibilitas atau perbaikan bertahap sambil menghindari kesalahan yang disebabkan oleh nilai ilegal, seperti halnya tipe bagus lainnya.

Alasan utama saya menggunakan golang daripada python dan javascript dan bahasa tanpa tipe umum lainnya adalah keamanan tipe. Saya melakukan banyak hal dengan Java dan satu hal yang saya lewatkan di golang yang disediakan Java adalah enum yang aman.

Saya tidak setuju untuk dapat membedakan tipe dengan enum. Jika Anda membutuhkan int, cukup gunakan int, seperti yang dilakukan Java. Jika Anda membutuhkan enum yang aman, saya sarankan sintaks berikut.

type enums enum { foo, bar, baz }

@rudolfschmidt , saya setuju dengan Anda, mungkin terlihat seperti itu juga:

type DaysOfTheWeek enum {
  Monday
  Tuesday
}

Tetapi ini memiliki kelemahan kecil - kita harus dapat mengendalikan enum jika kita harus memvalidasi data, mengubahnya menjadi JSON atau berinteraksi dengan atau FS.
Jika kita secara membabi buta berasumsi bahwa enum adalah himpunan bilangan bulat yang tidak ditandatangani, kita dapat berakhir dengan sedikit pun.
Jika kita ingin berinovasi kita harus memikirkan kenyamanan penggunaan.
Misalnya, seberapa mudah saya dapat memvalidasi nilai di dalam JSON yang masuk adalah elemen enum yang valid?
Bagaimana jika saya memberi tahu Anda bahwa perangkat lunak sedang berubah?

Katakanlah kita memiliki daftar cryptocurrency:

type CryptoCurrency enum {
  BTC
  ETH
  XMR
}

Kami bertukar data dengan beberapa sistem pihak ketiga. Katakanlah Anda memiliki ribuan dari mereka.
Anda memiliki sejarah panjang, jumlah data dalam penyimpanan. Waktu berlalu, katakanlah, BitCoin akhirnya mati. Tidak ada yang menggunakannya.
Jadi, Anda memutuskan untuk menghapusnya dari struktur:

type CryptoCurrency enum {
  ETH
  XMR
}

Hal ini menyebabkan perubahan data. Karena semua nilai enum bergeser. Tidak apa-apa untuk Anda. Anda dapat menjalankan migrasi untuk data Anda. Bagaimana dengan mitra Anda, beberapa dari mereka tidak bergerak secepat itu, beberapa tidak memiliki sumber daya atau tidak dapat melakukannya karena beberapa alasan.
Tetapi Anda telah menyerap data dari mereka. Jadi Anda akan memiliki 2 enum: lama dan baru; dan pemetaan data menggunakan keduanya.
Itu memberitahu kita untuk memberikan fleksibilitas definisi untuk enum dan kemampuan untuk memvalidasi dan menyusun/membuka data semacam itu.

type CryptoCurrency enum {
  ETH = 1, // reminds const?
  XMR = 2
}
// this is real life case 
v := 3
if v is CryptoCurrency {
 // right?
} else {
 // nope, provide default value
 v = CryptoCurrency.ETH
}

Kita harus memikirkan penerapan enum dan kasus penggunaan.

Kita dapat mempelajari 2 kata kunci baru jika itu dapat menyelamatkan kita dari ribuan baris kode boilerplate.

Jalan tengah memang memiliki kemampuan untuk memvalidasi nilai enum tanpa membatasi nilai-nilai itu. Jenis enum cukup banyak tetap sama - ini adalah sekelompok konstanta bernama. Variabel tipe enum dapat sama dengan nilai apa pun dari tipe dasar enum. Apa yang Anda tambahkan di atas itu adalah kemampuan untuk memvalidasi nilai untuk dilihat, apakah itu mengandung nilai enum yang valid atau tidak. Dan mungkin ada bonus lain seperti stringifikasi.

Sangat sering saya berada dalam situasi di mana saya memiliki protokol (protobuf atau hemat) dengan banyak enum di semua tempat. Saya harus memvalidasi masing-masing dari mereka dan, jika nilai enum tidak saya ketahui, buang pesan itu dan laporkan kesalahan. Tidak ada cara lain untuk menangani pesan seperti itu. Dengan bahasa di mana enum hanyalah sekelompok konstanta, saya tidak punya cara lain selain menulis sejumlah besar pernyataan sakelar yang memeriksa semua kemungkinan kombinasi. Itu sejumlah besar kode dan kesalahan pasti ada di dalamnya. Dengan sesuatu seperti C# saya dapat menggunakan dukungan bawaan untuk memvalidasi enum yang menghemat banyak waktu. Beberapa implementasi protobuf sebenarnya melakukan itu secara internal dan membuang pengecualian jika itu masalahnya. Belum lagi betapa mudahnya logging - Anda mendapatkan stringifikasi di luar kotak. Sangat menyenangkan bahwa protobuf menghasilkan implementasi Stringer untuk Anda tetapi tidak semua yang ada dalam kode Anda adalah protobuf.

Tetapi kemampuan untuk menyimpan nilai apa pun sangat membantu dalam kasus lain di mana Anda tidak ingin membuang pesan tetapi melakukan sesuatu dengannya meskipun itu tidak valid. Klien biasanya dapat membuang pesan tetapi di sisi server seringkali Anda perlu menyimpan semuanya dalam database. Membuang konten bukanlah pilihan di sana.

Jadi bagi saya ada nilai nyata dalam kemampuan untuk memvalidasi nilai enum. Itu akan menyelamatkan saya ribuan baris kode boilerplate yang tidak melakukan apa-apa selain validasi.

Tampaknya cukup sederhana bagi saya, untuk menyediakan fungsionalitas ini sebagai alat. Sebagian sudah ada di stringer-tool. Jika ada alat yang Anda panggil seperti enumer Foo , yang akan menghasilkan metode fmt.Stringer untuk Foo dan (katakanlah) metode Known() bool yang memeriksa apakah nilai tersimpan berada dalam kisaran nilai yang diketahui, apakah itu akan meringankan masalah Anda?

@Merovius dengan tidak adanya hal lain itu akan berguna. Tapi saya umumnya menentang autogen. Satu-satunya kasus di mana itu berguna dan hanya berfungsi adalah hal-hal seperti protobuf di mana Anda memiliki protokol yang cukup stabil yang dapat dikompilasi sekali. Menggunakannya untuk enum secara umum terasa seperti penopang untuk sistem tipe sederhana. Dan melihat bagaimana Go adalah tentang keamanan tipe, yang terasa bertentangan dengan filosofi bahasa itu sendiri. Alih-alih membantu bahasa, Anda mulai mengembangkan infrastruktur ini di atasnya yang sebenarnya bukan bagian dari ekosistem bahasa. Tinggalkan alat eksternal untuk pemeriksaan, bukan untuk menerapkan apa yang hilang dari bahasa.

. Menggunakannya untuk enum secara umum terasa seperti penopang untuk sistem tipe sederhana.

Karena itu - sistem tipe Go terkenal dan sengaja dibuat sederhana. Tapi itu bukan pertanyaannya, pertanyaannya adalah, apakah itu akan meringankan masalah Anda. Terlepas dari "Saya tidak menyukainya", saya tidak benar-benar melihat bagaimana tidak (jika Anda tetap menganggap enum terbuka).

Dan melihat bagaimana Go adalah tentang keamanan tipe, yang terasa bertentangan dengan filosofi bahasa itu sendiri.

Go bukan "semua tentang keamanan tipe". Bahasa seperti Idris adalah tentang keamanan tipe. Go adalah tentang masalah rekayasa skala besar dan karena itu desainnya didorong oleh masalah yang coba dipecahkannya. Misalnya, sistem tipenya memungkinkan untuk menangkap berbagai bug karena perubahan API dan memungkinkan beberapa refactoring skala besar. Tetapi juga sengaja dibuat sederhana, untuk memudahkan pembelajaran, mengurangi perbedaan basis kode dan meningkatkan keterbacaan kode pihak ketiga.

Dengan demikian, jika kasus penggunaan yang Anda minati (open enum) dapat diselesaikan tanpa perubahan bahasa, dengan alat yang menghasilkan kode yang mudah dibaca, maka itu tampaknya sangat sejalan dengan filosofi Go. Secara khusus, menambahkan fitur bahasa baru yang merupakan bagian dari fungsionalitas yang sudah ada tampaknya tidak sejalan dengan desain Go.

Jadi, untuk mengulangi: Akan sangat membantu jika Anda dapat memperluas bagaimana menggunakan alat yang menghasilkan boilerplate yang Anda khawatirkan tidak menyelesaikan masalah yang sebenarnya - jika tidak ada yang lain, maka karena pemahaman yang diperlukan untuk menginformasikan desain fitur tersebut. .

Saya telah menggabungkan beberapa ide dari diskusi, bagaimana menurut Anda?

Beberapa informasi dasar:

  1. Anda dapat memperluas enum seperti setiap jenis lainnya.
  2. Mereka disimpan seperti konstanta, tetapi dengan nama tipe sebagai awalan. Alasan: Saat menggunakan iota-enums saat ini, Anda mungkin akan menulis nama enum sebagai awalan dari setiap konstanta. Dengan fitur ini Anda bisa menghindarinya.
  3. Mereka tidak dapat diubah dan diperlakukan seperti konstanta lainnya.
  4. Anda dapat mengulangi enum. Ketika Anda melakukan ini, mereka berperilaku seperti peta. Kuncinya adalah nama enum, nilainya adalah nilai enum.
  5. Anda dapat menambahkan metode ke enum, seperti yang Anda lakukan untuk setiap jenis lainnya.
  6. Setiap nilai enum memiliki metode yang dibuat secara otomatis:
  7. Name() akan mengembalikan nama variabel enum
  8. Index() akan mengembalikan indeks enum, yang secara otomatis meningkat. Itu dimulai di mana array dimulai.

Kode:
``` pergi
paket utama

//Contoh A
type Country enum[struct] { //enums dapat memperluas tipe lain (lihat contoh B)
Austria("AT", "Austria", false) //Akan dapat diakses seperti const, tetapi dengan tipe as
Jerman("DE", "Jerman", benar) //awalan (mis. Negara.Austria)

//The struct will automatically begin when it doesn't match the format EnumName(...) anymore
Code, CountryName string
HasMerkel bool //Totally awesome

}

// Enum dapat memiliki metode seperti semua tipe lainnya
func (c Negara) test() {}

fungsi utama() {
println(Negara.Austria.NamaNegara) //Austria
println(Negara.Jerman.Kode) //DE

/* Prints:
Austria
0
Germany
1
 */
for name, country := range Country {
    println(name) //Austria
    println(name == country.Name()) //true ; also autogenerated 
    println(country.Index()) //Auto generated increasing index
}

}

//Contoh B
ketik Coolness enum[int] {
Sangat Keren(10)
keren (5)
Tidak Keren(0)
}```

@sinnlosername Saya pikir enum harus menjadi sesuatu yang sangat mudah dimengerti. Menggabungkan beberapa ide yang disajikan dalam diskusi sebelumnya mungkin tidak selalu mengarah pada ide terbaik untuk sebuah enum.

Saya percaya bahwa yang berikut ini akan mudah dipahami:

Pernyataan

type Day enum {
    Monday
    Tuesday
    ...
    Sunday
}

Konversi String (menggunakan antarmuka Stringer ):

func (d Day) String() string {
    switch d {
    case Monday:
        return "mon"
    case Tuesday:
        return "tues"
    ...
    case Sunday:
        return "sun"
    }
}

Sesederhana itu. Manfaat dari ini adalah memungkinkan keamanan tipe yang lebih kuat saat melewati enum.

Contoh Penggunaan

func IsWeekday(d Day) bool {
    return d != Saturday && d != Sunday
}

Jika saya menggunakan konstanta string di sini untuk mewakili Day , IsWeekday akan mengatakan bahwa string apa pun yang bukan "sat" atau "sun" adalah hari kerja (yaitu, apa yang akan/harus dikembalikan IsWeekday("abc") ?). Sebaliknya, domain dari fungsi yang ditampilkan di atas dibatasi, sehingga memungkinkan fungsi tersebut lebih masuk akal sehubungan dengan inputnya.

@ljeabmreosn

itu mungkin harus

func IsWeekday(d Day) bool {
    return d != Day.Saturday && d != Day.Sunday
}

Saya menyerah menunggu tim golang untuk meningkatkan bahasa dengan cara yang diperlukan. Saya semua orang dapat merekomendasikan untuk melihat rust lang, ia sudah memiliki semua fitur yang diinginkan seperti enum dan obat generik dan banyak lagi.

Kami berada di 14. Mei 2018 dan kami masih membahas tentang dukungan enum. Maksudku apa sih? Secara pribadi saya kecewa dengan golang.

Saya dapat memahami bahwa ini bisa membuat frustrasi saat menunggu fitur. Tetapi memposting komentar non-konstruktif seperti ini tidak membantu. Harap menjaga komentar Anda hormat. Lihat https://golang.org/conduct.

Terima kasih.

@agnivade Saya harus setuju dengan @rudolfschmidt. GoLang jelas bukan bahasa favorit saya juga karena kurangnya fitur, API, dan terlalu banyak penolakan untuk mengubah atau menerima kesalahan masa lalu oleh pembuat Go. Tetapi saya tidak punya pilihan saat ini karena saya bukan pembuat keputusan tentang bahasa apa yang harus dipilih untuk proyek terbaru saya di tempat kerja saya. Jadi saya harus bekerja dengan segala kekurangannya. Tapi jujur ​​itu seperti menyiksa menulis kode di GoLang ;-)

Saya menyerah menunggu tim golang untuk meningkatkan bahasa dengan cara yang diperlukan.

  • Kata perlu tidak berarti "apa yang saya inginkan".

Sebenarnya fitur inti dari setiap bahasa modern diperlukan. GoLang memiliki beberapa fitur bagus, tetapi tidak akan bertahan jika proyek tetap konservatif. Fitur seperti enum atau generik tidak memiliki kerugian bagi orang yang tidak menyukainya, tetapi fitur tersebut memiliki banyak keuntungan bagi orang yang ingin menggunakannya.

Dan jangan bilang "tapi pergi ingin tetap sederhana". Ada perbedaan besar antara "sederhana" dan "tidak ada fitur nyata". Java sangat sederhana tetapi banyak fitur yang hilang. Jadi baik pengembang Java adalah penyihir atau argumen ini buruk.

Sebenarnya fitur inti dari setiap bahasa modern diperlukan.

Tentu. Fitur-fitur inti itu disebut kelengkapan Turing. _Semuanya_ lainnya adalah pilihan desain. Ada banyak ruang antara kelengkapan Turing dan C++ (misalnya) dan di ruang itu Anda dapat menemukan banyak bahasa. Distribusi menunjukkan tidak ada optimal global.

GoLang memiliki beberapa fitur bagus, tetapi tidak akan bertahan jika proyek tetap konservatif.

Mungkin. Sampai sekarang masih terus berkembang. IMO, itu tidak akan tumbuh jika tidak konservatif. Pendapat kami berdua subjektif dan secara teknis tidak terlalu berharga. Ini adalah pengalaman dan selera para desainer yang mengatur. Tidak apa-apa untuk memiliki pendapat yang berbeda, tetapi itu tidak dapat menjamin bahwa para desainer akan membagikannya.

BTW, jika saya membayangkan seperti apa Go hari ini jika 10% dari fitur yang diminta orang diadopsi, sekarang saya mungkin tidak akan menggunakan Go lagi.

Sebenarnya Anda baru saja melewatkan argumen paling penting dari jawaban saya. Mungkin karena itu sudah bertentangan dengan beberapa hal yang Anda katakan.

"Fitur seperti enum atau generik tidak memiliki kerugian bagi orang yang tidak menyukainya, tetapi mereka memiliki banyak keuntungan bagi orang yang ingin menggunakannya."

Dan menurut Anda mengapa sikap konservatif ini menjadi alasan tumbuhnya golang? Saya pikir ini lebih mungkin terkait dengan efisiensi golang dan kumpulan perpustakaan standar yang hebat.

Java juga mengalami semacam "crash" ketika mereka mencoba mengubah hal-hal penting di Java 9, yang mungkin menyebabkan banyak orang mencari alternatif. Tapi lihatlah Java sebelum crash ini. Itu terus berkembang karena mendapat lebih banyak fitur yang membuat kehidupan pengembang lebih mudah.

"Fitur seperti enum atau generik tidak memiliki kerugian bagi orang yang tidak menyukainya, tetapi mereka memiliki banyak keuntungan bagi orang yang ingin menggunakannya."

Itu sangat jelas tidak benar. Setiap fitur pada akhirnya akan sampai ke stdlib dan/atau paket yang ingin saya impor. _Setiap orang_ harus berurusan dengan fitur-fitur baru terlepas dari suka atau tidak suka.

Sampai sekarang masih terus berkembang. IMO, itu tidak akan tumbuh jika tidak konservatif

Saya tidak berpikir pertumbuhannya yang lambat (jika ada) adalah karena kekonservatifan melainkan perpustakaan standar, set fitur bahasa yang sudah ada, perkakas. Itulah yang membawa saya ke sini. Menambahkan fitur bahasa tidak akan mengubah apa pun bagi saya dalam hal itu.

Jika kita melihat C# dan TypeScript atau bahkan Rust/Swift. Mereka menambahkan fitur baru seperti gila. C# masih dalam bahasa teratas berfluktuasi naik turun. TypeScript berkembang sangat cepat. Sama untuk Rust/Swift. Go, di sisi lain, meledak dalam popularitas pada tahun 2009 dan 2016. Namun antara itu tidak tumbuh sama sekali dan benar-benar kalah. Go tidak memiliki apa-apa untuk diberikan kepada pengembang baru jika mereka sudah mengetahuinya dan tidak memilihnya lebih awal karena suatu alasan. Tepat karena Go mengalami stagnasi dalam desainnya. Bahasa lain menambahkan fitur bukan karena mereka tidak memiliki hal lain untuk dilakukan tetapi karena basis pengguna yang sebenarnya menuntutnya. Orang membutuhkan fitur baru agar basis kode mereka tetap relevan dalam domain masalah yang terus berubah. Seperti async/menunggu. Itu diperlukan untuk memecahkan masalah yang sebenarnya. Tidak mengherankan bahwa Anda dapat melihatnya dalam banyak bahasa sekarang.

Pada akhirnya akan ada Go 2 dan Anda dapat benar-benar yakin itu akan membawa banyak pengembang baru. Bukan karena baru dan mengkilap, tetapi karena fitur baru mungkin meyakinkan seseorang untuk akhirnya beralih atau mencobanya. Jika kekonservatifan begitu penting, kami bahkan akan memiliki proposal ini.

Saya tidak berpikir pertumbuhannya yang lambat (jika ada) adalah karena kekonservatifan melainkan perpustakaan standar, set fitur bahasa yang sudah ada, perkakas. Itulah yang membawa saya ke sini.

Dan itulah hasil menjadi konservatif. Jika bahasa memecahkan sesuatu/semuanya setiap [setengah] tahun atau lebih, Anda tidak akan mendapatkan apa pun dari apa yang Anda katakan Anda hargai tentang Go karena akan jauh lebih sedikit orang yang membawakan Anda itu.

Menambahkan fitur bahasa tidak akan mengubah apa pun bagi saya dalam hal itu.

Apa kamu yakin akan hal itu? Lihat di atas.


BTW, sudah lihat hasil Survey 2017 ?

Jika bahasa merusak sesuatu/semuanya setiap [setengah] tahun atau lebih

Maka jangan merusak apa pun. C# menambahkan banyak fitur dan tidak pernah melanggar kompatibilitas mundur. Itu juga bukan pilihan bagi mereka. Sama untuk C++ saya pikir. Jika Go tidak dapat menambahkan fitur tanpa merusak sesuatu, maka itu masalah dengan Go dan, mungkin, bagaimana penerapannya.

BTW, sudah lihat hasil Survey 2017?

Komentar saya didasarkan pada survei 2017/2018, indeks TIOBE, dan pengamatan umum saya tentang apa yang terjadi dengan berbagai bahasa.

@cznic
Setiap orang harus berurusan dengan mereka, tetapi Anda tidak perlu menggunakannya. Jika Anda lebih suka menulis kode Anda dengan sedikit enum dan peta, Anda masih bisa melakukannya. Dan jika Anda tidak menyukai obat generik, gunakan perpustakaan tanpa mereka. Java membuktikan kemungkinan untuk memiliki keduanya.

Maka jangan merusak apa pun.

Ide bagus. Namun, banyak, jika bukan sebagian besar perubahan yang diusulkan pada bahasa, _termasuk yang satu ini_, melanggar perubahan.

C# menambahkan banyak fitur dan tidak pernah melanggar kompatibilitas mundur.

Silakan periksa fakta Anda: Visual C# 2010 Breaking Changes . (hasil pencarian web pertama, saya hanya bisa menebak apakah itu satu-satunya contoh atau bukan.)

Komentar saya didasarkan pada survei 2017/2018, indeks TIOBE, dan pengamatan umum saya tentang apa yang terjadi dengan berbagai bahasa.

Nah, bagaimana Anda bisa melihat bahasa tidak berkembang sementara hasil survei menunjukkan pertumbuhan 70% dari tahun ke tahun dari jumlah responden?

Bagaimana Anda mendefinisikan "perubahan yang melanggar"? Setiap baris kode go akan tetap berfungsi setelah menambahkan enum atau generik.

Setiap orang harus berurusan dengan mereka, tetapi Anda tidak perlu menggunakannya. Jika Anda lebih suka menulis kode Anda dengan sedikit enum dan peta, Anda masih bisa melakukannya. Dan jika Anda tidak menyukai obat generik, gunakan perpustakaan tanpa mereka. Java membuktikan kemungkinan untuk memiliki keduanya.

Saya tidak bisa setuju. Begitu bahasanya didapat, misalnya obat generik, maka meskipun saya mungkin tidak menggunakannya, mereka akan digunakan di mana-mana. Bahkan ketika secara internal tanpa mengubah API. Hasilnya adalah saya sangat terpengaruh oleh mereka, karena pasti tidak ada cara untuk menambahkan obat generik ke bahasa tanpa memperlambat pembangunan program apa pun yang menggunakannya. Alias ​​"Tidak ada makan siang gratis".

Bagaimana Anda mendefinisikan "perubahan yang melanggar"? Setiap baris kode go akan tetap berfungsi setelah menambahkan enum atau generik.

Tentu saja tidak. Kode ini tidak akan dikompilasi lagi dengan proposal ini:

package foo

var enum = 42

Kata perlu tidak berarti "apa yang saya inginkan".

tentu, itu tidak berarti, dan saya tidak pernah bersungguh-sungguh. Tentu saja Anda dapat menjawab bahwa fitur tersebut tidak diperlukan, tetapi kemudian saya dapat menjawab apa yang diperlukan secara umum. Tidak ada yang perlu dan kita bisa kembali ke pena dan kertas.

Golang mengklaim sebagai bahasa untuk tim besar. Saya tidak yakin apakah Anda dapat menggunakan golang untuk mengembangkan basis kode yang besar. Untuk itu Anda perlu kompilasi statis dan pengecekan tipe untuk menghindari kesalahan runtime sebanyak mungkin. Bagaimana Anda bisa melakukannya tanpa enum dan obat generik? Fitur-fitur itu bahkan tidak mewah atau bagus untuk dimiliki tetapi mutlak penting untuk pengembangan yang serius. Jika Anda tidak memilikinya, Anda akhirnya menggunakan antarmuka{} di mana-mana. Apa gunanya memiliki tipe data jika Anda terpaksa menggunakan antarmuka{} dalam kode Anda?

Tentu jika Anda tidak punya pilihan, Anda juga akan melakukannya, tetapi mengapa Anda harus melakukannya jika Anda memiliki alternatif seperti rust yang sudah menawarkan semua itu dan bahkan lebih cepat dalam eksekusi daripada yang bisa dilakukan golang? Saya benar-benar bertanya-tanya apakah go memiliki masa depan dengan pola pikir seperti itu:

Kata perlu tidak berarti "apa yang saya inginkan".

Saya menghargai semua kontribusi untuk open source dan jika golang adalah proyek hobi tidak apa-apa, tetapi golang ingin dianggap serius dan saat ini lebih merupakan mainan untuk beberapa pengembang yang bosan dan saya tidak melihat keinginan untuk mengubahnya.

Api tidak perlu diubah, hanya bagian api baru yang mungkin menggunakan generik, tetapi mungkin selalu ada alternatif tanpa generik di internet.

Dan keduanya, kompilasi yang sedikit lebih lambat dan variabel yang disebut "enum" adalah efek minimal. Sebenarnya 99% orang bahkan tidak akan menyadarinya dan 1% lainnya hanya perlu menambahkan beberapa perubahan kecil yang dapat ditoleransi. Ini tidak sebanding dengan misalnya jigsaw java yang fu... up semuanya.

Dan keduanya, kompilasi yang sedikit lebih lambat dan variabel yang disebut "enum" adalah efek minimal. Sebenarnya 99% orang bahkan tidak akan menyadarinya dan 1% lainnya hanya perlu menambahkan beberapa perubahan kecil yang dapat ditoleransi.

Semua orang akan senang jika seseorang dapat datang dengan desain dan implementasi yang memiliki kinerja yang luar biasa. Silakan berkontribusi ke #15292.

Namun, jika ini adalah permainan bernama "menarik nomor apa pun yang menguntungkan saya tanpa data pendukung apa pun" daripada maaf, tetapi saya tidak berpartisipasi.

Apakah Anda punya angka untuk perbedaan kecepatan dengan obat generik?

Dan ya, angka-angka itu tidak didukung oleh data apa pun, karena mereka hanya akan memberi tahu bahwa kemungkinan memiliki variabel yang disebut "enum" tidak terlalu tinggi.

Saya ingin mengingatkan semua orang bahwa ada banyak orang yang berlangganan masalah ini untuk pertanyaan spesifik apakah dan bagaimana enum dapat ditambahkan ke Go. Pertanyaan umum "apakah bahasa Go adalah bahasa yang baik?" dan "haruskah Go lebih fokus dalam menghadirkan fitur?" mungkin lebih baik dibahas di forum yang berbeda.

Apakah Anda punya angka untuk perbedaan kecepatan dengan obat generik?

Tidak, itu sebabnya saya tidak memposting apa pun. Saya hanya memposting, bahwa biayanya tidak boleh nol.

Dan ya, angka-angka itu tidak didukung oleh data apa pun, karena mereka hanya akan memberi tahu bahwa kemungkinan memiliki variabel yang disebut "enum" tidak terlalu tinggi.

Itu campur aduk. Perlambatan adalah tentang obat generik. "enum" adalah tentang kompatibilitas mundur dan "_Setiap_ baris kode go palsu Anda akan tetap berfungsi setelah menambahkan enum atau obat generik." mengeklaim. (menekankan milikku)

@Merovius Anda benar, saya sekarang tutup mulut.

Membawa ini kembali ke tipe enum, yang merupakan topik masalah ini, saya sepenuhnya memahami argumen mengapa Go membutuhkan obat generik, tetapi saya jauh lebih goyah pada argumen mengapa Go membutuhkan tipe enum. Sebenarnya, saya menanyakan ini di atas di https://github.com/golang/go/issues/19814#issuecomment -290878151 dan saya masih ragu. Jika ada jawaban yang bagus untuk itu, saya melewatkannya. Bisakah seseorang mengulanginya atau menunjukkannya? Terima kasih.

@ianlancetaylor Saya tidak berpikir kasus penggunaan rumit, menginginkan jenis cara yang aman untuk memastikan bahwa suatu nilai milik set nilai yang telah ditentukan, yang tidak mungkin hari ini di Go. Satu-satunya solusi adalah memvalidasi secara manual di setiap titik masuk yang memungkinkan ke dalam kode Anda, termasuk RPC dan panggilan fungsi, yang secara inheren tidak dapat diandalkan. Kehalusan sintaksis lainnya untuk iterasi membuat banyak kasus penggunaan umum lebih mudah. Apakah Anda menemukan itu berharga atau tidak itu subjektif, dan jelas tidak ada argumen di atas yang menarik untuk kekuatan itu, jadi saya pada dasarnya menyerah pada hal ini yang pernah ditangani pada tingkat bahasa.

@ianlancetaylor : semuanya ada hubungannya dengan keamanan tipe. Anda menggunakan jenis untuk meminimalkan risiko kesalahan runtime karena salah ketik atau menggunakan jenis yang tidak kompatibel. Saat ini Anda dapat menulis di go

if enumReference == 1

karena saat ini enum hanyalah angka atau tipe data primitif lainnya.

Kode itu seharusnya tidak mungkin sama sekali dan harus dihindari. Diskusi yang sama yang Anda lakukan di komunitas Java bertahun-tahun yang lalu, itulah alasan mereka memperkenalkan enum karena mereka memahami pentingnya.

Anda seharusnya hanya bisa menulis

if enumReference == enumType

Anda tidak perlu terlalu banyak fantasi untuk membayangkan di mana skenario if enumReference == 1 dapat terjadi dengan cara yang lebih tersembunyi dan menyebabkan masalah tambahan yang hanya akan Anda lihat saat runtime.

Saya hanya ingin menyebutkan: Go memiliki potensi, tetapi aneh bahwa hal-hal dan konsep yang terbukti dan dipahami sejak bertahun-tahun dibahas di sini seperti Anda membahas konsep atau paradigma baru pemrograman. Jika Anda memiliki cara alternatif untuk memastikan keamanan jenis, mungkin ada sesuatu yang lebih baik daripada enum tetapi saya tidak melihatnya.

Go memiliki potensi, tetapi aneh bahwa hal-hal dan konsep yang terbukti dan dipahami sejak bertahun-tahun dibahas di sini seperti Anda membahas konsep atau paradigma baru pemrograman.

Afais, terutama saat mengikuti diskusi lain tentang obat generik, tipe penjumlahan dll... ini bukan tentang apakah akan memilikinya, tetapi bagaimana menerapkannya. Sistem tipe Javas sangat extensible dan spec'ed baik. Itu adalah perbedaan besar.

Di Go, orang-orang mencoba mencari cara untuk menambahkan fitur ke bahasa, sementara tidak meningkatkan kompleksitas kompiler. Itu biasanya tidak bekerja dengan baik dan membuat mereka meninggalkan ide-ide awal itu.

Sementara saya juga berpikir bahwa prioritas tersebut agak tidak masuk akal dalam bentuk dan kualitasnya saat ini, upaya terbaik Anda adalah membuat implementasi yang paling sederhana dan paling tidak mengganggu . Hal lain tidak akan membuat Anda lebih jauh, imo.

@derekperkins @rudolfschmidt Terima kasih. Saya ingin memperjelas bahwa meskipun C++ memiliki tipe enum, fitur yang Anda sarankan tidak ada di C++. Jadi tidak ada yang jelas tentang ini.,

Secara umum jika variabel tipe enum hanya dapat menerima nilai enum itu, itu tidak akan berguna. Secara khusus harus ada konversi dari integer sewenang-wenang ke tipe enum; jika tidak, Anda tidak dapat mengirim enum melalui koneksi jaringan. Ya, Anda bisa, tetapi Anda harus menulis sakelar dengan kasing untuk setiap nilai enum, yang tampaknya sangat membosankan. Jadi ketika melakukan konversi, apakah kompiler menghasilkan pemeriksaan bahwa nilainya adalah nilai enum yang valid selama konversi tipe? Dan apakah panik jika nilainya tidak valid?

Apakah nilai enum harus berurutan, atau dapatkah mereka mengambil nilai apa pun seperti pada C++?

Di Go, konstanta tidak diketik, jadi jika kami mengizinkan konversi dari bilangan bulat ke tipe enum, akan aneh untuk melarang if enumVal == 1 . Tapi saya kira kita bisa.

Salah satu prinsip desain umum Go adalah orang yang menulis kode Go menulis, bukan mengetik. Saya belum melihat keuntungan apa pun yang datang dari tipe enum yang membantu kami saat menulis kode. Mereka tampaknya menambahkan satu set batasan tipe dari jenis yang biasanya tidak kita miliki di Go. Baik atau buruk, Go tidak menyediakan mekanisme untuk mengontrol nilai tipe. Jadi saya harus mengatakan bahwa bagi saya argumen yang mendukung penambahan enum ke Go tampaknya belum meyakinkan.

Saya akan mengulanginya sendiri tetapi saya lebih suka mempertahankan enum seperti sekarang ini dan menambahkan fitur di atasnya:

  • tipe enum memiliki tipe nilai yang mendasarinya dan beberapa konstanta bernama yang terkait dengannya
  • kompiler harus mengizinkan konversi dari nilai arbitrer ke nilai enum selama tipe dasarnya kompatibel. Nilai apa pun dari tipe int harus dapat dikonversi ke tipe enum integer apa pun.
  • konversi yang mengarah ke nilai enum yang tidak valid diperbolehkan. Jenis enum tidak boleh memberikan batasan apa pun pada nilai apa yang dapat diambil oleh variabel.

Apa yang disediakan di atas itu adalah:

  • stringifikasi nilai enum. Dari pengalaman saya, sangat berguna untuk UI dan logging. Jika nilai enum valid, stringifikasi mengembalikan nama konstanta. Jika tidak valid, ia mengembalikan representasi string dari nilai yang mendasarinya. Jika -1 bukan nilai enum yang valid dari beberapa tipe enum Foo , stringifikasi seharusnya mengembalikan -1 .
  • izinkan pengembang untuk menentukan, apakah nilainya adalah nilai enum yang valid saat runtime. Sangat berguna saat bekerja dengan semua jenis protokol. Saat protokol berkembang, nilai enum baru dapat diperkenalkan yang tidak diketahui oleh program. Atau itu bisa menjadi kesalahan sederhana. Saat ini Anda harus memastikan nilai enum benar-benar berurutan (bukan sesuatu yang selalu dapat Anda terapkan) atau secara manual memeriksa setiap nilai yang mungkin. Kode semacam itu menjadi sangat besar dengan sangat cepat dan kesalahan pasti akan terjadi.
  • mungkin mengizinkan pengembang untuk menghitung semua nilai yang mungkin dari tipe enum. Saya melihat orang-orang meminta ini di sini, bahasa lain juga memiliki ini, tetapi saya sendiri tidak ingat pernah membutuhkannya, jadi saya tidak memiliki pengalaman pribadi yang mendukung ini.

Pembenaran saya adalah tentang menulis kode dan menghindari bug. Semua tugas ini membosankan dan tidak perlu dilakukan oleh pengembang dengan tangan atau bahkan memperkenalkan perkakas eksternal yang memperumit kode dan membuat skrip. Fitur-fitur ini mencakup semua yang saya butuhkan dari enum tanpa terlalu memperumit dan membatasi mereka. Saya tidak berpikir Go membutuhkan sesuatu seperti enum di Swift atau bahkan Java.


Ada diskusi tentang memvalidasi pada waktu kompilasi bahwa pernyataan switch mencakup setiap nilai enum yang mungkin. Dengan proposal saya itu akan sia-sia. Melakukan pemeriksaan kelelahan tidak akan mencakup nilai enum yang tidak valid, jadi Anda masih harus memiliki case default untuk menanganinya. Itu diperlukan untuk mendukung perbaikan kode bertahap. Satu-satunya hal yang dapat kita lakukan di sini, menurut saya, adalah menghasilkan peringatan jika pernyataan switch tidak memiliki kasus default. Tapi itu bisa dilakukan bahkan tanpa mengubah bahasa.

@ianlancetaylor Saya pikir argumen Anda memiliki beberapa kekurangan.

Secara umum jika variabel tipe enum hanya dapat menerima nilai enum itu, itu tidak akan berguna. Secara khusus harus ada konversi dari integer sewenang-wenang ke tipe enum; jika tidak, Anda tidak dapat mengirim enum melalui koneksi jaringan.

Abstraksi untuk programmer baik-baik saja; Go menyediakan banyak abstraksi. Misalnya, kode berikut tidak dapat dikompilasi:

package main

import "fmt"

const NULL = 0x0

func main() {
    str := "hello"
    if &str == NULL {
        fmt.Println("str is null")
    }
}

tetapi dalam C , program dengan gaya ini akan dikompilasi. Ini karena Go diketik dengan kuat dan C tidak.

Indeks enum mungkin disimpan secara internal tetapi disembunyikan oleh pengguna sebagai abstraksi, mirip dengan alamat variabel.

@zerkms Ya, itu adalah satu kemungkinan, tetapi mengingat tipe d , inferensi tipe harus dimungkinkan; namun, penggunaan enum yang memenuhi syarat (seperti dalam contoh Anda) sedikit lebih mudah dibaca.

@ianlancetaylor itu adalah enum versi C yang sedang Anda bicarakan. Saya yakin banyak orang tetapi ingin itu tetapi, imo:

Nilai enum tidak boleh memiliki properti numerik. Nilai dari setiap tipe enum harus berupa semesta label diskritnya sendiri yang terbatas, diberlakukan pada waktu kompilasi, tidak terkait dengan angka atau tipe enum lainnya. Satu-satunya hal yang dapat Anda lakukan dengan sepasang nilai tersebut adalah == atau != . Operasi lain dapat didefinisikan sebagai metode atau dengan fungsi.

Implementasinya akan mengkompilasi nilai-nilai itu ke bilangan bulat tetapi itu bukan hal mendasar dengan alasan yang sah untuk diekspos langsung ke programmer, kecuali dengan tidak aman atau refleksi. Untuk alasan yang sama bahwa Anda tidak dapat melakukan bool(0) untuk mendapatkan false .

Jika Anda ingin mengonversi enum ke atau dari nomor atau jenis lainnya, Anda menulis semua kasus dan menyertakan penanganan kesalahan yang sesuai dengan situasi. Jika itu membosankan, Anda menggunakan pembuat kode seperti stringer atau setidaknya sesuatu untuk mengisi kasus dalam pernyataan switch.

Jika Anda mengirim nilai keluar dari proses, int bagus jika Anda mengikuti standar yang ditentukan dengan baik atau Anda tahu Anda sedang berbicara dengan contoh lain dari program Anda yang dikompilasi dari sumbernya atau Anda perlu membuat sesuatu muat di ruang sekecil mungkin bahkan jika itu dapat menyebabkan masalah, tetapi umumnya tidak ada yang bertahan dan lebih baik menggunakan representasi string sehingga nilainya tidak terpengaruh oleh urutan sumber dari definisi tipe. Anda tidak ingin proses A's Green menjadi proses B's Blue karena orang lain memutuskan Biru harus ditambahkan sebelum Green untuk menjaga hal-hal menurut abjad dalam definisi: Anda ingin unrecognized color "Blue" .

Ini adalah cara yang baik dan aman untuk mewakili sejumlah status secara abstrak. Itu meninggalkan program untuk menentukan apa arti status-status itu.

(Tentu saja, sering kali Anda ingin mengaitkan data dengan status tersebut dan tipe data tersebut bervariasi dari satu negara bagian ke negara bagian lainnya...)

@ljeabmreosn Maksud saya adalah jika Go mengizinkan konversi dari bilangan bulat ke tipe enum, maka wajar jika konstanta yang tidak diketik secara otomatis dikonversi ke tipe enum. Contoh tandingan Anda berbeda, karena Go tidak mengizinkan konversi dari bilangan bulat ke tipe pointer.

@jimmyfrasche Jika Anda harus menulis sakelar untuk mengonversi antara bilangan bulat dan tipe enum, maka saya setuju bahwa itu akan berfungsi dengan baik di Go, tetapi sejujurnya tampaknya tidak cukup berguna untuk ditambahkan ke bahasa itu sendiri. Ini menjadi kasus khusus dari tipe jumlah, yang lihat #19412.

Ada banyak proposal di sini.

Komentar umum: Untuk proposal apa pun yang tidak mengekspos nilai dasar (misalnya int) yang dapat Anda konversi ke dan dari untuk enum, berikut adalah beberapa pertanyaan yang harus dijawab.

Apa nilai nol dari tipe enum?

Bagaimana Anda berpindah dari satu enum ke enum lainnya? Saya menduga bahwa bagi banyak orang, hari dalam seminggu adalah contoh kanonik dari enum, tetapi orang mungkin ingin "bertambah" dari Rabu hingga Kamis. Saya tidak ingin harus menulis pernyataan saklar besar untuk itu.

(Juga, mengenai "stringifikasi", string yang benar untuk hari dalam seminggu bergantung pada bahasa dan lokal.)

@josharian stringifikasi biasanya berarti mengonversi nama nilai enum menjadi string secara otomatis oleh kompiler. Tidak ada lokalisasi atau apa pun. Jika Anda ingin membangun sesuatu di atas itu, seperti pelokalan, maka Anda melakukannya dengan cara lain dan bahasa lain menyediakan alat bahasa dan kerangka kerja yang kaya untuk melakukannya.

Misalnya, beberapa tipe C# memiliki penggantian ToString yang juga membutuhkan info budaya. Atau Anda dapat menggunakan objek DateTime itu sendiri dan menggunakan metode ToString yang menerima info format dan budaya. Tetapi penggantian ini tidak standar, kelas object yang diwarisi semua orang hanya memiliki ToString() . Hampir seperti antarmuka stringer di Go.

Jadi saya pikir lokalisasi harus di luar proposal ini dan enum secara umum. Jika Anda ingin menerapkannya, lakukan dengan cara lain. Seperti antarmuka stringer khusus, misalnya.

@josharian Karena, implementasi-bijaksana, itu masih akan menjadi int dan nilai nol adalah semua bit nol, nilai nol akan menjadi nilai pertama dalam urutan sumber. Itu semacam membocorkan intisari tetapi sebenarnya cukup bagus karena Anda dapat memilih nilai nol, memutuskan apakah seminggu dimulai pada hari Senin atau Minggu, misalnya. Tentu saja, urutan suku yang tersisa tidak memiliki dampak seperti itu dan penataan ulang nilainya dapat memiliki dampak yang tidak sepele jika Anda mengubah elemen pertama. Ini tidak benar-benar berbeda dari const/iota.

Merangkai ulang apa yang dikatakan @creker . Namun, untuk berkembang, saya harapkan

var e enum {
  Sunday
  Monday
  //etc.
}
fmt.Println(reflect.ValueOf(e))

untuk mencetak Minggu bukan 0. Label adalah nilai, bukan representasinya.

Untuk lebih jelasnya saya tidak mengatakan itu harus memiliki metode String implisit — hanya saja label disimpan sebagai bagian dari tipe dan dapat diakses dengan refleksi. (Mungkin Println memanggil Label() pada reflect.Value dari enum atau sesuatu seperti itu? Belum melihat secara mendalam bagaimana fmt melakukan voodoo-nya.)

Bagaimana Anda berpindah dari satu enum ke enum lainnya? Saya menduga bahwa bagi banyak orang, hari dalam seminggu adalah contoh kanonik dari enum, tetapi orang mungkin ingin "bertambah" dari Rabu hingga Kamis. Saya tidak ingin harus menulis pernyataan saklar besar untuk itu.

Saya pikir refleksi atau saklar besar adalah hal yang benar. Pola umum dapat dengan mudah diisi dengan go generate untuk membuat metode pada tipe atau fungsi pabrik dari tipe itu (dan bahkan mungkin dikenali oleh kompiler untuk menurunkannya ke aritmatika pada representasi).

Tidak masuk akal bagi saya untuk berasumsi bahwa semua enum memiliki urutan total atau mereka siklik. Mengingat type failure enum { none; input; file; network } , apakah benar-benar masuk akal untuk menegakkan bahwa input yang tidak valid kurang dari kegagalan file atau bahwa penambahan kegagalan file menghasilkan kegagalan jaringan atau bahwa peningkatan kegagalan jaringan menghasilkan kesuksesan?

Dengan asumsi bahwa penggunaan utama adalah untuk nilai urutan siklus, cara lain untuk menangani ini adalah dengan membuat kelas baru dari tipe bilangan bulat paramater. Ini sintaks yang buruk, tetapi, untuk diskusi, katakanlah I%N di mana I berada dalam tipe integer dan N adalah konstanta integer. Semua aritmatika dengan nilai tipe ini secara implisit mod N. Kemudian Anda bisa melakukannya

type Weekday uint%7
const (
  Sunday Weekday = iota
  //etc.

jadi Sabtu + 1 == Minggu dan Hari Kerja (456) == Senin. Tidak mungkin membuat Hari Kerja yang tidak valid. Ini bisa berguna di luar const/iota.

Karena ketika Anda tidak menginginkannya menjadi angka-y sama sekali, seperti yang ditunjukkan oleh @ianlancetaylor yang sebenarnya saya inginkan adalah tipe jumlah.

Memperkenalkan tipe aritmatika modular arbitrer adalah saran yang menarik. Kemudian enum dapat berupa formulir ini, yang memberi Anda metode String sepele:

var Weekdays = [...]string{"Sunday", ..., "Saturday"}

type Weekday = uint % len(Weekdays)

Dikombinasikan dengan int berukuran sewenang-wenang, ini juga membuat Anda int128, int256, dll.

Anda juga dapat menentukan beberapa bawaan:

type uint8 = uint%(1<<8)
// etc

Kompiler dapat membuktikan lebih banyak batasan daripada sebelumnya. Dan API dapat memberikan pernyataan yang lebih tepat melalui tipe, misalnya function Len64 di math/bits sekarang dapat mengembalikan uint % 64 .

Saat bekerja pada port RISC-V, saya menginginkan tipe uint12 , karena komponen encoding instruksi saya adalah 12 bit; itu bisa saja uint % (1<<12) . Banyak manipulasi bit, terutama protokol, dapat mengambil manfaat dari ini.

Kerugiannya signifikan, tentu saja. Go cenderung menyukai kode daripada tipe, dan ini tipe-berat. Operasi seperti + dan - tiba-tiba bisa menjadi semahal %. Tanpa jenis parametrik, Anda mungkin harus mengonversi ke kanonik uint8 , uint16 , dll. agar dapat beroperasi dengan hampir semua fungsi pustaka, dan konversi kembali dapat menyembunyikan batas kegagalan (kecuali kita memiliki cara untuk melakukan konversi panik-ke-luar-jangkauan, yang memperkenalkan kompleksitasnya sendiri). Dan saya dapat melihatnya digunakan secara berlebihan, misalnya menggunakan uint % 1000 untuk kode status HTTP.

Namun, ide yang menarik. :)


Balasan kecil lainnya:

Itu semacam membocorkan keutuhan

Ini membuat saya berpikir mereka benar-benar int. :)

Pola umum dapat dengan mudah diisi dengan go generate

Jika Anda harus membuat kode dengan enum, maka menurut saya Anda juga dapat membuat fungsi String dan pemeriksaan batas dan sejenisnya dan melakukan enum dengan pembuatan kode alih-alih bobot dukungan bahasa.

Tidak masuk akal bagi saya untuk berasumsi bahwa semua enum memiliki urutan total atau mereka siklik.

Cukup adil. Ini membuat saya berpikir bahwa memiliki beberapa kasus penggunaan konkret akan membantu memberikan kejelasan tentang apa yang kita inginkan dari enum. Saya agak curiga bahwa tidak akan ada serangkaian persyaratan yang jelas, dan bahwa meniru enum menggunakan konstruksi bahasa lain (yaitu status quo) akan menjadi yang paling masuk akal. Tapi itu hanya hipotesis.

Merangkai ulang apa yang dikatakan @creker .

Cukup adil. Tapi saya bertanya-tanya berapa banyak kasus yang berakhir seperti hari-hari dalam seminggu. Apa pun yang menghadap pengguna, pasti. Dan stringifikasi tampaknya menjadi salah satu permintaan utama untuk enum.

@josharian Enum yang benar-benar int mungkin membutuhkan mekanisme serupa. Jika tidak, apa itu enum { A; B; C}(42) ?

Anda dapat mengatakan itu adalah kesalahan kompiler tetapi itu tidak berfungsi dalam kode yang lebih rumit karena Anda dapat mengonversi ke dan dari int saat runtime.

Ini adalah A atau panik runtime. Dalam kedua kasus Anda menambahkan tipe integral dengan domain terbatas. Jika panik runtime Anda menambahkan tipe integral yang panik meluap ketika yang lain membungkus. Jika A, Anda telah menambahkan uint%N dengan beberapa upacara.

Pilihan lainnya adalah membiarkannya bukan dari A, B, atau C, tetapi itulah yang kita miliki hari ini dengan const/iota sehingga tidak ada keuntungan.

Semua alasan Anda mengatakan int%N tidak akan berhasil masuk ke bahasa tampaknya berlaku sama untuk enum yang agak int. (Meskipun saya sama sekali tidak akan marah jika sesuatu seperti mereka dimasukkan).

Mengambil keintiman menghilangkan teka-teki itu. Ini membutuhkan pembuatan kode untuk kasus-kasus ketika ingin menambahkan kembali beberapa keintiman itu tetapi juga memberi Anda pilihan untuk tidak melakukan itu, yang memungkinkan Anda mengontrol seberapa banyak keintiman yang akan diperkenalkan dan jenis apa: Anda tidak dapat menambahkan " next", metode siklik berikutnya, atau metode berikutnya yang mengembalikan kesalahan jika Anda gagal. (Anda juga tidak berakhir dengan hal-hal seperti Monday*Sunday - Thursday menjadi legal). Kekakuan ekstra membuatnya menjadi bahan bangunan yang lebih lunak. Serikat yang terdiskriminasi dengan baik memodelkan variasi non-int-y: pick { A, B, C struct{} } , antara lain.

Manfaat utama memiliki informasi seperti ini dalam bahasa adalah

  1. Nilai ilegal adalah ilegal.
  2. informasi tersedia untuk direfleksikan dan digunakan/diketik yang memungkinkan program untuk bertindak berdasarkan itu tanpa perlu membuat asumsi atau anotasi (yang saat ini tidak tersedia untuk direfleksikan).

Manfaat utama memiliki informasi seperti ini dalam bahasa adalah: Nilai-nilai ilegal adalah ilegal.

Saya pikir penting untuk menekankan bahwa tidak semua orang melihat ini sebagai keuntungan. Saya tentu tidak. Sering kali membuat lebih mudah saat mengonsumsi nilai, sering kali membuat lebih sulit saat memproduksinya. Yang menurut Anda lebih berat tampaknya, sejauh ini, tergantung pada preferensi pribadi. Jadi, pertanyaannya adalah apakah itu merupakan keuntungan bersih secara keseluruhan.

Saya juga tidak melihat gunanya melarang nilai-nilai ilegal. Jika Anda sudah memiliki sarana untuk memeriksa sendiri keabsahannya (seperti pada proposal saya di atas), apa manfaat pembatasan itu? Bagi saya, itu hanya memperumit masalah. Dalam aplikasi saya, enum untuk sebagian besar kasus dapat berisi nilai yang tidak valid/tidak diketahui dan Anda harus mengatasinya tergantung pada aplikasi - buang sepenuhnya, turunkan versi ke default atau simpan apa adanya.

Saya membayangkan enum ketat yang melarang nilai yang tidak valid dapat berguna dalam kasus yang sangat terbatas di mana aplikasi Anda diisolasi dari dunia luar dan tidak memiliki cara untuk menerima input yang tidak valid. Seperti enum internal yang hanya dapat Anda lihat dan gunakan.

const dengan iota tidak aman dalam waktu kompilasi, pemeriksaan akan tertunda ke runtime, dan pemeriksaan aman tidak pada level tipe. Jadi saya pikir iota tidak dapat menggantikan enum secara harfiah, saya lebih suka enum karena lebih kuat.

Nilai ilegal adalah ilegal.
Saya pikir penting untuk menekankan bahwa tidak semua orang melihat ini sebagai keuntungan.

Saya tidak mengerti logika ini. Tipe adalah kumpulan nilai. Anda tidak dapat menetapkan tipe ke variabel yang nilainya bukan tipe tersebut. Apakah saya salah paham tentang sesuatu?

PS: Saya setuju bahwa enum adalah kasus khusus dari tipe jumlah dan masalah itu harus didahulukan dari yang ini.

Biarkan saya ulangi/lebih tepatnya: Tidak semua orang melihatnya sebagai manfaat untuk menutup enum.

Jika Anda ingin tegas seperti itu, maka a) "Nilai ilegal adalah ilegal" adalah tautologi dan b) dengan demikian tidak dapat dihitung sebagai manfaat. Dengan enum berbasis const, dalam interpretasi Anda, nilai ilegal juga ilegal. Jenisnya hanya memungkinkan lebih banyak nilai.

Jika enum adalah int dan int apa pun legal (dari sudut pandang sistem tipe) maka satu-satunya keuntungan adalah bahwa nilai-nilai yang disebutkan dari tipe tersebut direfleksikan.

Itu pada dasarnya hanya const/iota tetapi Anda tidak perlu menjalankan stringer karena paket fmt bisa mendapatkan nama menggunakan refleksi. (Anda masih harus menjalankan stringer jika Anda ingin stringnya berbeda dari nama di sumbernya).

@jimmyfrasche stringifikasi hanyalah bonus yang bagus. Fitur utama bagi saya, seperti yang dapat Anda baca dalam proposal saya di atas, adalah kemampuan untuk memeriksa apakah nilai yang diberikan adalah nilai yang valid dari jenis enum yang diberikan saat runtime.

Misalnya, diberikan sesuatu seperti ini

type Foo enum {
    Val1 = 1
    Val2 = 2
}

Dan metode refleksi seperti

func IsValidEnum(v {}interface) bool

Kita bisa melakukan sesuatu seperti ini

a := Foo.Val1
b := Foo(-1)
reflection.IsValidEnum(a) //returns true
reflection.IsValidEnum(b)  //returns false

Untuk contoh dunia nyata, Anda dapat melihat enum di C# yang, menurut pendapat saya, menangkap jalan tengah ini dengan sempurna alih-alih secara membabi buta mengikuti apa yang dilakukan Java. Untuk memeriksa validitas dalam C# Anda menggunakan Enum.IsDefined metode statis .

@crecker Satu-satunya perbedaan antara itu dan const/iota adalah
informasi yang disimpan dalam refleksi. Itu tidak banyak keuntungan secara keseluruhan
tipe tipe baru.

Ide yang sedikit gila:

Simpan nama dan nilai untuk semua const yang dideklarasikan dalam paket yang sama
sebagai tipe yang ditentukan mereka dengan cara yang dapat dicapai oleh refleksi. Itu akan
aneh untuk memilih kelas penggunaan const yang sempit itu.

Fitur utama bagi saya, seperti yang Anda baca dalam proposal saya di atas

IMO ini menggambarkan salah satu hal utama yang menyeret diskusi ini: Kurangnya kejelasan tentang apa yang dimaksud dengan kumpulan "fitur utama". Setiap orang tampaknya memiliki ide yang sedikit berbeda tentang itu.
Secara pribadi, saya masih menyukai format laporan pengalaman untuk menemukan set itu. Bahkan ada satu dalam daftar (meskipun, secara pribadi, saya masih berkomentar pada fakta bahwa bagian "Apa yang Salah" hanya menyebutkan apa yang bisa salah, bukan apa yang sebenarnya terjadi ). Mungkin menambahkan beberapa, yang menggambarkan di mana kurangnya pengecekan tipe menyebabkan pemadaman/bug atau misalnya kegagalan untuk melakukan refactoring skala besar, akan sangat membantu.

@jimmyfrasche tapi itu memecahkan masalah besar di banyak aplikasi - memvalidasi data input. Tanpa bantuan dari sistem tipe Anda harus melakukannya dengan tangan dan itu bukan sesuatu yang dapat Anda lakukan dalam beberapa baris kode. Memiliki beberapa bentuk validasi berbantuan tipe akan menyelesaikannya. Menambahkan stringifikasi di atas itu akan menyederhanakan pencatatan karena Anda akan memiliki nama yang diformat dengan benar dan bukan nilai tipe yang mendasarinya.

Di sisi lain, membuat enum menjadi ketat akan sangat membatasi kemungkinan kasus penggunaan. Sekarang Anda tidak dapat menggunakannya dalam protokol dengan mudah, misalnya. Untuk mempertahankan bahkan nilai yang tidak valid, Anda harus menghapus enum dan menggunakan tipe nilai biasa, mungkin mengonversinya menjadi enum nanti jika perlu. Dalam beberapa kasus, Anda bisa menjatuhkan nilai yang tidak valid dan membuat kesalahan. Di tempat lain, Anda dapat menurunkan versi ke beberapa nilai default. Bagaimanapun, Anda berjuang dengan pembatasan sistem tipe Anda alih-alih membantu Anda menghindari kesalahan.

Lihat saja protobuf apa yang harus dihasilkan Java untuk bekerja di sekitar enum Java.

@Merovius mengenai validasi, saya pikir saya sudah membahasnya beberapa kali. Saya tidak tahu apa lagi yang bisa ditambahkan selain - tanpa validasi Anda harus menulis cukup banyak kode salin-tempel untuk memvalidasi input Anda. Masalahnya jelas, serta bagaimana solusi yang diusulkan dapat membantu dengan itu. Saya tidak bekerja pada beberapa aplikasi skala besar yang diketahui semua orang tetapi kesalahan dalam kode validasi itu cukup mengganggu saya dalam berbagai bahasa dengan konsep enum yang sama yang saya ingin melihat sesuatu dilakukan tentang hal itu.

Di sisi lain, saya tidak melihat (maaf jika saya melewatkan sesuatu) argumen apa pun yang mendukung penerapan enum yang tidak mengizinkan nilai yang tidak valid. Ini bagus dan rapi dalam teori, tetapi saya tidak melihatnya membantu saya dalam aplikasi nyata.

Tidak banyak fitur yang diinginkan orang dari enum. Stringifikasi, validasi, ketat/longgar dalam hal nilai yang tidak valid, enumerasi - itu saja dari apa yang saya lihat. Semua orang (termasuk saya tentu saja) hanya mengocoknya pada saat ini. ketat/longgar tampaknya menjadi pokok perdebatan karena sifatnya yang saling bertentangan. Saya tidak berpikir semua orang akan setuju satu sama lain. Mungkin solusinya adalah menggabungkan keduanya dalam beberapa cara dan membiarkan programmer memilih tetapi saya tidak tahu bahasa apa pun yang memilikinya untuk melihat bagaimana itu bisa bekerja di dunia nyata.

@crecker saran saya untuk menyimpan consts dalam data ekspor di atas
keadaan akan memungkinkan hal-hal yang Anda minta
tanpa pengenalan jenis jenis baru.

Saya tidak yakin ini adalah cara idiomatik, dan saya juga cukup baru dalam bahasa ini, tetapi berikut ini berfungsi dan ringkas

type Day struct {
    value string
}

// optional, if you need string representation
func (d Day) String() string { return d.value }

var (
    Monday = Day{"Monday"}
    Tuesday = Day{"Tuesday"}
)

func main() {
    getTask(Monday)
}

func getTask(d Day) string {
    if d == Monday {
        fmt.Println("today is ", d, "!”) // today is Monday !
        return "running"
    }

    return "nothing to do"
}

Keuntungan :

Kekurangan :

Apakah kita benar-benar membutuhkan enum?

Apa yang menghentikan seseorang melakukan sesuatu seperti ini:

NotADay := Day{"NotADay"}
getTask(NotADay)

Konsumen dari variabel semacam itu mungkin atau mungkin tidak menangkapnya dengan pemeriksaan yang tepat dari nilai yang diharapkan (dengan asumsi tidak ada kesalahan yang buruk melalui asumsi dalam pernyataan sakelar, seperti apa pun yang bukan Sabtu atau Minggu adalah hari kerja, misalnya), tetapi itu tidak akan terjadi. sampai waktu tayang. Saya pikir orang akan lebih suka jenis kesalahan ini ditangkap pada waktu kompilasi, bukan runtime.

@bpkroth
Dengan memiliki Day dalam paketnya sendiri dan hanya memperlihatkan bidang & metode yang dipilih, saya tidak dapat membuat nilai baru dengan tipe Day di luar package day
Juga, dengan cara ini saya tidak dapat meneruskan struct anonim ke getTask

./hari/hari.go

package day

type Day struct {
    value string
}

func (d Day) String() string { return d.value }

var (
    Monday  = Day{"Monday"}
    Tuesday = Day{"Tuesday"}
    Days    = []Day{Monday, Tuesday}
)

./main.go

package main

import (
    "fmt"
    "github.com/somePath/day"
)

func main() {
    january := day.Day{"january"} // implicit assignment of unexported field 'value' in day.Day literal

    var march struct {
        value string
    }
    march.value = "march"
    getTask(march) // cannot use march (type struct { value string }) as type day.Day in argument to getTask

    getTask(day.Monday)
}

func getTask(d day.Day) string {
    if d == day.Monday {
        fmt.Println("today is ", d, "!") // today is Monday !
        return "running"
    }

    return "nothing to do"
}

func iterateDays() {
    for _, d := range day.Days {
        fmt.Println(d)
    }
}

Saya belum pernah melihat sepanjang hidup saya bahasa lain yang bersikeras untuk tidak menambahkan fitur yang paling sederhana dan bermanfaat seperti enum, operator ternary, kompilasi dengan variabel yang tidak digunakan, tipe jumlah, generik, parameter default, dll ...

Apakah Golang adalah eksperimen sosial untuk melihat betapa bodohnya para pengembang?

@gh67uyyghj Seseorang menandai komentar Anda sebagai di luar topik! dan saya kira seseorang akan melakukan hal yang sama untuk jawaban saya. tapi saya rasa jawaban untuk pertanyaan Anda adalah YA. Di GoLang menjadi tanpa fitur berarti menjadi fitur jadi apa pun yang tidak dimiliki GoLang sebenarnya adalah fitur yang dimiliki GoLang yang tidak dimiliki bahasa pemrograman lain!!

@L-oris Ini adalah cara yang sangat menarik untuk mengimplementasikan enum dengan tipe. Tapi rasanya canggung, dan memiliki kata kunci enum (yang tentu memperumit bahasa lagi) akan membuatnya lebih mudah untuk:

  • menulis
  • Baca
  • alasan tentang

Dalam contoh Anda (yang bagus karena berfungsi hari ini) memiliki enum (dalam beberapa bentuk) menyiratkan perlunya:

  • Buat tipe struct
  • Buat metode
  • Buat variabel (bahkan bukan konstanta, meskipun pengguna perpustakaan tidak dapat mengubah nilai tersebut)

Ini membutuhkan waktu lebih lama (meskipun tidak _itu_ lebih lama) untuk membaca, menulis, dan bernalar (memahami bahwa itu mewakili dan harus digunakan sebagai enum).

Oleh karena itu, saya pikir proposal sintaksisnya tepat dalam hal kesederhanaan dan nilai tambah pada bahasa tersebut.

Terima kasih @andradei
Ya itu solusinya, tapi saya merasa tujuan bahasanya adalah untuk membuatnya tetap kecil dan sederhana
Kita juga bisa berargumen bahwa kita ketinggalan kelas, tapi mari kita pindah ke Java :)

Saya lebih suka fokus pada proposal Go 2, misalnya penanganan kesalahan yang lebih baik. akan memberi saya nilai lebih dari enum ini

Kembali ke poin Anda:

  • itu tidak terlalu banyak; paling buruk, kita dapat memiliki beberapa generator (tetapi, apakah itu benar-benar kode sebanyak itu?)
  • seberapa banyak "kesederhanaan" yang kita capai dengan menambahkan kata kunci baru, dan seluruh rangkaian perilaku spesifik yang mungkin dimiliki kata kunci itu?
  • menjadi sedikit kreatif dengan metode juga dapat menambah kemampuan menarik untuk enum tersebut
  • untuk keterbacaan, ini lebih tentang membiasakan diri; mungkin menambahkan komentar di atasnya, atau awali variabel Anda
package day

// Day Enum
type Day struct {
    value string
}

@L-oris begitu. Saya senang dengan proposal Go 2 juga. Saya berpendapat bahwa obat generik akan meningkatkan kompleksitas bahasa lebih dari enum. Tetapi untuk tetap pada poin Anda:

  • Memang tidak terlalu banyak boilerplate
  • Kita harus memeriksa seberapa terkenal konsep enum untuk memastikannya, menurut saya kebanyakan orang tahu apa itu (tapi saya tidak bisa membuktikannya). Kompleksitas bahasa akan menjadi "harga" yang baik untuk membayar manfaatnya.
  • Itu benar, tidak memiliki enum adalah masalah yang hanya muncul pada saya ketika memeriksa kode protobuf generetad dan ketika mencoba membuat model database yang meniru enum, misalnya.
  • Itu juga benar.

Saya telah banyak berpikir tentang proposal ini, dan saya dapat melihat nilai besar kesederhanaan pada produktivitas, dan mengapa Anda cenderung mempertahankannya kecuali perubahan itu jelas diperlukan. Enum juga dapat mengubah bahasa secara drastis sehingga bukan Go lagi, dan menilai pro/kontra itu sepertinya akan memakan waktu lama. Jadi saya telah berpikir bahwa solusi sederhana seperti milik Anda, di mana kodenya masih mudah dibaca, adalah solusi yang baik setidaknya untuk saat ini.

Guys, pengen banget fitur ini kedepannya!. Pointer dan cara mendefinisikan " enum " di _nowadays_ tidak cocok. Misalnya: https://play.golang.org/p/A7rjgAMjfCx

Proposal saya untuk enum mengikuti. Kita harus menganggap ini sebagai tipe baru. Misalnya saya ingin menggunakan tipe enum dengan struktur arbitrer dan implementasi berikut:

package application

type Status struct {
Name string
isFinal bool
}

enum Status {
     Started = &Status{"Started",false}
     Stopped = &Status{"Stopped",true}
     Canceled = &Status{"Canceled",true}
}

// application.Status.Start - to use

Dapat dimengerti bagaimana menyusun struktur ini dan bagaimana bekerja dan bagaimana mengubah ke string dan seterusnya.
Dan tentu saja jika saya bisa mengganti fungsi "Selanjutnya" akan bagus.

Untuk itu Go harus mendukung struct yang tidak dapat diubah terlebih dahulu. Tanpa tipe yang tidak dapat diubah, saya dapat membayangkan Anda dapat melakukan ini dengan enum untuk memiliki hal yang sama:

type Status enum {
  Started
  Stopped
}

func isFinal(s Status) bool {
  exhaustive switch(s) {
    case Started: return false;
    case Stopped: return true;
  }
}

Saya pikir itu akan terlihat lebih sederhana

func isFinal(s Status) bool {
  return s == Status.Stopped
}

Proposal untuk Go2

Secara logis, enum seharusnya menyediakan antarmuka tipe.
Saya menyatakan enum sebelumnya seharusnya terpisah.
Itu secara eksplisit dinamai konstanta yang diikat ke namespace tertentu.

enum Status uint8 {
  Started  // Status.Started == 0
  Stopped // Status.Stopped == 1, etc, like we have used iota
}
// or 
enum Status string  {
  Started // Status.Started == "Started", like it works with JSON
  Stopped // Status.Stopped == "Stopped", etc
}
// unless you wanna define its values explicitly
enum Status {
  Started "started"  // compiler can infer underlying type
  Stopped "finished"
}
// and enums are type extensions and should be used like this
type MyStatus Status

MyStatus validatedStatus // holds a nil until initialized

// for status value validation we can use map pattern
if validatedStatus, ok := MyStatus[s]; ok {
  // this value is a valid status
  // and we can use it later as regular read-only string
  // or like this
  if validatedStatus == MyStatus.Started {
     fmt.Printf("Hey, my status is %s", validatedStatus)
  }
}

Enum adalah ekstensi tipe, "kontainer konstan".

Untuk pecinta Tipe

Alternatif sintaks untuk mereka yang ingin melihatnya sebagai tipe

type Status uint8 enum {
  Started  // Status.Started == 0
  Stopped // Status.Stopped == 1, etc, like we have used iota
}

Tapi kita juga bisa menghindari deklarasi tingkat atas yang eksplisit itu

type Status enum {
  Started  // Status.Started == 0
  Stopped // Status.Stopped == 1, etc, like we have used iota
}

Contoh validasi tetap sama.

tapi kalau-kalau

type Status1 uint8 enum {
  Started  // Status1.Started == 0
  Stopped // Status1.Stopped == 1, etc, like we have used iota
}

type Status2 uint8 enum {
  Started  // Status1.Started == 0
  Stopped // Status1.Stopped == 1, etc, like we have used iota
}

Bagaimana dengan Status1.Started == Status2.Started ?
tentang Marshaling?

Jika saya mengubah posisi?

type Status uint8 enum {
  Started  // Status.Started == 0
  InProcess
  Stopped // Status.Stopped == 1, etc, like we have used iota
}

Saya setuju dengan @Goodwine tentang tipe yang tidak dapat diubah.

Marshaling adalah pertanyaan yang menarik.
Ini semua tergantung pada bagaimana kita akan memperlakukan nilai yang mendasarinya. Jika kita akan menggunakan nilai aktual, maka Status1.Started akan sama dengan Status2.Started .
Jika kita pergi dengan interpretasi simbolik itu akan dianggap sebagai nilai yang berbeda.

Menyisipkan sesuatu akan menyebabkan perubahan nilai (persis dengan cara yang sama dengan iota ).
Untuk menghindari ini, pengembang harus menentukan nilai bersama dengan deklarasi.

type Status uint8 enum {
  Started  0
  InProcess 2
  Stopped 1
}

Ini adalah hal yang jelas.
Jika kita ingin menghindari masalah seperti itu, kita harus menyediakan keluaran kompiler yang dapat diprediksi berdasarkan interpretasi leksikal dari nilai enum. Saya berasumsi cara paling sederhana - membangun tabel hash atau tetap menggunakan nama simbolis (string) kecuali casting tipe khusus ditentukan.

Saya suka bagaimana Rust diimplementasikan Enums.

Default tanpa tipe yang ditentukan

enum IpAddr {
    V4,
    V6,
}

Jenis kustom

enum IpAddr {
    V4(string),
    V6(string),
}

home := IpAddr.V4("127.0.0.1");
loopback := IpAddr.V6("::1");

Tipe kompleks

enum Message {
    Quit,
    Move { x: int32, y: int32 },
    Write(String),
    ChangeColor(int32, int32, int32),
}

Yang pasti bahkan memiliki enum sederhana seperti di C# yang disimpan sebagai tipe integral akan bagus.

Di atas melampaui enum s, itu adalah _discriminated unions_, yang memang lebih kuat, terutama dengan _pattern matching_, yang bisa menjadi ekstensi kecil untuk switch , seperti:

switch something.(type) {
case Quit:
        ...
case ChangeColor; r, g, b := something:
        ...
case Write: // Here `something` is known to be a string
        ...
// Ideally Go would warn here about the missing case for "Move"
}

Saya tidak memerlukan pemeriksaan waktu kompilasi dari enum, karena itu bisa berbahaya seperti yang disebutkan

Yang saya perlukan beberapa kali adalah mengulangi semua konstanta dari tipe tertentu:

  • baik untuk validasi ( jika kami sangat yakin kami hanya ingin menerima ini atau mengabaikan opsi yang tidak diketahui )

    • atau untuk daftar kemungkinan konstanta (pikirkan dropdown).

Kita bisa melakukan validasi dengan sedikit pun dan menentukan akhir daftar. Namun menggunakan iota untuk hal lain selain hanya di dalam kode, akan cukup berbahaya karena hal-hal akan rusak dengan memasukkan konstanta di baris yang salah (Saya tahu kita perlu menyadari di mana kita meletakkan sesuatu dalam pemrograman, tetapi bug seperti itu jauh lebih sulit ditemukan daripada hal-hal lain). Selain itu, kami tidak memiliki deskripsi tentang apa sebenarnya kepanjangan dari konstanta ketika itu adalah angka. Itu mengarah ke poin berikutnya:

Tambahan yang bagus adalah menentukan nama string untuk itu.

Apa yang menghentikan seseorang melakukan sesuatu seperti ini:

NotADay := Day{"NotADay"}
getTask(NotADay)

Konsumen dari variabel semacam itu mungkin atau mungkin tidak menangkapnya dengan pemeriksaan yang tepat dari nilai yang diharapkan (dengan asumsi tidak ada kesalahan yang buruk melalui asumsi dalam pernyataan sakelar, seperti apa pun yang bukan Sabtu atau Minggu adalah hari kerja, misalnya), tetapi itu tidak akan terjadi. sampai waktu tayang. Saya pikir orang akan lebih suka jenis kesalahan ini ditangkap pada waktu kompilasi, bukan runtime.

@L-oris Jadi Bagaimana dengan ini:

package main
import "yet/it/is/not/a/good/practice/in/Go/enum/example/day"

func main()
{
  // var foo day.Day
  foo := day.Day{}
  bar(foo)
}

func bar(day day.Day)
{
  // xxxxxxxxxx
}

Yang kami inginkan BUKAN RUNTIME SILENCE & Weird BUG yang disebabkan oleh [mengembalikan "tidak ada hubungannya"] tetapi PELAPORAN KESALAHAN waktu kompilasi / waktu pengkodean !
MEMAHAMI?

  1. enum memang tipe baru, itulah yang dilakukan type State string , tidak perlu idiomatis untuk memperkenalkan kata kunci baru. Go bukan tentang menghemat ruang dalam kode sumber Anda, ini tentang keterbacaan, kejelasan tujuan.

  2. Kurangnya keamanan tipe, membingungkan tipe baru berbasis string - atau int - untuk string/ints sebenarnya adalah rintangan utama. Semua klausa enum dideklarasikan sebagai const , yang membuat sekumpulan nilai yang diketahui dapat diperiksa oleh kompiler.

  3. Antarmuka Stringer adalah idiom untuk mewakili jenis apa pun sebagai teks yang dapat dibaca manusia. Tanpa penyesuaian, type ContextKey string enum ini adalah nilai string, dan untuk iota -enum yang dihasilkan itu adalah bilangan bulat, seperti kode XHR ReadyState (0 - tidak terkirim, 4 - selesai) dalam JavaScript.

    Sebaliknya, masalahnya terletak pada kesalahan implementasi func (k ContextKey) String() string kustom, yang biasanya dilakukan menggunakan sakelar yang harus berisi setiap konstanta klausa enum yang diketahui.

  4. Dalam bahasa seperti Swift, ada gagasan tentang _an switch yang lengkap_. Ini adalah pendekatan yang baik untuk kedua jenis pemeriksaan terhadap set const s dan membangun cara idiomatis untuk menjalankan pemeriksaan itu. Fungsi String() , menjadi kebutuhan umum, adalah kasus yang bagus untuk implementasi.

Usul

package main

import (
    "context"
    "strconv"
    "fmt"
    "os"
)

// State is an enum of known system states.
type DeepThoughtState int

// One of known system states.
const (
    Unknown DeepThoughtState = iota
    Init
    Working
    Paused
    ShutDown
)

// String returns a human-readable description of the State.
//
// It switches over const State values and if called on
// variable of type State it will fall through to a default
// system representation of State as a string (string of integer
// will be just digits).
func (s DeepThoughtState) String() string {
    // NEW: Switch only over const values for State
    switch s.(const) {
    case Unknown:
        return fmt.Printf("%d - the state of the system is not yet known", Unknown)
    case Init:
        return fmt.Printf("%d - the system is initializing", Init)
    } // ERR: const switch must be exhaustive; add all cases or `default` clause

    // ERR: no return at the end of the function (switch is not exhaustive)
}

// RegisterState allows changing the state
func RegisterState(ctx context.Context, state string) (interface{}, error) {
    next, err := strconv.ParseInt(state, 10, 32)
    if err != nil {
        return nil, err
    }
    nextState := DeepThoughtState(next)

    fmt.Printf("RegisterState=%s\n", nextState) // naive logging

        // NEW: Check dynamically if variable is a known constant
    if st, ok := nextState.(const); ok {
        // TODO: Persist new state
        return st, nil
    } else {
        return nil, fmt.Errorf("unknown state %d, new state must be one of known integers", nextState)
    }
}

func main() {
    _, err := RegisterState(context.Background(), "42")
    if err != nil {
        fmt.Println("error", err)
        os.Exit(1)
    }
    os.Exit(0)
    return
}

Nilai terkait PS di Swift enum adalah salah satu gimmick favorit saya. Di Go tidak ada tempat untuk mereka. Jika Anda ingin memiliki nilai di sebelah data enum Anda — gunakan struct yang diketik dengan kuat untuk membungkus keduanya.

Beberapa bulan yang lalu saya menulis bukti konsep untuk linter yang memeriksa apakah tipe yang disebutkan ditangani dengan benar. https://github.com/loov/enumcheck

Saat ini menggunakan komentar untuk menandai hal-hal sebagai enumerasi:

type Letter byte // enumcheck

const (
    Alpha Letter = iota
    Beta
    Gamma
)

func Switch(x Letter) {
    switch x { // error: "missing cases Beta and Gamma"
    case Alpha:
        fmt.Println("alpha")
    case 4: // error: "implicit conversion of 4 to Letter"
        fmt.Println("beta")
    default: // error: "Letter shouldn't have a default case"
        fmt.Println("default")
    }
}

Saya terjebak dalam mencari tahu bagaimana menangani semua konversi implisit, tetapi ini berfungsi dengan baik untuk kasus-kasus dasar.

Catatan, saat ini masih dalam proses, jadi mungkin ada perubahan. misalnya alih-alih komentar, ia dapat menggunakan beberapa paket rintisan untuk membuat anotasi jenisnya, tetapi komentar cukup baik saat ini.

Implementasi enum saat ini di Go1 adalah implementasi enum paling aneh yang paling tidak jelas dalam bahasa apa pun yang pernah saya ketahui. Bahkan C mengimplementasikannya dengan lebih baik. Hal sedikit pun terlihat seperti peretasan. Dan apa sih artinya iota? Bagaimana saya harus menghafal kata kunci itu? Go seharusnya mudah dipelajari. Tapi itu hanya qiurky.

@pofl :
Meskipun saya setuju bahwa enum Go cukup canggung, iota sebenarnya hanyalah kata bahasa Inggris biasa:

sedikit pun
_kata benda_

  1. jumlah yang sangat kecil; mencatat; sedikit.
  2. huruf kesembilan dari alfabet Yunani (I, ).
  3. suara vokal yang diwakili oleh surat ini.

Agaknya, mereka akan mendefinisikan satu dalam hal penggunaan dalam bahasa.

Di samping catatan sebagai tanggapan atas komentar yang lebih lama di sini:
Meskipun saya juga sangat menyukai serikat pekerja yang didiskriminasi di Go, saya merasa mereka harus terpisah dari enum yang sebenarnya. Dengan cara obat generik saat ini berjalan, Anda mungkin benar-benar mendapatkan sesuatu yang sangat mirip dengan serikat pekerja yang didiskriminasi melalui daftar tipe di antarmuka. Lihat #41716.

Penggunaan iota di Go secara longgar didasarkan pada penggunaannya di APL. Mengutip https://en.wikipedia.org/wiki/Iota :

Dalam beberapa bahasa pemrograman (misalnya, A+, APL, C++[6], Go[7]), iota (baik sebagai simbol huruf kecil atau pengidentifikasi iota) digunakan untuk mewakili dan menghasilkan array bilangan bulat berurutan. Misalnya, dalam APL 4 memberikan 1 2 3 4.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat