Go: 提案:仕様:型付き列挙型サポートを追加

作成日 2017ĺš´04月01日  Âˇ  180コメント  Âˇ  ソース: golang/go

列挙型を特別な種類のtypeとしてGoに追加することを提案したいと思います。 以下の例は、protobufの例から借用したものです。

今日のGoの列挙型

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
}

言語サポートでどのように見えるか

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"
}

このパターンは十分に一般的であるため、特別な大文字と小文字を区別する必要があり、コードが読みやすくなると思います。 実装層では、大部分のケースをコンパイル時にチェックできると思います。その中には、今日すでに発生しているものもあれば、ほぼ不可能であるか、重大なトレードオフが必要なものもあります。

  • エクスポートされたタイプの安全性:誰かがSearchRequest(99)またはSearchRequest("MOBILEAPP")を実行することを妨げるものは何もありません。 現在の回避策には、オプションを使用してエクスポートされていない型を作成することが含まれますが、これにより、結果のコードが使用しにくくなることがよくあります。
  • 実行時の安全性:protobufがアンマーシャリング中に有効性をチェックするのと同じように、これは列挙型がインスタンス化されるときはいつでも、言語全体の検証を提供します。
  • ツール/ドキュメント:今日の多くのパッケージは有効なオプションをフィールドコメントに入れていますが、誰もがそれを行うわけではなく、コメントが古くないという保証はありません。

考慮事項

  • なし:型システムの上にenumを実装することで、これに特別なケーシングが必要になるとは思わない。 誰かがnilを有効にしたい場合は、列挙型をポインターとして定義する必要があります。
  • デフォルト値/実行時の割り当て:これは、行うのが難しい決定の1つです。 Goのデフォルト値が有効な列挙型として定義されていない場合はどうなりますか? 静的分析はコンパイル時にこれの一部を軽減できますが、外部入力を処理する方法が必要になります。

構文については強い意見はありません。 これはうまくいく可能性があり、生態系にプラスの影響を与えると私は信じています。

Go2 LanguageChange NeedsInvestigation Proposal

最も参考になるコメント

列挙型ではない@ md2perpe 。

  1. それらを列挙したり、繰り返したりすることはできません。
  2. 有用な文字列表現はありません。
  3. 彼らにはアイデンティティがありません:

`` `行く
パッケージメイン

輸入 (
「fmt」
)。

func main(){
SearchRequestintと入力します
const
Universal SearchRequest = iota
ウェブ
)。

const (
    Another SearchRequest = iota
    Foo
)

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

}
`` ``

私は@derekperkinsに完全に同意します。Goには一級市民として列挙型が必要です。 それがどのように見えるかはわかりませんが、Go1のガラスの家を壊さずにそれを行うことができたのではないかと思います。

全てのコメント180件

@ derekparker #19412でGo2提案を行うための議論があります

今日の初めにそれを読みましたが、それは有効な型に焦点を当てているようで、これは有効な型の値に焦点を当てています。 たぶんこれはその提案のサブセットですが、今日Goに入れることができる型システムへのそれほど広範囲な変更でもありません。

列挙型は、すべての型が同じであり、メソッドによってそれぞれに関連付けられた値がある合計型の特殊なケースです。 確かに、もっとタイプするが、同じ効果。 とにかく、それはどちらか一方であり、合計タイプはより多くの根拠をカバーし、合計タイプでさえありそうにありません。 いずれにせよ、Go1互換性契約のため、Go2まで何も起こりません。これらの提案は、少なくとも、新しいキーワードが必要になるため、いずれかが受け入れられた場合です。

十分に公平ですが、これらの提案はどちらも互換性の合意に違反していません。 合計タイプはGo1に追加するには「大きすぎる」という意見がありました。 その場合、この提案は、Go2の全額型への足がかりとなる可能性のある貴重な中間点です。

それらは両方とも、識別子としてそれを使用して有効なGo1コードを壊す新しいキーワードを必要とします

私はそれが回避できると思います

新しい言語機能には、説得力のあるユースケースが必要です。 すべての言語機能が便利であるか、誰もそれらを提案しません。 問題は、言語を複雑にし、すべての人に新しい概念を学ぶことを要求することを正当化するのに十分に役立つかどうかです。 ここでの説得力のあるユースケースは何ですか? 人々はこれらをどのように使用しますか? たとえば、人々は有効な列挙値のセットを反復処理できることを期待しますか?もしそうなら、どのようにそれを実行しますか? この提案は、一部のスイッチにデフォルトのケースを追加することを回避できる以上のことをしますか?

現在のGoで列挙を書く慣用的な方法は次のとおりです。

type SearchRequest int

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

これには、OR:ed(演算子|を使用)できるフラグを簡単に作成できるという利点があります。

type SearchRequest int

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

キーワードenumを導入すると、キーワードが大幅に短くなることはわかりません。

列挙型ではない@ md2perpe 。

  1. それらを列挙したり、繰り返したりすることはできません。
  2. 有用な文字列表現はありません。
  3. 彼らにはアイデンティティがありません:

`` `行く
パッケージメイン

輸入 (
「fmt」
)。

func main(){
SearchRequestintと入力します
const
Universal SearchRequest = iota
ウェブ
)。

const (
    Another SearchRequest = iota
    Foo
)

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

}
`` ``

私は@derekperkinsに完全に同意します。Goには一級市民として列挙型が必要です。 それがどのように見えるかはわかりませんが、Go1のガラスの家を壊さずにそれを行うことができたのではないかと思います。

@ md2perpe iotaは、列挙型にアプローチするための非常に限られた方法であり、限られた状況でうまく機能します。

  1. intが必要です
  2. パッケージ内で一貫している必要があるだけで、外部の状態を表す必要はありません

文字列または別の型を表す必要があるとすぐに、これは外部フラグで非常に一般的ですが、 iotaは機能しません。 外部/データベース表現と照合する場合は、 iotaは使用しません。これは、ソースコードでの順序付けが重要であり、並べ替えるとデータの整合性の問題が発生するためです。

これは、コードを短くするための便利な問題だけではありません。 これは、今日の言語では強制できない方法でデータの整合性を可能にする提案です。

@ianlancetaylor

たとえば、人々は有効な列挙値のセットを反復処理できることを期待しますか?もしそうなら、どのようにそれを実行しますか?

@bepが述べたように、これは確かなユースケースだと思います。 反復は標準のGoループのように見え、定義された順序でループすると思います。

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

Goがiota以外のものを追加する場合、その時点で代数的データ型を追加してみませんか?

定義順序に従って順序付けを拡張し、protobufの例に従うと、フィールドのデフォルト値は最初に定義されたフィールドになると思います。

@bepそれほど便利ではありませんが、次のすべてのプロパティを取得できます。

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)
    }
}

コンパイル時にチェックされる列挙型は良い考えではないと思います。 私は今、これがほとんどあると信じています。 私の推論は

  • コンパイル時にチェックされる列挙型は、追加または削除の場合に下位互換性も上位互換性もありません。 18130は、段階的なコード修復を可能にするために多大な労力を費やしています。 列挙型はその努力を破壊するでしょう。 列挙型のセットを変更したいパッケージは、すべてのインポーターを自動的かつ強制的に破壊します。
  • 元のコメントが主張していることとは反対に、protobufは(その特定の理由で)実際には列挙型フィールドの有効性をチェックしません。 proto2は、列挙型の不明な値を不明なフィールドのように扱う必要があることを指定し、proto3は、生成されたコードがエンコードされた値でそれらを表す方法を持っている必要があることを指定します(goが現在偽の列挙型で行うのとまったく同じです)
  • 結局、それは実際には多くを追加しません。 ストリンガーツールを使用して文字列化を取得できます。 センチネルMaxValidFooconstを追加することで、反復を取得できます(ただし、上記の警告を参照してください。要件さえないはずです)。 そもそも2つぎconst-declsを使用するべきではありません。 それをチェックするツールをCIに統合するだけです。
  • 私はint以外のタイプが実際に必要であるとは思わない。 ストリンガーツールは、文字列への変換と文字列からの変換をすでにカバーしているはずです。 結局、生成されたコードは、コンパイラがとにかく生成するものと同等になります(「string-enums」での比較がバイトを反復することを真剣に提案しない限り…)

全体として、私にとっては巨大な-1です。 何も追加しないだけではありません。 それは積極的に痛い。

Goでの現在の列挙型の実装は非常に簡単で、十分なコンパイル時のチェックを提供していると思います。 実際には、基本的なパターンマッチングを備えたある種のRust列挙型を期待していますが、Go1の保証が破られる可能性があります。

列挙型は合計型の特殊なケースであり、一般的な知恵は、インターフェイスを使用して合計型をシミュレートする必要があるため、答えは明らかにhttps://play.golang.org/p/1BvOakvbj2です。

(明確でない場合:はい、それは冗談です。古典的なプログラマーのやり方では、私は1つ離れています)。

真面目な話ですが、このスレッドで説明されている機能については、いくつかの追加のツールが役立ちます。

ストリンガーツールと同様に、「レンジャー」ツールは、上記でリンクしたコードでIter関数に相当するものを生成できます。

{Binary、Text} {Marshaler、Unmarshaler}の実装を生成して、ネットワーク経由での送信を容易にすることができます。

たまにとても役立つこのような小さなものがたくさんあると確信しています。

インターフェイスでシミュレートされた合計タイプの徹底的なチェックのためのいくつかの検査/リンターツールがあります。 ケースが欠落している場合や、型指定されていない無効な定数が使用されている場合に通知するiota列挙型がない理由はありません(おそらく、0以外のものを報告する必要がありますか?)。

言語を変更しなくても、その面では確かに改善の余地があります。

列挙型は、すでに確立されている型システムを補完します。 この号の多くの例が示しているように、列挙型の構成要素はすでに存在しています。 チャネルがより多くのプリミティブタイプに基づいて構築された高レベルの抽象化であるように、列挙型も同じ方法で構築する必要があります。 人間は傲慢で不器用で物忘れが多いので、列挙型のようなメカニズムは人間のプログラマーがプログラミングエラーを減らすのに役立ちます。

@bep私はあなたの3つのポイントすべてに反対しなければなりません。 Goの慣用的な列挙型はCの列挙型に非常に似ており、有効な値の反復がなく、文字列への自動変換がなく、必ずしも明確なIDがありません。

反復は便利ですが、ほとんどの場合、反復が必要な場合は、最初と最後の値の定数を定義するのが適切です。 iotaは自動的に過去1つになるため、新しい値を追加するときに更新を必要としない方法でこれを行うこともできます。 言語サポートが意味のある違いを生む状況は、列挙型の値が連続していない場合です。

文字列への自動変換は小さな値にすぎません。特にこの提案では、文字列値はint値に対応するように記述する必要があるため、文字列値の配列を自分で明示的に書き込むことで得られるメリットはほとんどありません。 別の提案では、それ以上の価値があるかもしれませんが、変数名を文字列表現に対応させることには欠点もあります。

最後に、はっきりとしたアイデンティティは、まったく便利な機能であるかどうかさえわかりません。 列挙型は、たとえばHaskellのように合計型ではありません。 それらは名前付きの数字です。 たとえば、列挙型をフラグ値として使用するのが一般的です。 たとえば、 ReadWriteMode = ReadMode | WriteModeを持つことができ、これは便利なことです。 他の値を使用することも可能です。たとえば、 DefaultMode = ReadModeを使用する場合があります。 いずれにせよ、誰かがconst DefaultMode = ReadModeを書くのを阻止できるような方法はありません。 別の宣言でそれが発生することを要求することは、どのような目的に役立ちますか?

@bep私はあなたの3つのポイントすべてに反対しなければなりません。 Goの慣用的な列挙型はCの列挙型に非常に似ており、有効な値の反復がなく、文字列への自動変換がなく、必ずしも明確なIDがありません。

@alercah 、このidomatic Goを、おそらく「勝利の議論」として議論に引き込まないでください。 Goには組み込みの列挙型がないため、存在しないidomについて話すことはほとんど意味がありません。

Goは、より優れたC / C ++またはより冗長性の低いJavaになるように構築されているため、後者と比較する方が理にかなっています。 また、JavaにはEnum typeが組み込まれています(「Javaプログラミング言語の列挙型は、他の言語の列挙型よりもはるかに強力です。」): https ://docs.oracle.com/javase/tutorial/java

そして、「はるかに強力な部分」に同意できないかもしれませんが、Java Enumタイプには、私が述べた3つの機能すべてがあります。

Goはよりスリムでシンプルなどであり、この方法を維持するにはある程度の妥協が必要であるという議論を理解できます。このスレッドでは、この種の動作のハッキーな回避策をいくつか見ましたが、 iotaのセットです。

列挙と自動文字列変換は、「生成する」機能の良い候補です。 すでにいくつかの解決策があります。 Java列挙型は、従来の列挙型と合計型の中間にあるものです。 ですから、私の意見ではそれは悪い言語デザインです。
慣用的なGoについてのことが重要であり、誰かが精通しているという理由だけで、言語Xから言語Yにすべての機能をコピーする強い理由はわかりません。

Javaプログラミング言語の列挙型は、他の言語の列挙型よりもはるかに強力です

それは10年前に真実でした。 合計タイプとパターンマッチングを利用したRustのOptionの最新のゼロコスト実装を参照してください。

慣用的なGoについてのことが重要であり、誰かが精通しているという理由だけで、言語Xから言語Yにすべての機能をコピーする強い理由はわかりません。

ここで与えられた結論にあまり同意しないことに注意してください。しかし、_慣用的なGo_を使用すると、Goは芸術的な台座になります。 ほとんどのソフトウェアプログラミングはかなり退屈で実用的です。 そして、多くの場合、ドロップダウンボックスに列挙型を入力する必要があります...

//go:generate enumerator Foo,Bar
一度書かれ、どこでも利用できます。 例は抽象的であることに注意してください。

@bep元のコメントを読み間違えたと思います。 「Goidiomaticenums」は、タイプFoo int + const-decl + iotaを使用する現在の構造を指すはずでしたが、「提案しているものはすべて慣用的ではない」とは言えません。

@rsc Go2ラベルに関しては、これはこの提案を提出する私の理由に反しています。 #19412は、完全な合計型の提案です。これは、ここでの単純な列挙型の提案よりも強力なスーパーセットであり、Go2でそれを確認したいと思います。 私の見解では、今後5年間でGo2が発生する可能性は非常に低く、より短い時間枠で何かが発生することを望んでいます。

新しい予約キーワードenumの提案がBCにとって不可能な場合、それが完全な言語統合であろうとgo vetに組み込まれたツールであろうと、それを実装する他の方法があります。 最初に述べたように、私は構文にこだわっていませんが、新しいユーザーに大きな認知的負担を加えることなく、今日のGoに価値のある追加になると強く信じています。

Go 2より前では、新しいキーワードを使用することはできません。これは、Go1の互換性保証に明らかに違反することになります。

個人的には、列挙型、さらに言えば、合計型、Go 2についても、説得力のある議論はまだ見ていません。私は、それらが起こり得ないと言っているわけではありません。 しかし、Go言語の目標の1つは、言語の単純さです。 言語機能が役立つだけでは十分ではありません。 すべての言語機能は便利です-それらが役に立たなかった場合、誰もそれらを提案しませんでした。 Goに機能を追加するには、その機能に、言語を複雑にする価値のある十分な説得力のあるユースケースが必要です。 最も説得力のあるユースケースは、この機能なしでは記述できないコードです。少なくとも今では、非常に厄介なことはありません。

Goで列挙型を見てみたいです。 有効な入力の数が限られている公開APIを制限したい(またはアプリの外部で制限されたAPIを使用したい)と常に思っています。 私にとって、これは列挙型に最適な場所です。

たとえば、ある種のRPCスタイルのAPIに接続し、指定された一連のアクション/オペコードを持つクライアントアプリを作成することができます。 これにはconstを使用できますが、無効なコードを送信することを妨げるものは何もありません。

その反対側で、同じAPIのサーバー側を記述している場合は、列挙型にswitchステートメントを記述できると便利です。これにより、コンパイラエラー(または少なくともいくつかのgo vet )がスローされます。 default:が存在する場合)。

これ(列挙型)は、Swiftが本当に正しかった領域だと思います。

ある種のRPCスタイルのAPIに接続し、指定された一連のアクション/オペコードを持つクライアントアプリを作成している可能性があります。 これにはconstsを使用できますが、無効なコードを送信することを妨げるものは何もありません。

これは、列挙型で解決するための恐ろしいアイデアです。 これは、RPCが突然失敗したり、ロールバック時にデータが読み取れなくなったりする可能性があるため、新しい列挙値を追加できないことを意味します。 proto3が生成された列挙型コードが「不明なコード」値をサポートすることを要求する理由は、これが苦痛によって学んだ教訓であるためです( proto2がこれをどのように解決したかと比較してください。 アプリケーションがこのケースを適切に処理できるようにする必要があります。

@Merovius私はあなたの意見を尊重しますが、丁寧に反対します。 有効な値のみが使用されていることを確認することは、列挙型の主な用途の1つです。

列挙型はすべての状況に適しているわけではありませんが、一部の状況には最適です。 適切なバージョン管理とエラー処理により、ほとんどの状況で新しい値を処理できるはずです。

uh-oh状態の外部プロセスを処理するには、確かに必須です。

列挙型(またはより一般的で便利な合計型)を使用すると、コンパイラーが強制的に処理する合計/列挙型に明示的な「不明な」コードを追加できます(または、実行できるのがすべての場合は、エンドポイントでその状況を完全に処理します)ログに記録して、次のリクエストに進みます)。

対処しなければならないXのケースがあることがわかっている場合、プロセス内で合計タイプの方が便利です。 小さいXの場合、管理は難しくありませんが、大きいXの場合、特にリファクタリングの際に、コンパイラが私に怒鳴りつけてくれたことに感謝します。

APIの境界を越えて使用するケースは少なく、拡張性の側面で常に誤りを犯す必要がありますが、ASTのように、本当にXの1つにしかなり得ないものや、「この時点で範囲がほぼ確定している「週」の値(カレンダーシステムの選択まで)。

@jimmyfrasche私はあなたに曜日を与えるかもしれませんが、ASTは与えません。 文法は進化します。 今日は無効である可能性があり、明日は完全に有効である可能性があり、ASTに新しいノードタイプを追加する必要がある可能性があります。 コンパイラーでチェックされたsum-typeを使用すると、これは破損なしでは不可能です。

そして、なぜこれが獣医チェックによって解決できないのかわかりません。 徹底的なケースの完全に適切な静的チェックを提供し、段階的な修復の可能性を私に与えます。

サーバーAPI用のクライアントの実装で遊んでいます。 一部の引数と戻り値は、APIの列挙型です。 合計45の列挙型があります。

私の場合、異なる列挙型の値の一部が同じ名前を共有しているため、Goで列挙定数を使用することはできません。 以下の例では、 Destroyが2回表示されるため、コンパイラーはエラーDestroy redeclared in this blockを発行します。

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

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

したがって、私は別の表現を考え出す必要があります。 理想的には、IDEが特定のタイプの可能な値を表示できるようにして、クライアントのユーザーがそれをより簡単に使用できるようにするものです。 Goで一級市民として列挙型を持っていることはそれを満足させるでしょう。

@kongslund完全な実装ではないことはわかっていますが、興味のあるコードジェネレーターを作成しました。 型宣言の上のコメントで列挙型を宣言するだけで、残りは生成されます。

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

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

生成します

const(
  _ TaskAllowedOperations = iota
  TaskAllowedOperationsCancel
  TaskAllowedOperationsDestroy
)

const(
  _ OnNormalExit = iota
  OnNormalExitDestroy
  OnNormalExitRestart
)

より良い部分は、プレフィックスを除外するString()メソッドを生成し、 "Destroy"をTaskAllowedOperationsまたはOnNormalExit $として解析できることです。

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

プラグが邪魔にならないようになりました...

個人的には、列挙型がgo言語の一部として含まれていないことを気にしません。これは、この問題に対する私の本来の感覚ではありませんでした。 最初に行ったとき、なぜそんなに多くの選択がなされたのかについて、私はしばしば混乱した反応をしました。 しかし、この言語を使用した後は、それが順守するシンプルさがあるのは素晴らしいことです。何か特別なことが必要な場合は、他の誰かがそれを必要としていて、その特定の問題を解決するための素晴らしいパッケージを作成した可能性があります。 私の裁量に残酷な量を保つ。

この議論では多くの有効な点が提起されており、列挙型のサポートに賛成するものもあれば、反対するものもあります(少なくとも、提案が「列挙型」とは何かについて何かを述べている限り)。 私のために突き出たいくつかのこと:

  • 導入例(今日のGoの列挙型)は誤解を招く可能性があります。そのコードは生成され、そのようなGoコードを手作業で作成する人はほとんどいません。 実際、提案(言語サポートでどのように見えるか)は、Goで実際に行っていることにはるかに近いものです。

  • @jediorangeは、Swiftが「本当に正しい(列挙型)」と述べています。それでも、Swiftの列挙型は驚くほど複雑な獣であり、あらゆる種類の概念が混在しています。 Goでは、他の言語機能と重複するメカニズムを意図的に回避し、その見返りとして、より多くの直交性を取得します。 プログラマーにとっての結果は、使用する機能(列挙型またはクラス、合計型(存在する場合)、またはインターフェース)を決定する必要がないことです。

  • 言語機能の有用性に関する@ianlancetaylorの指摘は、軽視してはなりません。 膨大な数の便利な機能があります。 問題は、どれが本当に説得力があり、コストに見合う価値があるかということです(言語が非常に複雑で、読みやすさ、そして実装が非常に複雑です)。

  • マイナーな点として、Goぎiota定義定数は、もちろんintに制限されていません。 それらが定数である限り、それらは(おそらく名前が付けられた)基本型(float、booleans、stringsを含む:https://play.golang.org/p/lhd3jqqg5z)に制限されます。

  • @meroviusは、(静的!)コンパイル時チェックの制限について良い点を示しています。 拡張できない列挙が、拡張が望ましい、または期待される(長寿命のAPIサーフェスは時間の経過とともに進化する)状況に適しているかどうかは非常に疑わしいです。

これは、この提案に関するいくつかの質問に私をもたらします。意味のある進歩を遂げる前に、答える必要があると私は信じています。

1)提案された列挙型の実際の期待は何ですか? @bepは、列挙可能性、反復可能性、文字列表現、アイデンティティについて言及しています。 もっとありますか? 少ないですか?

2)1)のリストを想定すると、列挙型を拡張できますか? もしそうなら、どのように? (同じパッケージですか?別のパッケージですか?)拡張できない場合は、どうしてですか? なぜそれが実際には問題にならないのですか?

3)名前空間:Swiftでは、列挙型によって新しい名前空間が導入されます。 名前空間名をどこでも繰り返す必要がないように、重要な機構(構文糖衣構文、型推論)があります。 たとえば、列挙型月の列挙値の場合、適切なコンテキストでは、Month.January(さらに悪いことにMyPackage.Month.January)ではなく.Januaryと書くことができます。 列挙型名前空間が必要ですか? もしそうなら、列挙型名前空間はどのように拡張されますか? これを実際に機能させるには、どのような糖衣構文が必要ですか?

4)列挙値は定数ですか? 不変の値?

5)列挙値に対してどのような操作が可能ですか(たとえば、反復以外):1つを前に、もう1つを後ろに移動できますか? 追加の組み込み関数または演算子が必要ですか? (すべての反復が順番に行われるとは限りません)。 最後の列挙値を超えて前進するとどうなりますか? それはランタイムエラーですか?

(https://github.com/golang/go/issues/19814#issuecomment-322771922の次の段落の言い回しを修正しました。以下の単語の不注意な選択についてお詫びします。)

これらの質問に実際に答えようとしない限り、この提案は無意味です(「自分がやりたいことを実行する列挙型が欲しい」は提案ではありません)。

これらの質問に実際に答えようとしない限り、この提案は無意味です

@griesemerあなたには素晴らしいポイント/質問がありますが、これらの質問に答えないことでこの提案に無意味なラベルを付けることはほとんど意味がありません。 このプロジェクトでは貢献の基準が高く設定されていますが、コンパイラーで博士号を取得せずに「何かを提案」できるようにする必要があります。また、提案は「実装の準備ができている」設計である必要はありません。

  • 待望の議論が始まったので、この提案が必要になりました=>価値と意味
  • それが提案#21473にもつながる場合=>価値と意味

導入例(今日のGoの列挙型)は誤解を招く可能性があります。そのコードは生成され、そのようなGoコードを手作業で作成する人はほとんどいません。 実際、提案(言語サポートでどのように見えるか)は、Goで実際に行っていることにはるかに近いものです。

@griesemer私は反対しなければなりません。 Go変数名を大文字のままにするべきではありませんでしたが、Goコミュニティで尊敬しているGoogle社員が書いた、私の提案とほぼ同じように見える手書きのコードがたくさんあります。 コードベースでも同じパターンに従うことがよくあります。 これは、Google CloudGoライブラリから抜粋した例です。

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

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

それらは複数の場所で同じ構成を使用します。
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

iotaを使用しても問題がない場合に、物事をより簡潔にする方法については、後でいくつかの議論がありました。これは、それ自体で役立つ可能性がありますが、ユースケースは限られています。 詳細については、以前のコメントを参照してください。 https://github.com/golang/go/issues/19814#issuecomment -290948187

@bepフェアポイント; 不注意な言葉の選択をお詫びします。 もう一度試してみましょう。今回は、上記の最後の段落をより丁寧かつ明確に表現してください。

有意義な進歩を遂げられるようにするために、この提案の提案者は、列挙型の重要な機能であると彼らが信じていることについてもう少し正確にしようとする必要があると思います(たとえば、https:// githubのいくつかの質問に答えることによって)。 com / golang / go / issues / 19814#issuecomment-322752526)。 これまでの議論から、望ましい機能はかなり漠然としか説明されていません。

おそらく最初のステップとして、既存のGoが(大幅に)不足し、列挙型が問題をより良く/より速く/より明確に解決する方法などを示すケーススタディがあると非常に便利です。Gopherconでの@rscの優れた講演も参照してください。 Go2言語の変更に関して。

@derekperkins列挙型ではなく、これらの(型指定された)定数定義と呼びます。 私たちの意見の相違は、「列挙型」が何であるかについての異なる理解によるものだと思います。したがって、上記の私の質問です。

(私の以前のhttps://github.com/golang/go/issues/19814#issuecomment-322774830は、もちろん@derekparkerではなく@derekperkinsに行く必要がありました。オートコンプリートは私を打ち負かしました。)

@derekperkinsのコメントから判断し、私自身の質問に部分的に答えると、Goの「列挙型」には少なくとも次の品質が必要であることがわかります。

  • (新しい)タイプの下で値のセットをグループ化する機能
  • 構文上のオーバーヘッドや定型文を最小限に抑えながら、そのタイプで名前(および対応する値がある場合は対応する値)を簡単に宣言できるようにします
  • 宣言の昇順でこれらの値を反復処理する機能

それは正しいですか? もしそうなら、このリストに他に何を追加する必要がありますか?

あなたの質問はすべて良いものです。

提案された列挙型の実際の期待は何ですか? @bepは、列挙可能性、反復可能性、文字列表現、アイデンティティについて言及しています。 もっとありますか? 少ないですか?

1)のリストを想定すると、列挙型を拡張できますか? もしそうなら、どのように? (同じパッケージですか?別のパッケージですか?)拡張できない場合は、どうしてですか? なぜそれが実際には問題にならないのですか?

次の2つの理由から、列挙型を拡張できるとは思いません。

  1. 列挙型は許容可能な値の全範囲を表す必要があるため、列挙型を拡張しても意味がありません。
  2. 通常のGo型を外部パッケージで拡張できないのと同じように、これは同じメカニズムと開発者の期待を維持します

名前空間:Swiftでは、列挙型によって新しい名前空間が導入されます。 名前空間名をどこでも繰り返す必要がないように、重要な機構(構文糖衣構文、型推論)があります。 たとえば、列挙型月の列挙値の場合、適切なコンテキストでは、Month.January(さらに悪いことにMyPackage.Month.January)ではなく.Januaryと書くことができます。 列挙型名前空間が必要ですか? もしそうなら、列挙型名前空間はどのように拡張されますか? これを実際に機能させるには、どのような糖衣構文が必要ですか?

私が言及したすべての例は型名の接頭辞であるため、名前空間がどのように発生したかを理解しています。 誰かが名前空間を追加することについて強く感じても反対はしませんが、それはこの提案の範囲外だと思います。 プレフィックスは現在のシステムにうまく適合します。

列挙値は定数ですか? 不変の値?

私は定数だと思います。

列挙値に対してどのような種類の操作が可能です(たとえば、反復以外に):1つを前に、もう1つを後ろに移動できますか? 追加の組み込み関数または演算子が必要ですか? (すべての反復が順番に行われるとは限りません)。 最後の列挙値を超えて前進するとどうなりますか? それはランタイムエラーですか?

スライス/配列(マップではない)の標準のGoプラクティスをデフォルトにします。 列挙値は、宣言の順序に基づいて反復可能になります。 少なくとも、範囲のサポートがあります。 インデックスを介して列挙型にアクセスできるようにすることは避けていますが、それについては強く感じていません。 これをサポートしないと、潜在的なランタイムエラーがなくなるはずです。

列挙型に無効な値を割り当てると、直接割り当てであろうと型キャストであろうと、新しいランタイムエラー(パニック?)が発生します。

これを正しく要約すると、提案する列挙値は型付き定数のようになります(定数のように、ユーザー定義の定数値を持つ場合があります)が、次のようになります。

  • また、同じ宣言で列挙値に関連付けられた列挙型(それ以外の場合は単なる定数)を定義します
  • 宣言の外に既存の列挙型の列挙値をキャスト/作成することはできません
  • それらを繰り返すことが可能です

それは正しいと思いますか? (これは、言語が列挙型に対して採用した、約45年前にPascalによって開拓された古典的なアプローチと一致します)。

はい、まさにそれが私が提案していることです。

switchステートメントはどうですか? 提案の主な推進力の1つであるAIUI。

基本的に何でもオンにできるので、列挙型をオンにできることを意味していると思います。 スイッチの列挙型を完全に満たしていない場合、swiftにエラーが発生するのは好きですが、獣医が処理できます。

@jediorange私は特に、(提案を完全な状態に保つために)徹底的なチェックが必要かどうかという、その最後の部分の質問に言及していました。 もちろん、「いいえ」は完全に良い答えです。

この問題の元のメッセージは、動機としてprotobufsに言及しています。 ここで示したセマンティクスでは、protobuf-compilerが任意の列挙型に対して追加の「認識されない」ケースを作成する必要があることを明示的に強調したいと思います(衝突を防ぐための名前マングリングスキームを意味します)。 また、デコードされたenum-valueがコンパイルされた範囲内にない場合は、enumを使用して生成された構造体にフィールドを追加する必要があります(ここでも、何らかの方法で名前をマングリングします)。 現在javaで行われているのと同じです。 または、おそらく、 intを引き続き使用します。

@Merovius私の最初の提案では、提案の主な動機としてではなく、例としてprotobufsについて言及していました。 あなたはその統合について良い点を持ち出します。 おそらく直交する懸念として扱われるべきだと思います。 私が見たほとんどのコードは、生成されたprotobufタイプからアプリレベルの構造体に変換し、それらを内部で使用することを好みます。 protobufは変更せずに続行でき、アプリの作成者がそれらをGo列挙型に変換したい場合は、変換プロセスで発生したエッジケースを処理できることは私には理にかなっています。

@derekperkinsその他の質問:

  • 明示的に初期化されていない列挙型の変数のゼロ値は何ですか? 一般的にゼロにすることはできないと思います(これにより、メモリの割り当て/初期化が複雑になります)。

  • 列挙値を使用して限定的な演算を実行できますか? たとえば、Pascal(私が一度プログラムしたときのことですが)では、驚くべきことに、1を超えるステップで反復する必要がありました。また、列挙値を計算したい場合もありました。

  • 反復に関して、go generateが生成された反復(およびstringify)のサポートが十分でないのはなぜですか?

明示的に初期化されていない列挙型の変数のゼロ値は何ですか? 一般的にゼロにすることはできないと思います(これにより、メモリの割り当て/初期化が複雑になります)。

最初の提案で述べたように、これは難しい決断の1つです。 定義の順序が反復にとって重要である場合、最初に定義された値をデフォルトにすることも同様に理にかなっていると思います。

列挙値を使用して限定的な演算を実行できますか? たとえば、Pascal(私が一度プログラムしたときのことですが)では、驚くべきことに、1を超えるステップで反復する必要がありました。また、列挙値を計算したい場合もありました。

数値ベースの列挙型と文字列ベースの列挙型のどちらを使用している場合でも、すべての列挙型に暗黙のゼロベースのインデックスがあることを意味しますか? 私がサポートされているrangeの反復のみに傾倒し、インデックスベースではないことを前に述べた理由は、配列やマップなどを使用できる基礎となる実装を公開しないためです。 インデックスを介して列挙型にアクセスする必要があるとは思いませんが、それが有益である理由がある場合は、それを禁止する理由はないと思います。

反復に関して、go generateが生成された反復(およびstringify)のサポートが十分でないのはなぜですか?

反復は個人的には私の主なユースケースではありませんが、提案に付加価値を与えると思います。 それが推進要因である場合、おそらくgo generateで十分でしょう。 それは価値の安全性を保証するのに役立ちません。 Stringer()引数は、生の値がiotaまたはint 、あるいは「実際の」値を表す他のタイプになることを前提としています。 また、 (Un)MarshalJSON 、 (Un)MarshalBinary 、 Scanner/Valuer 、およびStringer値が通信に使用されたことを確認するために使用する可能性のあるその他のシリアル化方法を生成する必要があります。 Goが内部で使用するものは何でも。

@griesemer少なくとも値の追加/削除に関して、列挙型の拡張性についてのあなたの質問に完全には答えていなかったと思います。 それらを編集する能力を持つことは、この提案の重要な部分です。

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

列挙型のセットを変更したいパッケージは、すべてのインポーターを自動的かつ強制的に破壊します

これが他の破壊的なAPIの変更とどのように違うのかわかりません。 タイプ、関数、または関数のシグネチャが変更された場合と同じように、BCを適切に処理するのはパッケージの作成者次第です。

実装の観点からは、デフォルト値がall-bits-zeroではない型をサポートすることは非常に複雑です。 今日、そのようなタイプはありません。 そのような機能を要求することは、この考えに対する印として数えられなければならないでしょう。

言語がチャネルを作成するためにmakeを必要とする唯一の理由は、チャネルタイプに対してこの機能を保持するためです。 それ以外の場合、 makeはオプションであり、チャネルバッファサイズを設定するか、既存の変数に新しいチャネルを割り当てるためにのみ使用されます。

@derekperkins他のほとんどのAPIの変更は、段階的な修復のために調整できます。 Russ Coxの説明を読むことを強くお勧めします。これにより、多くのことが非常に明確になります。

オープン列挙型(現在のconst + iotaコンストラクトなど)は、(たとえば)a)新しい値を使用せずに定義する、b)新しい値を処理するために逆依存関係を更新する、c)値の使用を開始することにより、段階的な修復を可能にします。 または、値を削除する場合は、a)値の使用を停止し、b)削除する値に言及しないように逆依存関係を更新し、c)値を削除します。

閉じた(コンパイラーが網羅性をチェックした)列挙型では、これは不可能です。 値の処理を削除するか、新しい値を定義すると、コンパイラーはスイッチケースの欠落についてすぐに文句を言います。 また、値を定義する前に値の処理を追加することはできません。

問題は、個々の変更が壊れていると見なすことができるかどうかではなく(それらは単独でできる)、壊れない分散コードベース上で壊れない一連のコミットがあるかどうかについてです。

実装の観点からは、デフォルト値がall-bits-zeroではない型をサポートすることは非常に複雑です。 今日、そのようなタイプはありません。 そのような機能を要求することは、この考えに対する印として数えられなければならないでしょう。

@ianlancetaylor完全な実装について話すことは絶対にできませんが、列挙型が0ベースの配列として実装されている場合( @griesemerが支持しているように聞こえます)、インデックスとしての0は次のようになります。 「all-bits-zero」を兼ねることができます。

閉じた(コンパイラーが網羅性をチェックした)列挙型では、これは不可能です。

@Merovius go vetまたは@jediorangeによって提案されたものとコンパイラによって強制されたものと同様のツールによって網羅性がチェックされた場合、それはあなたの懸念を軽減しますか?

@derekperkins彼らの有害性についてはそうです。 彼らの有用性の欠如についてではありません。 バージョンスキューの同じ問題は、通常考慮されるほとんどのユースケース(システムコール、ネットワークプロトコル、ファイル形式、共有オブジェクトなど)でも発生します。 proto3がオープン列挙型を必要とし、proto2が必要としないのには理由があります。これは、多くの停止とデータ破損のインシデントから学んだ教訓です。 グーグルはすでにバージョンスキューを避けるためにかなり注意を払っていますが。 私の見解では、デフォルトのケースを使用したオープン列挙型が正しい解決策です。 そして、無効な値に対する安全性の主張を除けば、私が言えることから、それらは実際には多くをテーブルにもたらしません。

そうは言っても、私は決定者ではありません。

@derekperkins https://github.com/golang/go/issues/19814#issuecomment -322818206で、(あなたの観点から)次のことを確認しています。

  • enum宣言は、名前付きenum値(定数)とともにenum型を宣言します
  • それらを繰り返すことが可能です
  • 宣言外の列挙型に値を追加することはできません
    そして後で:列挙型の切り替えは網羅的でなければなりません(またはそうでないかもしれません)(それほど重要ではないようです)

https://github.com/golang/go/issues/19814#issuecomment -322895247で、次のように言っています。

  • 最初に定義された値は、おそらくデフォルト(ゼロ)値である必要があります(これは反復では重要ではなく、列挙型変数の初期化では重要であることに注意してください)
  • 反復はあなたの主な動機ではありません

そしてhttps://github.com/golang/go/issues/19814#issuecomment-322903714で、「それらを編集する機能はこの提案の重要な部分です」と言っています。

私は混乱しています。つまり、反復は主要な動機ではありません。 これにより、少なくとも定数である列挙値の列挙宣言が残り、宣言の外に拡張することはできません。 しかし今、あなたはそれらを編集する能力が重要であると言っています。 どういう意味ですか? 確かにそれらを拡張できるというわけではありません(それは矛盾です)。 それらは変数ですか? (しかし、それらは定数ではありません)。

https://github.com/golang/go/issues/19814#issuecomment -322903714では、列挙型を0ベースの配列として実装できると言っています。 これは、列挙型宣言が、0ベースの定数インデックスである列挙型名の順序付きリストとともに新しい型を列挙値の配列(スペースが自動的に予約される)に導入することを示唆しています。 それはあなたが意味することですか? もしそうなら、なぜ固定サイズの配列とそれに伴う定数インデックスのリストを宣言するだけで十分ではないのでしょうか? 配列境界チェックは、列挙型の範囲を「拡張」できないことを自動的に確認し、反復はすでに可能です。

私は何が欠けていますか?

私は混乱しています。つまり、反復は主要な動機ではありません。

列挙型が必要な理由は私自身にありますが、 @ bepなど、このスレッドの他のユーザーが提案の必要な部分として表現していることも考慮に入れようとしています。

これにより、少なくとも定数である列挙値の列挙宣言が残り、宣言の外に拡張することはできません。 しかし今、あなたはそれらを編集する能力が重要であると言っています。 どういう意味ですか? 確かにそれらを拡張できるというわけではありません(それは矛盾です)。 それらは変数ですか? (しかし、それらは定数ではありません)。

私がそれらを編集すると言うとき、それらがオープン列挙型であるのは@Meroviusのポイントです。 ビルド時の定数ですが、永久にロックダウンされることはありません。

#19814(コメント)では、列挙型は0ベースの配列として実装できると言っています。

これは、 https: //github.com/golang/go/issues/19814#issuecomment -322884746と@ianlancetaylorのhttpsに基づいて、ペイグレードを超えて、舞台裏で実装される可能性があることを推測しているだけです。 //github.com/golang/go/issues/19814#issuecomment -322899668

「列挙値を使用して限定的な演算を実行できますか?たとえば、Pascal(1回プログラムしたとき、昔のことです)では、驚くべきことに、1を超えるステップで反復する必要がありました。また、列挙値を計算したい場合もありました。」

整数以外の列挙型に対してどのように計画するかはわかりません。したがって、その算術演算で、列挙型の各メンバーに宣言の順序に基づいて暗黙的にインデックスを割り当てる必要があるかどうかについての質問です。

実装の観点からは、デフォルト値がall-bits-zeroではない型をサポートすることは非常に複雑です。 今日、そのようなタイプはありません。 そのような機能を要求することは、この考えに対する印として数えられなければならないでしょう。

繰り返しになりますが、コンパイラがどのように機能するかわからないので、会話を続けようとしていました。 結局のところ、私は急進的なものを提案しようとはしていません。 前に述べたように、「これは、言語が列挙型に対して採用した、約45年前にPascalによって開拓された古典的なアプローチと一致します」、そしてそれは法案に適合します。

興味を示してくださった方は、お気軽にご参加ください。

もう1つの質問は、これらの列挙型を使用して配列またはスライスにインデックスを付けることができるかどうかです。 スライスは、列挙型->値のマッピングを表す非常に効率的でコンパクトな方法であることが多く、マップを要求することは残念なことだと思います。

@derekperkinsわかりました、それが私たち(または少なくとも私)を正方に戻すのではないかと心配しています:あなたが解決しようとしている問題は何ですか? 定数とおそらくiota(そして文字列表現を取得するためにgo generateを使用する)で現在行っていることをより良い方法で記述したいですか? つまり、(おそらく)過度に負担が大きいと感じる表記法の構文糖衣ですか? (それは良い答えです、ただ理解しようとしています。)

あなたはそれらを欲するあなた自身の理由があると言いました、おそらくあなたはそれらの理由が何であるかをもう少し説明することができます。 あなたが最初に示した例は私にはあまり意味がありませんが、おそらく何かが欠けています。

現状では、さまざまな回答から明らかなように、この提案(「列挙型」)が何を意味するのかについて、誰もが少し異なる理解を持っています。パスカル列挙型とスウィフト列挙型の間には非常に幅広い可能性があります。 あなた(または他の誰か)が提案されたものを非常に明確に説明しない限り(私は実装を求めていません、気に留めてください)、意味のある進歩を遂げることは困難であり、この提案のメリットについて議論することさえ困難です。

それは理にかなっていますか?

@griesemerそれは完全に理にかなっており、@ rscがGopherconで話していた通過する基準を理解しています。 あなたは明らかに私がこれまでよりもはるかに深い理解を持っています。 #21473で、当時は説得力のあるユースケースがなかったため、varsのiotaは実装されていないとおっしゃいました。 enumが最初から含まれていなかったのと同じ理由ですか? それがGoに付加価値をもたらすかどうかについてのあなたの意見を知りたいと思います。もしそうなら、どこからプロセスを開始しますか?

@derekperkins https://github.com/golang/go/issues/19814#issuecomment -323144075での質問について:当時(Goの設計では)、比較的単純な(たとえば、PascalまたはCスタイルの)列挙型のみを検討していました。 詳細をすべて覚えているわけではありませんが、列挙型に必要な追加の機械には十分なメリットがないという感覚は確かにありました。 私たちは、それらが本質的に栄光に満ちた一定の宣言であると感じました。

これらの従来の列挙型にも問題があります。それらを使用して算術演算を実行することは可能です(これらは単なる整数です)が、「(列挙型)範囲外」になるとはどういう意味ですか? Goでは、これらは単なる定数であり、「範囲外」は存在しません。 もう1つは反復です。Pascalには、列挙型の変数の値を前後に進めるための特別な組み込み関数(SUCCとPREDだと思います)がありました(Cでは++または-を実行します)。 ただし、ここでも同じ問題が発生します。最後を超えるとどうなりますか(++またはPascalの同等のSUCCを使用して列挙値にまたがるforループで非常に一般的な問題)。 最後に、enum宣言は新しい型を導入し、その要素はenum値です。 これらの値には名前(enum宣言で定義されている名前)がありますが、これらの名前は(Pascal、Cでは)型と同じスコープにあります。 これは少し不満です。2つの異なる列挙型を宣言する場合、競合することなく各列挙型に同じ列挙値名を使用できることを期待しますが、これは不可能です。 もちろん、Goもそれを解決しませんが、定数宣言も新しい名前空間を導入しているようには見えません。 より良い解決策は、列挙ごとに名前空間を導入することですが、列挙値を使用するたびに、列挙型名で修飾する必要があり、これは煩わしいことです。 Swiftは、可能な場合は列挙型を推測することでこれを解決します。その後、ドットが前に付いた列挙値名を使用できます。 しかし、それはかなりの機械です。 そして最後に、時々(多くの場合、パブリックAPIで)、列挙型宣言を拡張する必要があります。 それが不可能な場合(コードを所有していない場合)、問題があります。 定数を使用すると、これらの問題は存在しません。

おそらくそれ以上のものがあります。 これが頭に浮かぶことです。 最終的に、すでに持っている直交ツールを使用してGoで列挙型をエミュレートする方がよいと判断しました。誤った割り当ての可能性を低くするカスタム整数型と、構文糖衣のiotaメカニズム(および繰り返しの初期化式を除外する機能)です。

したがって、私の質問:構文上のオーバーヘッドがほとんどないGoでは適切にエミュレートできない特殊な列挙型宣言から何を得ようとしていますか? 列挙型と、宣言の外に拡張できない列挙型を考えることができます。 Swiftのように、列挙値の能力をもっと考えることができます。

列挙は、Goのgoジェネレーターを使用して簡単に解決できます。 すでにストリンガーがあります。 拡張を制限すると、APIの境界を越えて問題が発生します。 列挙値のより多くの能力(たとえばSwiftのように)は、多くの直交する概念を混合しているため、非常にうまくいかないようです。 Goでは、基本的なビルディングブロックを使用することでそれを達成できるでしょう。

@griesemer思いやりのある返信ありがとうございます。 私はそれらが基本的に栄光の定数宣言であることに同意しません。 Goで型安全性を持つことは素晴らしいことであり、 enumが個人的に私に提供する主な価値は価値の安全性です。 今日のGoでそれを模倣する方法は、その変数のすべてのエントリポイントで検証関数を実行することです。 それは冗長で間違いを犯しやすいですが、今日のように言語で可能です。 列挙型の前に型名を接頭辞として付けることで、すでに名前空間を作成しています。これは、冗長ではありますが、大したことではありません。

私は個人的にiotaのほとんどの用途が嫌いです。 かっこいいですが、ほとんどの場合、私のenumのような値は、dbや外部APIなどの外部リソースにマップされます。また、並べ替えた場合に値を変更しないように、より明確にしたいと思います。 iotaは、文字列値のリストを使用するため、 enumを使用するほとんどの場所でも役に立ちません。

結局のところ、この提案をどれだけ明確にできるかわかりません。 Goにとって意味のある方法でサポートされていればと思います。 正確な実装に関係なく、私はそれらを使用することができ、それらは私のコードをより安全にするでしょう。

Goが今日列挙する標準的な方法(https://github.com/golang/go/issues/19814#issuecomment-290909885に見られるように)はかなり正しいと思います。
いくつかの欠点があります。

  1. それらを繰り返すことはできません
  2. 文字列表現はありません
  3. クライアントは偽の列挙値を導入できます

#1がなくても大丈夫です。
go:generate + stringerは#2に使用できます。 それがユースケースを処理しない場合は、「列挙型」の基本型をintではなく文字列にし、文字列定数値を使用します。

3は、今日のGoでは扱いにくいものです。 私はこれをうまく処理するかもしれないばかげた提案をしています。

型定義にexplicitキーワードを追加します。 このキーワードは、このタイプが定義されているパッケージのconstブロックでの変換を除いて、このタイプへの変換を禁止します。 (またはrestricted ?またはenumはexplicit type $を意味しますか?)

上で参照した例を再利用して、

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

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

constブロック内でintからSearchRequestへの変換があります。 ただし、パッケージの作成者だけが新しいSearchRequest値を導入でき、誤って導入することはほとんどありません(たとえば、 SearchRequestを期待する関数にintを渡すことによって)。

私はこのソリューションを実際に積極的に提案しているわけではありませんが、無効なものを誤って構築しないことは、今日のGoでキャプチャできない列挙型の顕著な特性であると思います(未エクスポートのフィールドルート)。

列挙型の興味深いリスクは、型指定されていない定数の場合だと思います。 明示的な型変換を作成する人は、自分が何をしているのかを知っています。 特定の状況下でGoが明示的な型変換を禁止する方法を検討したいと思いますが、それは列挙型の概念と完全に直交していると思います。 これは、あらゆる種類のタイプに適用されるアイデアです。

ただし、型指定されていない定数は、明示的な型変換には当てはまらない方法で、誤って予期せずに型の値を作成する可能性があります。 したがって、 @ randall77のexplicitの提案は単純化して、型なし定数が暗黙的に型に変換されない可能性があることを意味すると思います。 明示的な型変換は常に必要です。

特定の状況下でGoが明示的な型変換を禁止する方法を検討したいと思います

@ianlancetaylor必要に応じて、明示的か暗黙的かを問わず、型変換を禁止することで、最初にこの提案を作成する原因となった問題を解決できます。 その場合、元のパッケージのみが作成できるため、任意のタイプを満たすことができます。 これは、 const宣言だけでなく、任意の型をサポートするため、いくつかの点でenumソリューションよりも優れています。

@ randall77 、@ ianlancetaylor explicitの提案を、そのタイプのゼロ値とどのように連携させるのでしょうか。

@derekperkins型変換を完全に禁止すると、 encoding/*パッケージなどの汎用エンコーダー/デコーダーでこれらの型を使用できなくなります。

@Merovius @ianlancetaylorは、暗黙的な変換(たとえば、型なし定数の制限付き型への割り当て)に対してのみ制限を提案していると思います。 明示的な変換は引き続き可能です。

@griesemer私は知っています:)しかし、私は@derekperkinsが別の方法で提案することを理解しました。

明示的な変換を許可すると、この「明示的な」修飾子について考えている理由そのものが損なわれませんか? 誰かが任意の値を「明示的な」タイプに変換することを決定できる場合、指定された値が現在よりも列挙型定数の1つであるという保証はありません。

これは、型指定されていない定数のカジュアルな使用や意図しない使用に役立つと思います。これはおそらく最も重要なことです。

明示的な変換を禁止することが「Goの精神」にあるのかどうか疑問に思っていると思います。 明示的な変換を禁止することは、コードの記述に基づくプログラミングではなく、型に基づくプログラミングに向けて大きな一歩を踏み出します。 囲碁は後者を支持する明確な立場を取っていると思います。

@griesemer @Merovius @ianlancetaylorからの引用をもう一度投稿します。これは、私の提案ではなく、彼の提案だったからです。

特定の状況下でGoが明示的な型変換を禁止する方法を検討したいと思います

@rogpeppeと@Meroviusはどちらも、その影響について良い点を示しています。 明示的な変換を許可するが暗黙的な変換を許可しないことは、有効な型を保証する問題を解決しませんが、汎用エンコーディングを失うことはかなり大きな欠点になります。

ここにはたくさんの行き来がありましたが、良いアイデアがいくつかあったと思います。 これが私が見たいもの(または同様のもの)の要約であり、他の人が言ったことと一致しているようです。 私は言語デザイナーでもコンパイラープログラマーでもないことを公然と認めているので、それがどれほどうまく機能するかはわかりません。

  1. 基本型のみ(string、uint、int、runeなど)をルートとする列挙型。 基本型が必要ない場合、デフォルトでuintになりますか?
  2. 列挙型のすべての有効な値は、型宣言-定数で宣言する必要があります。 無効な(型宣言で宣言されていない)値は、列挙型に変換できません。
  3. デバッグ用の自動文字列表現(あると便利です)。
  4. 列挙型のswitchステートメントのコンパイル時チェックで網羅性を確認します。 オプションで、将来の変更のためにすでに網羅的(エラーの可能性が高い)である場合でも、( go vet ?を介して) defaultのケースを推奨します。
  5. ゼロ値は本質的に無効である必要があります(列挙型宣言の何かではありません)。 個人的には、スライスのようにnilにしたいと思います。

その最後のものは少し物議をかもしているかもしれません。 そして、それが機能するかどうかはわかりませんが、意味的には適合すると思います- nilスライスをチェックするのと同じように、 nilをチェックすることができます

イテレーションに関しては、実際に使用することはないと思いますが、害は見られません。

それを宣言する方法の例として:

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

また、タイプを推測して「ドット構文」を使用するSwiftスタイルは_nice_ですが、絶対に必要というわけではありません。

タイプEnumAint
const
不明なEnumA = iota
AAA
)。


タイプEnumBint
const
不明なEnumB = iota
BBB
)。

1つのGoファイルに2つのコードを含めることも、同じパッケージに含めることも、1つを別のパッケージからインポートすることもできません。

列挙型を実装するC#の方法を実装してください:
タイプDaysenum {土、日、月、火、水、木、金}
タイプDaysenum [int] { Sat:1 、Sun、Tue、Wed、Thu、Fri}
タイプDaysenum [string] {Sat: "Saturay"、Sun: "Sunday"など}

@KamyarMそれはどのように優れています

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

と

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

コメントを新しいアプローチ/議論に限定するようお願いしたいと思います。 多くの人がこのスレッドに登録しており、ノイズ/繰り返しを追加することは、彼らの時間と注意を軽視していると見なされる可能性があります。 前のコメントの両方に対する詳細な回答を含め、そこにはたくさんの議論があります↑。 あなたは言われたことすべてに同意することはなく、これまでのところその議論の結果を好む側はいないかもしれませんが、それを単に無視することも生産的な方向に進むのに役立ちません。

名前の競合の問題がないため、より優れています。 コンパイラの型チェックもサポートします。 あなたが言及したアプローチは、何もないよりもうまく組織化されましたが、コンパイラーは、あなたがそれに割り当てることができるものにあなたを制限しません。 そのタイプのオブジェクトには、どの日でもない整数を割り当てることができます。
var a Days
a = 10
コンパイラは実際には何もしません。 したがって、この種の列挙型にはあまり意味がありません。 それ以外は、GoLandのようなIDEでより適切に編成されています

そんなものが見たい

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

または、自動iota使用の場合:

// 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
}

これにより、使いやすさとシンプルさが実現します。

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

また、enumには、ユーザー入力から何かを検証できるように、値を検証するための組み込みメソッドが必要です。

if day in WeekDay {
  makeItWorkOn(day)
}

そして、次のような単純なもの:

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

正直なところ、私のお気に入りの構文は次のようになります(KISS):

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

@zoonman最後の例は、Goの次の原則に従っていません。関数宣言はfuncで始まり、型宣言はtypeで始まり、変数宣言はvarで始まります。 、..。

@ md2perpe私はGoの「タイプ」の原則に従おうとはしていません。毎日コードを書いていますが、私が従う唯一の原則は、物事をシンプルに保つことです。
_原則に従うために_記述しなければならないコードよりも多くの時間が無駄になります。
TBH私は初心者ですが、批判できることはたくさんあります。
例えば:

struct User {
  Id uint
  Email string
}

書くよりも理解しやすい

type User struct {
  Id uint
  Email string
}

タイプを使用する場所の例を示します。

// 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}

以前はAsm、C、C ++、Pascal、Perl、PHP、Ruby、Python、JavaScript、TypeScriptでコードを記述していましたが、現在はGoです。 私はそのすべてを見ました。 この経験から、コードは簡潔で、読みやすく、理解しやすいものでなければならないことがわかります。

機械学習プロジェクトを作成し、MIDIファイルを解析する必要があります。
そこで、SMPTEタイムコードを解析する必要があります。 iotaで慣用的な方法を使用するのは非常に難しいと思いますが、それは私を止めません)

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
)

もちろん、防御的なプログラミングでランタイムチェックが必要になるかもしれません...

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

列挙型は、場合によってはプログラマーのライブをより簡単にします。 列挙型は単なる手段であり、適切に使用すると、時間を節約し、生産性を向上させることができます。 C ++、C#、その他の言語のように、Go2でこれを実装するのに問題はないと思います。 この例は単なる冗談ですが、問題を明確に示しています。

@ streeter12あなたの例がどのように「問題を明確に示している」のかわかりません。 列挙型はどのようにしてこのコードをより良くまたはより安全にするのでしょうか?

列挙型と同じロジックを実装したC#クラスがあります。

 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;
        }
   } 

コンパイル時の列挙型を使用すると、次のことができます。

  1. ランタイムチェックはありません。
  2. チームによるエラーの可能性を大幅に減らします(コンパイル時に間違った値を渡すことはできません)。
  3. iotaの概念と矛盾しません。
  4. 定数の名前を1つ持つよりも、ロジックを理解する方が簡単です(定数は、いくつかの低レベルのプロトコル値を表すことが重要です)。
  5. ToString()メソッドをアナログにして、値の単純な表現を作成できます。 (CONNECTION_ERROR.NO_INTERNETは0x12よりも優れています)。 ストリンガーについては知っていますが、列挙型を使用した明示的なコード生成はありません。
  6. 一部の言語では、値の配列、範囲などを取得できます。
  7. コードを読みながら理解するのは簡単です(頭の中で計算する必要はありません)。

結局のところ、これは一般的な人為的エラーを防ぎ、パフォーマンスを節約するための単なるツールです。

@ streeter12意味を明確にしてくれてありがとう。 ここでのGo定数に対する唯一の利点は、型システムが他の値を受け入れず、列挙値の1つを受け入れるため、無効な値を導入できないことです。 それは確かに素晴らしいことですが、代償も伴います。この列挙型をそのコードの外に拡張する方法はありません。 外部列挙型拡張は、Goで標準列挙型に反対することを決定した主な理由の1つです。

一部の拡張機能で列挙型を使用しないようにするという単純な必要性に答えてください。
FEは、ステートマシンに列挙型ではなくステートパターンを使用させる必要があります。

列挙型には独自のスコープがあります。 私は列挙型なしでいくつかの大きなプロジェクトを完了します。 列挙型を定義コードの外に拡張することは、アーキテクチャの決定がひどいことだと思います。 あなたはあなたの同僚がしていることを制御することができず、それはいくつかの面白いエラーを引き起こします)

また、多くの場合、大規模なプロジェクトでエラーを大幅に削減するヒューマンファクターの列挙型を忘れています。

@ streeter12残念ながら、実際には、列挙型を拡張する必要があることがよくあります。

@griesemerがenum / sum型を拡張すると、別の、場合によっては互換性のない型が作成されます。

列挙型/合計の明示的な型がない場合でも、これはGoでも当てはまります。 パッケージに{1、2、3}の値を期待する「列挙型」があり、「拡張列挙型」から4を渡した場合でも、暗黙の「型」のコントラクトに違反しています。

列挙型/合計を拡張する必要がある場合は、互換性のない場合を明示的に処理する明示的なTo / From変換関数も作成する必要があります。

この提案または#19412のような同様の提案に対するその議論と人々との断絶は、トレードオフが「時々あなたが変換関数を書く」のではなく、「コンパイラが処理できる基本的な検証コードを常に書く」ことであるというのは奇妙だと思うことだと思いますとにかく書く必要があるかもしれません」。

それは、どちらの側が正しいか間違っているか、またはそれが考慮すべき唯一のトレードオフであるということではありませんが、私が気付いた側の間のコミュニケーションのボトルネックを特定したかったのです。

この提案または#19412のような同様の提案に対するその議論と人々との断絶は、トレードオフが「時々あなたが変換関数を書く」のではなく、「コンパイラが処理できる基本的な検証コードを常に書く」ことであるというのは奇妙だと思うことだと思いますとにかく書く必要があるかもしれません」。

非常によく述べられている

@jimmyfrascheそれは私が個人的にトレードオフを説明する方法ではありません。 「コンパイラが処理できる基本的な検証コードを常に作成する」対「Goを使用するすべての人が学習して理解する必要のある型システムにまったく新しい概念を追加する」と言えます。

または、別の言い方をします。 私が知る限り、Goの列挙型のバージョンに欠けている唯一の重要な機能は、型なし定数からの代入の検証がなく、明示的な変換のチェックがなく、すべての値がスイッチ。 これらの機能はすべて、列挙型の概念から独立しているように思われます。 他の言語が列挙型を持っているという事実が、Goも列挙型を必要としているという結論に私たちを導くべきではありません。 はい、列挙型はそれらの不足している機能を提供します。 しかし、それらを取得するには、まったく新しい種類のタイプを追加する必要が本当にありますか? そして、言語の複雑さの増加は利益に値するのでしょうか?

@ianlancetaylor言語に複雑さを加えることは確かに考慮すべき有効なことであり、「他の言語がそれを持っているから」は確かに議論ではありません。 私は個人的に、列挙型はそれ自体で価値があるとは思いません。 (ただし、それらの一般化、合計タイプ、確かに多くのボックスにチェックマークを付けてください)。

タイプが割り当て可能性をオプトアウトする一般的な方法は便利ですが、プリミティブの外でどれほど広く役立つかはわかりません。

コンパイラに正当な値の完全なリストを知らせる方法がなければ、「スイッチで処理されるすべての値をチェックする」という概念がどれほど一般化できるかはわかりません。 列挙型と合計型を除いて、私が考えることができるのはAdaの範囲型のようなものだけですが、変換または反映されるたびにオフセットを処理するために範囲またはコードが生成される必要がない限り、これらはゼロ値と自然に互換性がありませんその上。 (他の言語にも同様のタイプのファミリーがあり、一部はパスカルファミリーにありますが、現時点で頭に浮かぶのはAdaだけです)

とにかく、私は具体的に言及していました:

ここでのGo定数に対する唯一の利点は、型システムが他の値を受け入れず、列挙値の1つを受け入れるため、無効な値を導入できないことです。 それは確かに素晴らしいことですが、代償も伴います。この列挙型をそのコードの外に拡張する方法はありません。 外部列挙型拡張は、Goで標準列挙型に反対することを決定した主な理由の1つです。

と

残念ながら、現実には、列挙型を拡張する必要があることがよくあります。

私が述べた理由により、その議論は私にはうまくいきません。

@jimmyfrasche理解しました; それは難しい問題です。 そのため、Goではそれを解決しようとはせず、定数値を繰り返す必要なしに定数のシーケンスを簡単に作成するメカニズムを提供するだけでした。

(遅延送信-https://github.com/golang/go/issues/19814#issuecomment-349158748への応答として意図されていました)

@griesemerは確かに、それは間違いなくGo 1の正しい呼び出しでしたが、その一部はGo2で再評価する価値があります。

言語には、列挙型から必要なものを_ほぼ_すべて取得するのに十分なものがあります。 タイプ定義よりも多くのコードが必要ですが、ジェネレーターはそのほとんどを処理でき、列挙型に付属するパワーを取得するだけでなく、状況に合わせて定義することができます。

このアプローチhttps://play.golang.org/p/7ud_3lrGfxは、以下を除くすべてを取得します

  1. 定義パッケージ内の安全性
  2. 完全性のためにスイッチをリントする機能

このアプローチは、小さくて単純な合計タイプにも使用できます†が、使用するのがより厄介です。そのため、 https: //github.com/golang/go/issues/19412#issuecomment-323208336のようなものを追加すると思います。言語とそれをコードジェネレーターで使用して、問題1と2を回避する列挙型を作成できます。

†この構造のjson.Tokenのスケッチについては、 https://play.golang.org/p/YFffpsvx5eを参照してください

トレードオフが、「とにかく作成しなければならない可能性のある変換関数を作成する」のではなく、「コンパイラが処理できる基本的な検証コードを常に作成する」というのは奇妙だと思います。

私(段階的な修復の激しい支持者の陣営の代表)にとって、これは正しい(っぽい)トレードオフのように思えます。 正直なところ、段階的な修復について話していなくても、それはより良いメンタルモデルだと思います。

1つは、型チェックされた列挙型は、とにかくソースコードに挿入された値のみをチェックできるようになることです。 列挙型がネットワーク上を移動する場合、ディスクに保持される場合、またはプロセス間で交換される場合、すべての賭けは無効になります(そして、列挙型のほとんどの提案された使用法はこのカテゴリに分類されます)。 したがって、実行時に非互換性を処理する問題を回避することはできません。 また、無効な列挙値に遭遇した場合の一般的な万能のデフォルトの動作はありません。 多くの場合、エラーが発生する可能性があります。 場合によっては、それをデフォルト値に強制したいことがあります。 ほとんどの場合、再シリアル化で失われないように、保存して渡す必要があります。

もちろん、有効性がチェックされ、必要な動作が実装される信頼境界がまだ存在する必要があると主張するかもしれません-そしてその境界内のすべてがその動作を信頼できるはずです。 そしてメンタルモデルは、この信頼の境界はプロセスであるべきだということのようです。 バイナリ内のすべてのコードはアトミックに変更され、内部的に一貫性が保たれるためです。 しかし、そのメンタルモデルは段階的な修復のアイデアによって侵食されます。 突然、自然な信頼境界は、アトミック修復を適用するユニットおよび自己無撞着であると信頼するユニットとしてのパッケージ(またはリポジトリ)になります。

そして、個人的には、非常に自然で素晴らしい自己一貫性の単位であることがわかりました。 パッケージは、そのセマンティクス、ルール、および規則を頭の中で維持するのに十分な大きさである必要があります。 これが、エクスポートがタイプレベルではなくパッケージレベルで機能する理由であり、トップレベルの宣言がプログラムレベルではなくパッケージレベルでスコープされる理由でもあります。 パッケージレベルでも不明な列挙値の正しい処理を決定するために、問題なく、十分に節約できるようです。 エクスポートされていない関数があり、それをチェックして、内部的に望ましい動作を維持します。

私は、網羅性チェックを含む型チェックされた列挙型を持つという提案よりも、すべてのスイッチにデフォルトのケースが必要であるという提案にはるかに賛成するでしょう。

@Meroviusあなたが言うように、オペレーティングシステムのプロセスとパッケージはどちらも信頼の境界です。

プロセス外からの情報は、その入口で検証され、プロセスの適切な表現にマーシャリングされていない必要があり、プロセスが失敗した場合は適切な注意が払われます。 それは決して消えません。 合計/列挙型に固有のものは実際には見当たりません。 構造体についても同じことが言えます。場合によっては、余分なフィールドを取得したり、フィールドが少なすぎたりします。 構造体はまだ便利です。

そうは言っても、列挙型を使用すると、もちろん、これらのエラーのモデル化に固有のケースを含めることができます。 例えば

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
}

合計タイプを使用すると、さらに先に進むことができます。

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
}

(前者は、エラーケースに固有のフィールドを持つ構造体に保持されていない限り、それほど有用ではありませんが、これらのフィールドの有効性は列挙型の値に依存します。sumタイプは、本質的にはそれを処理します。一度に1つのフィールドしか設定できない構造体。)

パッケージレベルでは、高レベルの検証を処理する必要がありますが、低レベルの検証はタイプに付属しています。 タイプのドメインを減らすことは、パッケージを小さくして頭の中に保つのに役立つと思います。 また、エディターがすべてのcase X:行を書き出して実際のコードを入力できるようにするか、リンターを使用してすべてのコードがすべてのケースをチェックしていることを確認できるように、ツールの意図を明確にします(以前にコンパイラーに網羅性があることについて私に話しました)。

合計/列挙型に固有のものは実際には見当たりません。 構造体についても同じことが言えます。場合によっては、余分なフィールドを取得したり、フィールドが少なすぎたりします。 構造体はまだ便利です。

オープン列挙型(現在iotaによって構築されているものなど)について話している場合は、確かに。 閉じた列挙型(人々が列挙型について話すときに通常話しているもの)または網羅性チェック付きの列挙型について話している場合、それらは確かに特別です。 それらは拡張可能ではないからです。

構造体との類似性は、これをかなり完全に説明しています。Go1互換性の約束は、キーなしの構造体リテラルをすべての約束から除外します。したがって、キー付きの構造体リテラルを使用することは、「最良」と強く見なされる慣行であり、獣医はそれをチェックします。 理由はまったく同じです。キーなしの構造体リテラルを使用している場合、構造体は拡張できなくなります。

あ、はい。 構造体は、この点で列挙型とまったく同じです。 そして、私たちはコミュニティとして、それらを拡張可能な方法で使用することが望ましいということに同意しました。

そうは言っても、列挙型を使用すると、もちろん、これらのエラーのモデル化に固有のケースを含めることができます。

あなたの例は、パッケージ境界ではなく、プロセス境界(ネットワークエラーについて話すことによる)のみをカバーしています。 FromTheNetworkに(何かを構成するために)「InvalidInternalState」を追加した場合、パッケージはどのように動作しますか? 再コンパイルする前にスイッチを修正する必要がありますか? その場合、段階的修復モデルでは拡張できません。 そもそもコンパイルするにはデフォルトのケースが必要ですか? そうすると、列挙型には意味がないようです。

繰り返しますが、列挙型を開くことは別の質問です。 私は次のようなものに乗ります

タイプのドメインを減らすことは、パッケージを小さくして頭の中に保つのに役立つと思います。 また、エディターがすべてのケースX:行を書き出して実際のコードを入力できるようにするか、リンターを使用してすべてのコードがすべてのケースをチェックしていることを確認できるように、ツールの意図を明確にします。

ただし、そのためには、型として実際の列挙型は必要ありません。 このようなリンティングツールは、 iotaを使用して$ const宣言をヒューリスティックにチェックすることもできます。ここで、すべてのケースは特定のタイプであり、「列挙型」と見なして、必要なチェックを実行します。 これらの「慣例による列挙型」を使用して、すべてのスイッチにデフォルトを設定する必要があること、またはすべての(既知の)ケースをチェックする必要があることをオートコンプリートまたはリンティングするのに役立つツールを完全に使用します。 私は、ほとんどそのように動作する列挙型キーワードを追加することに反対することさえありません。 つまり、列挙型は開いており(任意の整数値を取ることができます)、追加のスコープを提供し、任意のスイッチにデフォルトを設定する必要があります(追加のコストに対して、iota-enumを十分に追加するとは思いませんが、少なくとも彼らは私の議題を害することはありません)。 それが提案されているのであれば、問題ありません。 しかし、それはこの提案の支持者の大多数(確かに最初のテキストではない)が意味するものではないようです。

段階的な修復と拡張性を可能に保つことの重要性については意見が分かれます。たとえば、多くの人は、セマンティックバージョニングが解決する問題のより良い解決策であると信じています。 しかし、それらが重要であると思われる場合は、列挙型を有害または無意味と見なすことは完全に有効で合理的です。 そして、それは私が答えていた質問でした。コンパイラーにチェックを入れる代わりに、どこでもチェックを要求するというトレードオフを人々が合理的に行うことができる方法。 回答:APIの拡張性と進化を評価することにより、とにかく使用サイトでこれらのチェックが必要になります。

列挙型の反対者は時々、拡張可能ではないと言いました。シリアル化/遷移後もチェックが必要です。互換性を壊すことができます。

これが列挙型の問題ではないという主な問題は、開発とアーティキュレーションの問題です。
列挙型の使用がばかげている例を挙げようとしますが、いくつかの状況をより詳細に検討しましょう。

例1.私は低レベルの開発者であり、一部のレジスタアドレスにconstが必要であり、低レベルのプロトコル値を確立する必要があります。現在、Goでは1つの解決策しかありません。 。 1つのパッケージに対して、を押した後、複数の定数ブロックを取得できます。 私は20個の定数をすべて取得しましたが、それらが同じタイプで類似した名前を持っている場合、エラーが発生する可能性があります。 プロジェクトが大きい場合、このエラーが発生します。 防御的なプログラミング、TDDでこれを防ぐには、重複したチェックコードを頻繁に使用する必要があります(重複したコード=いずれの場合も重複したエラー/テスト)。 転送を使用すると、同様の問題は発生せず、この場合、値が変更されることはありません(レジスターアドレスが本番環境で変更される状況を見つけてください:))。 ファイル/ネットなどから取得した値が範囲内にあるかどうかを確認することもありますが、これを集中化する問題はありません(c#Enum.TryParseを参照)例えば)。 この場合、列挙型を使用すると、開発時間とパフォーマンスを節約できます。

例2.状態/エラーロジックを備えた小さなモジュールを開発しています。 私が列挙型をプライベートにすると、誰もこの列挙型について知ることができず、1のすべての利点を問題なく変更/拡張できます。コードをプライベートロジックに基づいている場合、開発で問題が発生しました。

例3.さまざまなアプリケーション向けに頻繁に変更され拡張可能なモジュールを開発しています。 パブリックロジック/インターフェイスを決定するために列挙型またはその他の定数を使用するのは奇妙な解決策です。 クライアント/サーバーアーキテクチャに新しい列挙型番号を追加すると、クラッシュする可能性がありますが、定数を使用すると、モデルの予測できない状態を取得し、ディスクに保存することもできます。 私はしばしば、予測できない状態よりもクラッシュを好む。 これは、バックコピー可能性/拡張の問題が列挙型ではなく開発の問題であることを示しています。 この場合、どの列挙型が適切でないかを理解している場合は、それらを使用しないでください。 私たちは選択するのに十分な能力を持っていると思います。

私の意見では、constsとコンパイル時enumの主な違いは、enumには2つの主要なコントラクトがあるということです。

  1. ネーミング契約。
  2. 値は契約します。
    この段落に対する賛成と反対のすべての議論は以前に検討されました。
    契約プログラミングを使用する場合、これの利点を簡単に理解できます。

他に不利な点がいくつあるかを列挙します。
ブレーキの可搬性がなければ交換できません。 しかし、SOLIDの原則からOを知っている場合、これは列挙型だけでなく、開発全般にも当てはまります。 誰かが言うことができます、私は並列ロジックと可変構造体で私のプログラムを醜くします。 可変構造を禁止しましょう。 これの代わりに、可変/可変なしの構造体を追加して、開発者に選択させることができます。

結局のところ、イオタにも欠点があることに注意したいと思います。

  1. 常にint型で、
  2. あなたは頭の中で値を計算する必要があります。 あなたは多くの時間を失って値を計算しようとすることができます、そしてそれが大丈夫であることを確認してください。
    enums / constを使用すると、F12キーを押すだけで、すべての値を表示できます。
  3. Iota式は、これもテストする必要があるコード式です。
    一部のプロジェクトでは、これらの理由でiotaの使用を完全に拒否しました。

列挙型の使用がばかげている例を挙げようとします

失礼ですが、このコメントの後、ここに立つ根拠はあまりないと思います。

そして、私はあなたが言うことさえしていませんでした-つまり、列挙型を使用することがばかげている場所の例を示すことです。 それらがどのように必要であるかを示し、それらがどのように傷つくかを説明することになっている例を取り上げました。

私たちは合理的に反対することができますが、少なくともすべての人が誠意を持って議論する必要があります。

例1

「レジスター名」は本当に変更できないものとしてお伝えするかもしれませんが、プロトコル値に関しては、拡張性と互換性のために任意の値を取るという立場は合理的であると断言します。 繰り返しますが、proto2-> proto3にはまさにその変更が含まれており、それは学んだ経験から行われました。

いずれにせよ、なぜリンターがこれを捕まえられないのかわかりません。

私は20個の定数をすべて取得しましたが、それらが同じタイプで類似した名前を持っている場合、エラーが発生する可能性があります。 プロジェクトが大きい場合、このエラーが発生します。

名前の入力を間違えている場合は、列挙型を閉じても役に立ちません。 シンボリック名を使用せず、代わりにint / string-literalsを使用する場合のみ。

例2

個人的には「大規模プロジェクトではない」ということで「シングルパッケージ」をしっかりと掲げる傾向があります。 したがって、列挙型を拡張するときに、ケースを忘れたり、コードの場所を変更したりする可能性ははるかに低いと思います。

いずれにせよ、なぜリンターがこれを捕まえられないのかわかりません。

例3

ただし、これは列挙型に提示される最も一般的なユースケースです。 適切な例:この特定の問題は、それらを正当化として使用します。 よく言及されるもう1つのケースは、システムコールです。これは、偽装したクライアントサーバーアーキテクチャです。 この例の一般化は、「2つ以上の独立して開発されたコンポーネントがそのような値を交換するコード」であり、非常に幅広く、それらのユースケースの大部分をカバーし、段階的修復モデルでは、エクスポートされたAPIもカバーします。 。

FTR、私はまだ列挙型が有害であることをだれにも納得させようとはしていません(私はそうしないと確信しています)。 私がどうやって彼らがそうだという結論に達したのか、そしてなぜ私が彼らに有利な議論を納得できないと思うのかを説明するためだけに。

常にint型で、

iotaは(必ずしもそうとは限りませんが)可能性がありますが、 constブロックはそうではなく、さまざまな定数型を持つことができます。実際、最も一般的に提案されている列挙型実装のスーパーセットです。

あなたは頭の中で値を計算する必要があります。

繰り返しますが、列挙型を支持する引数としてこれを使用することはできません。 enum-declarationの場合と同じように、定数を書き出すことができます。

Iota式は、これもテストする必要があるコード式です。

すべての式をテストする必要はありません。 それがすぐに明らかな場合、テストはやり過ぎです。 そうでない場合は、定数を書き留めてください。とにかくテストでそれを行います。

iotaは、Goで列挙型を実行するために現在推奨されている方法ではありません- const宣言はそうです。 iotaは、連続した、または公式のconst宣言を書き留めるときに、入力を節約するためのより一般的な方法としてのみ機能します。

そして、はい、Goのオープン列挙型には明らかに欠点があります。 それらは上で広く言及されています:あなたはスイッチのケースを忘れて、バグにつながるかもしれません。 名前空間はありません。 誤って非シンボリック定数を使用して、最終的に無効な値になる可能性があります(バグにつながる)。
しかし、固定ソリューション(列挙型)を使用して問題を解決する特定のトレードオフについて議論するよりも、これらのデメリットについて話し、提案されたソリューションのデメリットに対してそれらを測定する方が生産的であるように思われます。

私にとって、欠点のほとんどは、現在の言語で実用的に解決できます。リンターツールは、特定の種類のconst宣言を検出し、その使用法を確認します。 名前空間はこの方法では解決できません。これは素晴らしいことではありません。 しかし、列挙型とは異なる解決策があるかもしれません。

「レジスター名」は本当に変更できないものとしてお伝えするかもしれませんが、プロトコル値に関しては、拡張性と互換性のために任意の値を取るという立場は合理的であると断言します。 繰り返しますが、proto2-> proto3にはまさにその変更が含まれており、それは学んだ経験から行われました。

これが私が確立された価値観を言った理由です。 Fe wav形式のベースは何年も変更されておらず、優れたバック機能を備えています。 新しい値が残っている場合は、列挙型を使用していくつかの値を追加します。

名前の入力を間違えている場合は、列挙型を閉じても役に立ちません。 シンボリック名を使用せず、代わりにint / string-literalsを使用する場合のみ。

はい、それは私が良い名前を付けるのに役立ちませんが、それらは1つの名前でいくつかの値を整理するのに役立ちます。 場合によっては、開発プロセスが速くなります。 オートタイピングによるバリアントの数を1つに減らすことができます。

ただし、これは列挙型に提示される最も一般的なユースケースです。 適切な例:この特定の問題は、それらを正当化として使用します。 よく言及されるもう1つのケースは、システムコールです。これは、偽装したクライアントサーバーアーキテクチャです。 この例の一般化は、「2つ以上の独立して開発されたコンポーネントがそのような値を交換するコード」であり、非常に幅広く、それらのユースケースの大部分をカバーし、段階的修復モデルでは、エクスポートされたAPIもカバーします。 。

ただし、定数/列挙型を使用する/使用しないことで問題の核心が取り除かれるわけではありません。それでも、バックコピー可能性について考える必要があります。 問題は列挙型/定数ではなく、使用例にあると言いたいです。

個人的には「大規模プロジェクトではない」ということで「シングルパッケージ」をしっかりと掲げる傾向があります。 したがって、列挙型を拡張するときに、ケースを忘れたり、コードの場所を変更したりする可能性ははるかに低いと思います。

この場合でも、名前の変換とコンパイル時のチェックの利点があります。

すべての式をテストする必要はありません。 それがすぐに明らかな場合、テストはやり過ぎです。 そうでない場合は、定数を書き留めてください。とにかくテストでそれを行います。

多くの場合、コードのすべての行をテストする必要はないことを理解していますが、前例がある場合は、これをテストするか、書き直す必要があります。 私はイオタなしでこれを作る方法を知っていますが、私の古い例は単なる冗談です。

繰り返しますが、列挙型を支持する引数としてこれを使用することはできません。 enum-declarationの場合と同じように、定数を書き出すことができます。

列挙型の引数ではありません。

@メロヴィクス

閉じた列挙型(人々が列挙型について話すときに通常話しているもの)または網羅性チェック付きの列挙型について話している場合、それらは確かに特別です。 それらは拡張可能ではないからです。

また、安全に拡張することもできません。

あなたが持っている場合

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

と

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

q内では、 Dを使用しても安全です( p Enum(3)の独自のラベルが追加されない限り)。 pに戻す: p.Makeの結果を取得して、その状態をDに遷移させることができますが、 p.Takeを呼び出す場合は、それがq.Dが渡されず、 A 、 B 、 Cのいずれか1つだけを取得するようにする必要があります。そうしないと、バグが発生します。 これを回避するには、

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) {...}

言語のクローズドタイプの有無にかかわらず、クローズドタイプを持つことのすべての問題がありますが、コンパイラがあなたを監視していません。

あなたの例は、パッケージ境界ではなく、プロセス境界(ネットワークエラーについて話すことによる)のみをカバーしています。 FromTheNetworkに「InvalidInternalState」(何かを構成するため)を追加した場合、パッケージはどのように動作しますか? 再コンパイルする前にスイッチを修正する必要がありますか? その場合、段階的修復モデルでは拡張できません。 そもそもコンパイルするにはデフォルトのケースが必要ですか? そうすると、列挙型には意味がないようです。

列挙型だけでも、上記のようにして、追加の状態と書き込み変換関数を使用して独自のバージョンを定義する必要があります。

ただし、列挙型として使用する場合でも合計型は構成可能であるため、この方法で非常に自然かつ安全に合計型を「拡張」できます。 私は例を挙げましたが、より明確に言うと、

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

Enumは次のように「拡張」できます

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

今回は、新しいバージョンのpでD $を追加しても完全に安全です。 唯一の欠点は、 q.Enumの内部からp.Enumの状態に到達するためにダブルスイッチする必要があることですが、それは明示的で明確であり、前述したように、エディターはスケルトンを吐き出す可能性があります自動的にスイッチアウトします。

ただし、そのためには、型として実際の列挙型は必要ありません。 このようなリンティングツールは、iotaを使用してconst-declarationsをヒューリスティックにチェックすることもできます。ここで、すべてのケースは特定のタイプであり、「列挙型」と見なして、必要なチェックを実行します。 これらの「慣例による列挙型」を使用して、すべてのスイッチにデフォルトを設定する必要があること、またはすべての(既知の)ケースをチェックする必要があることをオートコンプリートまたはリンティングするのに役立つツールを完全に使用します。

これには2つの問題があります。

const / iotaを介してそのドメインのサブセットにラベルが与えられた定義済みの積分型がある場合:

1つは、閉じた列挙型または開いた列挙型を表すことができます。 主に閉じた型をシミュレートするために使用されますが、一般的に使用される値に名前を付けるために使用することもできます。 架空のファイル形式のオープン列挙型について考えてみます。

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
)

これは、0、1、および42がレコードタイプのドメインであると言っているわけではありません。 コントラクトははるかに微妙であり、モデル化するには依存型が必要になります。 (それは間違いなく行き過ぎでしょう!)

2つ目は、定数ラベルを使用して定義された積分型は、ドメインが制限されていることを意味するとヒューリスティックに想定できます。 上記から誤検知が発生しますが、完璧なものはありません。 go / typesを使用して定義からこの疑似型を抽出し、その型の値に対するすべての切り替えを調べて、すべてに必要なラベルが含まれていることを確認できます。 これは役立つかもしれませんが、現時点では網羅性を示していません。 すべての有効な値を確実にカバーしていますが、無効な値が作成されていないことは証明されていません。 そうすることはできません。 値のすべてのソース、シンク、および変換を見つけて、それらを抽象的に解釈して、無効な値が作成されていないことを静的に保証できたとしても、reflectは真を認識していないため、実行時の値については何も言えません。型システムでエンコードされていないため、型のドメイン。

これを回避する列挙型と合計型の代替手段がありますが、独自の問題があります。

型リテラルrange m nが、少なくともm、最大でnの整数型を作成するとします(すべてのvについて、m≤v≤n)。 これで、列挙型のドメインを制限できます。

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

ドメインのサイズ=ラベルの数であるため、switchステートメントがすべての可能性を使い果たしているかどうかを100%の信頼度でリントすることができます。 その列挙型を外部に拡張するには、マッピングを処理するための型変換関数を作成する必要がありますが、とにかくそれを行う必要があると私は主張します。

もちろん、これは実際には実装するタイプの驚くほど微妙なファミリーであり、Goの他の部分ではうまく機能しません。 また、これ以外の用途は多くなく、ニッチなユースケースもあります。

段階的な修復と拡張性を可能に保つことの重要性については意見が分かれます。たとえば、多くの人は、セマンティックバージョニングが解決する問題のより良い解決策であると信じています。 しかし、それらが重要であると思われる場合は、列挙型を有害または無意味と見なすことは完全に有効で合理的です。 そして、それは私が答えていた質問でした。コンパイラーにチェックを入れる代わりに、どこでもチェックを要求するというトレードオフを人々が合理的に行うことができる方法。 回答:APIの拡張性と進化を評価することにより、とにかく使用サイトでこれらのチェックが必要になります。

基本的な列挙型については、同意します。 このディスカッションの開始時には、合計タイプよりも選択された場合は単に不満でしたが、今ではなぜそれらが有害であるかを理解しています。 私のためにそれを明確にしてくれたあなたと@griesemerに感謝します。

合計タイプの場合、あなたが言ったことは、コンパイル時にスイッチが網羅的であることを要求しない正当な理由だと思います。 クローズドタイプには多くの利点があり、ここで検討した3つのタイプの中で、合計タイプは他のタイプの欠点がなく最も柔軟であると私はまだ考えています。 これらは、他の優れたタイプと同様に、不正な値によって引き起こされるエラーを回避しながら、拡張性や段階的な修復を阻害することなくタイプを閉じることができます。

私がPythonやJavaScript、その他の一般的な型なし言語よりもgolangを使用する主な理由は、型の安全性です。 私はJavaで多くのことを行いましたが、Javaが提供するgolangで見逃していることの1つは、安全な列挙型です。

列挙型でタイプを区別できることに同意しません。 intが必要な場合は、Javaのように、代わりにintに固執してください。 安全な列挙型が必要な場合は、次の構文をお勧めします。

type enums enum { foo, bar, baz }

@rudolfschmidt 、私はあなたに同意します、それもそのように見えるかもしれません:

type DaysOfTheWeek enum {
  Monday
  Tuesday
}

ただし、小さな落とし穴があります。データを検証したり、JSONに変換したり、FSとやり取りしたりする必要がある場合は、 enumを制御できる必要があります。
列挙型が符号なし整数のセットであると盲目的に仮定すると、iotaになってしまう可能性があります。
イノベーションを起こしたいのなら、使い方の利便性を考えなければなりません。
たとえば、着信JSON内の値が列挙型の有効な要素であることを簡単に検証できますか?
ソフトウェアが変更されていると言ったらどうなりますか?

暗号通貨のリストがあるとしましょう:

type CryptoCurrency enum {
  BTC
  ETH
  XMR
}

複数のサードパーティシステムとデータを交換します。 あなたがそれらの何千も持っているとしましょう。
あなたには長い歴史があり、ストレージ内のデータの数があります。 時が経ち、たとえば、ビットコインは最終的には死にます。 誰もそれを使用していません。
したがって、構造から削除することにします。

type CryptoCurrency enum {
  ETH
  XMR
}

これにより、データが変更されます。 enumのすべての値がシフトしたためです。 大丈夫です。 データの移行を実行できます。 あなたのパーナーについてはどうですか、それらのいくつかはそれほど速く動いていない、いくつかはリソースを持っていない、または単にいくつかの理由でそれを行うことができません。
しかし、あなたはそれらからデータを取り込んでいます。 したがって、2つの列挙型を持つことになります。古いものと新しいものです。 と両方を使用するデータマッパー。
これは、列挙型の定義に柔軟性を持たせ、それらの種類のデータを検証およびマーシャリング/アンマーシャリングする機能を提供することを示しています。

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
}

列挙型とユースケースの適用可能性について考える必要があります。

ボイラープレートコードの数千行を節約できれば、2つの新しいキーワードを学ぶことができます。

中間点は、実際に、列挙値が何であるかを制限することなく、列挙値を検証する機能を持つことです。 列挙型はほとんど同じです-それは名前付き定数の束です。 列挙型の変数は、列挙型の基になる型の任意の値と等しくすることができます。 その上に追加するのは、値を検証して表示する機能です。有効な列挙値が含まれているかどうか。 そして、ストリング化のような他のボーナスがあるかもしれません。

非常に多くの場合、私はプロトコル(protobufまたはthrift)を持っていて、至る所にたくさんの列挙型があります。 それぞれを検証する必要があり、列挙値が不明な場合は、そのメッセージを破棄してエラーを報告します。 そのようなメッセージを処理する方法は他にありません。 列挙型が単なる定数の集まりである言語では、考えられるすべての組み合わせをチェックする大量のswitchステートメントを作成する以外に方法はありません。 これは大量のコードであり、間違いが必ず含まれます。 C#のようなものを使用すると、列挙型を検証するための組み込みサポートを使用できるため、時間を大幅に節約できます。 一部のprotobuf実装は実際にそれを内部で実行しており、その場合は例外をスローします。 ロギングがいかに簡単になるかは言うまでもありません。箱から出して文字列化を行うことができます。 protobufがStringer実装を生成するのは素晴らしいことですが、コード内のすべてがprotobufであるとは限りません。

ただし、値を保存する機能は、メッセージを破棄したくないが、無効であってもメッセージを処理する場合に役立ちます。 クライアントは通常メッセージを破棄できますが、サーバー側では多くの場合、すべてをデータベースに保存する必要があります。 コンテンツを破棄することはできません。

したがって、私にとって、列挙値を検証する機能には真の価値があります。 検証以外の何もしないボイラープレートコードの数千行を節約できます。

この機能をツールとして提供することは、私には非常に簡単に思えます。 その一部はすでにstringer-toolに存在します。 enumer Fooのように呼び出すツールがある場合、 Foo fmt.Stringerメソッドを生成し、(たとえば) Known() boolメソッドを生成して保存された値は既知の値の範囲内にありますが、それで問題が軽減されますか?

@Meroviusは、他に何もなければ便利です。 しかし、私は一般的にautogenに反対しています。 それが便利でうまく機能する唯一のケースは、一度コンパイルできるかなり安定したプロトコルがあるprotobufのようなものです。 一般に列挙型に使用すると、単純な型システムの松葉杖のように感じられます。 そして、Goが型安全性についてどのようになっているのかを見ると、それは言語自体の哲学に反していると感じます。 言語支援の代わりに、実際には言語エコシステムの一部ではないインフラストラクチャの上にこのインフラストラクチャの開発を開始します。 言語に欠けているものを実装するためではなく、検証のために外部ツールを残します。

。 一般に列挙型に使用すると、単純な型システムの松葉杖のように感じられます。

それは-Goの型システムは有名で意図的に単純化されているからです。 しかし、それは問題ではありませんでした。問題は、それがあなたの問題を軽減するかどうかということでした。 「私はそれが好きではない」ことを除けば、私はそれがどのようにそうでないかを実際にはわかりません(とにかくオープン列挙型を想定している場合)。

そして、Goが型安全性についてどのようになっているのかを見ると、それは言語自体の哲学に反していると感じます。

Goは「型安全性のすべて」ではありません。 Idrisのような言語は、すべて型安全性に関するものです。 Goは大規模なエンジニアリングの問題に関するものであり、そのため、Goの設計は解決しようとしている問題によって決定されます。 たとえば、その型システムでは、APIの変更によるさまざまなバグを検出でき、大規模なリファクタリングが可能になります。 ただし、学習を容易にし、コードベースの相違を減らし、サードパーティのコードの可読性を高めるために、意図的にシンプルに保たれています。

そのため、関心のあるユースケース(オープン列挙型)を言語を変更せずに、同じように読みやすいコードを生成するツールで解決できる場合、それはGoの哲学に非常に一致しているように見えます。 特に、既存の機能のサブセットである新しい言語機能を追加することは、Goの設計と一致していないようです。

繰り返しになりますが、関係するボイラープレートを生成するツールを使用しても実際の問題が解決されない方法を拡張できれば便利です-それ以外の場合は、とにかく機能の設計を通知するためにそれを理解する必要があるためです。

ディスカッションのアイデアをいくつか組み合わせましたが、それについてどう思いますか?

いくつかの基本的な情報:

  1. 他のすべてのタイプと同じように列挙型を拡張できます。
  2. これらは定数のように格納されますが、タイプ名がプレフィックスとして使用されます。 理由:現在のiota-enumを使用する場合、おそらくすべての定数のプレフィックスとしてenumの名前を記述します。 この機能を使用すると、それを回避できます。
  3. それらは不変であり、他のすべての定数と同様に扱われます。
  4. 列挙型を反復処理できます。 これを行うと、マップのように動作します。 キーは列挙型の名前であり、値は列挙型の値です。
  5. 他のすべてのタイプの場合と同様に、列挙型にメソッドを追加できます。
  6. すべての列挙値は、自動生成されたメソッドを使用する必要があります。
  7. Name()は列挙型変数の名前を返します
  8. Index()は、自動的に増加する列挙型インデックスを返します。 配列が始まるところから始まります。

コード:
`` `行く
パッケージメイン

//例A
type Country enum [struct] {//列挙型は他の型を拡張できます(例Bを参照)
Austria( "AT"、 "Austria"、false)// constのようにアクセスできますが、タイプは
Germany( "DE"、 "Germany"、true)//プレフィックス(例:Country.Austria)

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

}

//列挙型は他のすべてのタイプと同様のメソッドを持つことができます
func(c Country)test(){}

func main(){
println(Country.Austria.CountryName)//オーストリア
println(Country.Germany.Code)// 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
}

}

//例B
タイプCoolnessenum [int] {
VeryCool(10)
Cool(5)
NotCool(0)
} `` `

@sinnlosername列挙型は非常に理解しやすいものでなければならないと思います。 前の説明で提示されたアイデアのいくつかを組み合わせることは、必ずしも列挙型の最良のアイデアにつながるとは限りません。

以下は簡単に理解できると思います。

宣言

type Day enum {
    Monday
    Tuesday
    ...
    Sunday
}

文字列変換( Stringerインターフェイスを使用):

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

とても簡単です。 これの利点は、列挙型を渡すときに、より強力な型の安全性を可能にすることです。

使用例

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

ここでstring定数を使用してDayを表す場合、 IsWeekdayは、 "sat"または"sun"以外の文字列を示します。は平日です(つまり、 IsWeekday("abc")は何を返す/すべきですか?)。 対照的に、上記の関数の定義域は制限されているため、関数はその入力に関してより意味のあるものになります。

@ljeabmreosn

おそらく

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

私はgolangチームが必要な方法で言語を改善するのを待つのをあきらめました。 私は誰もがラストラングを見てみることをお勧めできます、それはすでに列挙型やジェネリックスなどのようなすべての望ましい機能を備えています。

2018年5月14日ですが、列挙型のサポートについてはまだ話し合っています。 一体何なの? 個人的に私はgolangに失望しています。

機能を待っている間、イライラする可能性があることは理解できます。 しかし、このような非建設的なコメントを投稿しても役に立ちません。 コメントは尊重してください。 https://golang.org/conductを参照してください。

ありがとう。

@ agnivade @ rudolfschmidtに同意する必要があります。 GoLangは、機能やAPIが不足していることと、Goの作成者による過去の過ちを変更したり受け入れたりすることへの抵抗が大きすぎるため、私のお気に入りの言語ではありません。 しかし、私は職場での最新のプロジェクトにどの言語を選択するかについての意思決定者ではなかったため、現時点では選択の余地はありません。 だから私はそのすべての欠点に取り組む必要があります。 しかし、正直なところ、それはGoLangでコードを書く拷問のようなものです;-)

私はgolangチームが必要な方法で言語を改善するのを待つのをあきらめました。

  • 必要という言葉は「欲しいもの」という意味ではありません。

実際には、すべての現代語のコア機能が必要です。 GoLangにはいくつかの優れた機能がありますが、プロジェクトが保守的であると存続しません。 列挙型やジェネリックスなどの機能は、それらを嫌う人にとっては不利な点はありませんが、それらを使用したい人にとっては多くの利点があります。

そして、私に「しかし、行きたいのは単純なままでいたい」と言わないでください。 「シンプル」と「実際の機能なし」には大きな違いがあります。 Javaは非常にシンプルですが、多くの機能が欠けています。 したがって、Java開発者がウィザードであるか、この引数が悪いだけです。

実際には、すべての現代語のコア機能が必要です。

もちろん。 これらのコア機能はチューリング完全性と呼ばれます。 _Everything_elseは設計上の選択です。 チューリング完全性とC ++の間には(たとえば)多くのスペースがあり、そのスペースには多くの言語を見つけることができます。 分布は、グローバル最適が存在しないことを示唆しています。

GoLangにはいくつかの優れた機能がありますが、プロジェクトが保守的であると存続しません。

おそらく。 これまでのところ、それはまだ成長しています。 IMO、それが保守的でなければ、それはまだ成長していなかったでしょう。 私たちの意見はどちらも主観的であり、技術的にはあまり価値がありません。 支配するのはデザイナーの経験と好みです。 別の意見を持っていても構いませんが、それはデザイナーがそれを共有することを保証するものではありません。

ところで、人々が要求する機能の10%が採用されたとしたら、今日のGoがどうなるかを想像すると、今ではおそらくGoをもう使用しないでしょう。

実際、あなたは私の答えの最も重要な議論を見逃しただけです。 多分それはあなたが言ったことのいくつかに対するカウンターであるからでしょう。

「列挙型やジェネリックスなどの機能は、それらを嫌う人にとっては不利な点はありませんが、それらを使用したい人にとっては多くの利点があります。」

そして、なぜこの保守性がゴランの成長の理由だと思いますか? これは、golangの効率と標準ライブラリの優れたセットに関連している可能性が高いと思います。

また、Javaは、Java 9で重要なことを変更しようとしたときに、一種の「クラッシュ」を経験しました。これにより、おそらく多くの人が代替手段を検索するようになりました。 しかし、このクラッシュの前にJavaを見てください。 開発者の生活を楽にする機能がどんどん増えてきたので、それは絶えず成長していました。

「列挙型やジェネリックスなどの機能は、それらを嫌う人にとっては不利な点はありませんが、それらを使用したい人にとっては多くの利点があります。」

それは明らかに真実ではありません。 すべての機能は、最終的にはインポートしたいstdlibやパッケージに到達します。 _Everyone_は、新しい機能が好きかどうかに関係なく、それらに対処する必要があります。

これまでのところ、それはまだ成長しています。 IMO、保守的でなければまだ成長していなかったでしょう

その成長の遅さ(もしあれば)は保守性によるものではなく、むしろ標準ライブラリ、既存の言語機能のセット、ツールによるものだと思います。 それが私をここに連れてきた理由です。 言語機能を追加しても、その点では何も変わりません。

C#とTypescript、さらにはRust / Swiftを見ると。 彼らは狂ったような新機能を追加しています。 C#は、上下に変動する上位言語のままです。 Typescriptは非常に急速に成長しています。 Rust / Swiftについても同じです。 一方、Goは、2009年と2016年に人気が急上昇しました。しかし、その間はまったく成長せず、実際には失われていました。 Goは、新しい開発者がすでにそれを知っていて、何らかの理由で以前にそれを選択しなかった場合、何も提供しません。 Goがデザインに停滞しているからです。 他の言語が機能を追加するのは、他に何もすることがないからではなく、実際のユーザーベースがそれを要求しているからです。 人々は、コードベースが絶えず変化する問題のドメインに関連し続けるために、新しい機能を必要としています。 async / awaitのように。 実際の問題を解決するために必要でした。 今では多くの言語でそれを見ることができるのは当然のことです。

最終的にはGo2が登場し、多くの新しい開発者が登場することは間違いありません。 それが新しくて光沢があるからではなく、新しい機能が誰かに最終的に切り替えるか試してみるように説得するかもしれないからです。 保守性がそれほど重要であるならば、私たちはこれらの提案さえ持っているでしょう。

その成長の遅さ(もしあれば)は保守性によるものではなく、むしろ標準ライブラリ、既存の言語機能のセット、ツールによるものだと思います。 それが私をここに連れてきた理由です。

そして、それは保守的な蜂の結果です。 言語が[半]年ごとに何か/すべてを壊す場合、あなたはそれを持ってくる人がはるかに少なくなるので、あなたがGoについてあなたが評価することは何もしません。

言語機能を追加しても、その点では何も変わりません。

よろしいですか? 上記を参照。


ところで、 2017年の調査結果を見たことがありますか?

言語が[半]年ごとに何か/すべてを壊す場合

その後、何も壊さないでください。 C#は大量の機能を追加し、下位互換性に違反することはありませんでした。 それは彼らにとっても選択肢ではありません。 C ++でも同じだと思います。 Goが何かを壊さずに機能を追加できない場合、それはGoの問題であり、おそらくそれがどのように実装されているかという問題です。

ところで、2017年の調査結果を見たことがありますか?

私のコメントは、2017/2018年の調査、TIOBEインデックス、およびさまざまな言語で何が起こっているかについての私の一般的な観察に基づいています。

@cznic
誰もがそれらに対処する必要がありますが、あなたはそれらを使用する必要はありません。 iota列挙型とマップを使用してコードを記述したい場合でも、これを行うことができます。 また、ジェネリックが気に入らない場合は、ジェネリックなしでライブラリを使用してください。 Javaは、両方を持つことが可能であることを証明しています。

その後、何も壊さないでください。

良いアイデア。 ただし、提案されている言語の変更のほとんどではないにしても、多くは、_これを含めて_、重大な変更です。

C#は大量の機能を追加し、下位互換性に違反することはありませんでした。

事実を確認してください: Visual C#2010 BreakingChanges 。 (最初のWeb検索結果、それが唯一の例であるかどうかを推測することしかできません。)

私のコメントは、2017/2018年の調査、TIOBEインデックス、およびさまざまな言語で何が起こっているかについての私の一般的な観察に基づいています。

では、調査結果で回答者数が年々70%増加しているのに、言語が成長していないことをどのように確認できますか?

「重大な変更」をどのように定義しますか? 列挙型またはジェネリックを追加した後でも、goコードのすべての行が機能します。

誰もがそれらに対処する必要がありますが、あなたはそれらを使用する必要はありません。 iota列挙型とマップを使用してコードを記述したい場合でも、これを行うことができます。 また、ジェネリックが気に入らない場合は、ジェネリックなしでライブラリを使用してください。 Javaは、両方を持つことが可能であることを証明しています。

同意できません。 言語が、たとえばジェネリックスになると、私がそれらを使用しなくても、それらは至る所で使用されます。 内部的にAPIを変更しない場合でも。 その結果、私はそれらの影響を非常に受けています。なぜなら、それらを使用するプログラムの構築を遅らせることなく、ジェネリックを言語に追加する方法は確かにないからです。 別名「フリーランチなし」。

「重大な変更」をどのように定義しますか? 列挙型またはジェネリックを追加した後でも、goコードのすべての行が機能します。

もちろん違います。 このコードは、この提案ではコンパイルされなくなります。

package foo

var enum = 42

必要という言葉は「欲しいもの」という意味ではありません。

確かに、それは意味しません、そして私はそれを決して意味しませんでした。 もちろん、そのような機能は必要ないと答えることはできますが、それなら私は一般的に必要なものに答えることができます。 何も必要なく、ペンと紙に戻ることができます。

Golangは、大規模なチーム向けの言語であると主張しています。 golangを使用して大規模なコードベースを開発できるかどうかはわかりません。 そのためには、実行時エラーを可能な限り回避するために、静的コンパイルと型チェックが必要です。 列挙型とジェネリックなしでこれを行うにはどうすればよいですか? これらの機能は、凝ったものでも、持っているのも素晴らしいものではありませんが、本格的な開発には絶対に不可欠です。 それらがない場合は、どこでもインターフェイス{}を使用することになります。 コードでinterfaces {}を使用することを余儀なくされた場合、データ型を持つことのポイントは何ですか?

確かに、選択肢がない場合もそうしますが、rustのように、すでにすべてのものを提供し、golangよりも実行が速い代替手段がある場合は、なぜそうする必要がありますか? 私は、goに次のような考え方の未来があるかどうか本当に疑問に思っています。

必要という言葉は「欲しいもの」という意味ではありません。

私はオープンソースへのすべての貢献を尊重し、golangが趣味のプロジェクトである場合、それは素晴らしいことですが、golangは真剣に受け止めたいと考えており、現時点では退屈な開発者にとってはおもちゃであり、それを変える意志はありません。

APIを変更する必要はありません。新しいAPIパーツのみがジェネリックを使用する可能性がありますが、インターネットにはジェネリックのない代替手段が常に存在する可能性があります。

そして、両方とも、コンパイルが少し遅く、「列挙型」と呼ばれる変数は最小限の効果です。 実際、99%の人はそれに気付かず、他の1%は許容できる小さな変更を追加するだけで済みます。 これは、たとえばJavaのジグソーパズルに匹敵するものではありません。

そして、両方とも、コンパイルが少し遅く、「列挙型」と呼ばれる変数は最小限の効果です。 実際、99%の人はそれに気付かず、他の1%は許容できる小さな変更を追加するだけで済みます。

誰かがそのような素晴らしいパフォーマンスを持つ設計と実装を持って来ることができれば、誰もが幸せになるでしょう。 #15292に貢献してください。

ただし、これが「バッキングデータなしで自分に有利な数字を引く」という名前のゲームの場合は、申し訳ありませんが、私は参加しません。

ジェネリックとの速度の違いについて何か数字はありますか?

そして、はい、これらの数値は、「列挙型」と呼ばれる変数を持つ確率がそれほど高くないことを示しているだけなので、データに裏付けられていません。

列挙型をGoに追加するかどうか、またどのように追加するかという具体的な質問については、この問題を購読している人がたくさんいることを皆さんに思い出させたいと思います。 「Goは良い言語ですか?」という一般的な質問。 と「Goは機能の提供にもっと焦点を当てるべきですか?」 おそらく別のフォーラムで議論したほうがいいでしょう。

ジェネリックとの速度の違いについて何か数字はありますか?

いいえ、それが私が何も投稿しなかった理由です。 私は、コストをゼロにすることはできないと投稿しただけです。

そして、はい、これらの数値は、「列挙型」と呼ばれる変数を持つ確率がそれほど高くないことを示しているだけなので、データに裏付けられていません。

それは混乱しています。 スローダウンはジェネリック医薬品に関するものでした。 「enum」は下位互換性に関するものであり、誤った「_Every_行のgoコードはenumまたはジェネリックを追加した後も機能します。」 請求。 (私のことを強調します)

@Meroviusあなたは正直です、私は今黙っています。

これを列挙型に戻すと、この問題の内容であり、Goがジェネリックを必要とする理由についての議論は完全に理解していますが、Goが列挙型を必要とする理由についての議論ははるかに不安定です。 実際、私は上記のhttps://github.com/golang/go/issues/19814#issuecomment -290878151でこれを尋ねましたが、まだ動揺しています。 それに対する良い答えがあったなら、私はそれを逃しました。 誰かがそれを繰り返すか、それを指すことができますか? ありがとう。

@ianlancetaylorユースケースは複雑ではないと思います。値が事前定義された値のセットに属することを保証するタイプセーフな方法が必要です。これは、今日のGoでは不可能です。 唯一の回避策は、RPCや関数呼び出しなど、コードへのすべての可能なエントリポイントで手動で検証することです。これは本質的に信頼性がありません。 反復するための他の構文上の優れた点により、多くの一般的なユースケースが簡単になります。 あなたが価値があると思うかどうかは主観的であり、明らかに上記の議論のどれもその力を説得するものではなかったので、私はこれまで言語レベルで扱われることを本質的にあきらめました。

@ianlancetaylor :すべてが型安全性と関係があります。 タイプを使用して、タイプミスや互換性のないタイプの使用によるランタイムエラーのリスクを最小限に抑えます。 現時点では、goで書き込むことができます

if enumReference == 1

現時点では、列挙型は単なる数値またはその他のプリミティブデータ型であるためです。

そのコードはまったく不可能であり、避けるべきです。 何年も前にJavaコミュニティで行ったのと同じ議論が、彼らが重要性を理解したために列挙型を導入した理由です。

あなただけが書くことができるはずです

if enumReference == enumType

if enumReference == 1がより隠された方法で発生し、実行時にのみ発生する追加の問題につながる可能性があるシナリオを想像するのに、あまり多くのファンタジーは必要ありません。

ただ言及したいのですが、Goにはその可能性がありますが、プログラミングの新しい概念やパラダイムについて話し合うように、何年にもわたって証明され理解されてきたものや概念がここで議論されるのは奇妙です。 型の安全性を確保するための別の方法がある場合は、列挙型よりも優れたものがあるかもしれませんが、私にはわかりません。

Goにはその可能性がありますが、プログラミングの新しい概念やパラダイムについて話し合うように、何年にもわたって証明され理解されてきたものや概念がここで議論されるのは奇妙です。

Afaisは、特にジェネリックス、合計型などに関する他の議論をフォローしている間、それを持っているかどうかではなく、それをどのように実装するかについてです。 Java型システムは非常に拡張性があり、十分に仕様が定められています。 それは大きな違いです。

Goでは、コンパイラの複雑さを増やさずに、言語に機能を追加する方法を考え出そうとしています。 それは通常あまりうまく機能せず、彼らにそれらの最初のアイデアを放棄させます。

私も、これらの優先順位は現在の形式と品質では無意味だと思いますが、最善の策は、可能な限り単純で混乱の少ない実装を考え出すことです。 他の何かはあなたをさらに前進させません、imo。

@ derekperkins @ rudolfschmidtありがとう。 C ++には列挙型がありますが、提案している機能はC ++にはないことを明確にしておきます。 したがって、これについて明らかなことは何もありません。、

一般に、列挙型の変数がその列挙の値のみを受け入れることができる場合、それは役に立ちません。 特に、任意の整数から列挙型への変換が必要です。 そうしないと、ネットワーク接続を介して列挙型を送信できません。 できますが、列挙値ごとに大文字と小文字を区別するスイッチを作成する必要があります。これは非常に面倒な作業のようです。 それで、変換を行うとき、コンパイラは、型変換中に値が有効な列挙値であるかどうかのチェックを生成しますか? そして、値が無効な場合はパニックになりますか?

列挙型の値はシーケンシャルである必要がありますか、それともC ++のように任意の値を取ることができますか?

Goでは、定数は型指定されていないため、整数から列挙型への変換を許可する場合、 if enumVal == 1を禁止するのは奇妙です。 しかし、私たちはできると思います。

Goの一般的な設計原則の1つは、Goを作成する人は、型ではなくコードを作成することです。 コードを書くときに役立つ列挙型から得られる利点はまだわかりません。 それらは、Goには通常ない種類の型制約のセットを追加しているようです。 良くも悪くも、Goは型の値を制御するメカニズムを提供していません。 したがって、Goに列挙型を追加することを支持する議論はまだ説得力がないように思われると私は言わなければなりません。

繰り返しますが、列挙型を現在の状態に保ち、その上に機能を追加することに賛成です。

  • 列挙型には、基になる値型とそれに関連付けられたいくつかの名前付き定数があります
  • コンパイラは、基になる型に互換性がある限り、任意の値から列挙値への変換を許可する必要があります。 タイプintの値は、任意の整数列挙型に変換可能である必要があります。
  • 無効な列挙値につながる変換が許可されます。 列挙型は、変数が取ることができる値に制約を課すべきではありません。

その上に提供されるものは次のとおりです。

  • 列挙値の文字列化。 私の経験から、UIとロギングに非常に役立ちます。 列挙値が有効な場合、stringificationは定数の名前を返します。 無効な場合は、基になる値の文字列表現を返します。 -1が特定の列挙型Fooの有効な列挙値でない場合、文字列化は-1を返す必要があります。
  • 開発者が判断できるようにします。値は実行時に有効な列挙値ですか。 あらゆるタイプのプロトコルで作業する場合に非常に便利です。 プロトコルが進化するにつれて、プログラムが認識しない新しい列挙値が導入される可能性があります。 または、単純な間違いである可能性があります。 現在、列挙値が厳密にシーケンシャルであることを確認するか(常に強制できるものではありません)、すべての可能な値を手動でチェックする必要があります。 この種のコードは非常に速く大きくなり、間違いが必ず発生します。
  • 開発者が列挙型のすべての可能な値を列挙できるようにする可能性があります。 私はここでこれを求める人々を見ました、他の言語もこれを持っています、しかし私は実際にそれを自分で必要としたことを覚えていません、それで私はこれに賛成する個人的な経験がありません。

私の正当な理由は、コードを記述してバグを回避することです。 これらのタスクはすべて、開発者が手作業で行うのは面倒で不要であり、コードやビルドスクリプトを複雑にする外部ツールを導入することさえあります。 これらの機能は、列挙型を過度に複雑にしたり制限したりすることなく、列挙型から必要なすべてをカバーします。 GoにはSwiftやJavaの列挙型のようなものは必要ないと思います。


switchステートメントがすべての可能な列挙値をカバーすることをコンパイル時に検証することについての議論がありました。 私の提案では役に立たないでしょう。 枯渇チェックを実行しても無効な列挙値はカバーされないため、それらを処理するにはデフォルトのケースが必要です。 これは、段階的なコード修復をサポートするために必要です。 ここでできることは、switchステートメントにデフォルトのケースがない場合に警告を生成することだけだと思います。 しかし、それは言語を変えなくても行うことができます。

@ianlancetaylorあなたの議論にはいくつかの欠陥があると思います。

一般に、列挙型の変数がその列挙の値のみを受け入れることができる場合、それは役に立ちません。 特に、任意の整数から列挙型への変換が必要です。 そうしないと、ネットワーク接続を介して列挙型を送信できません。

プログラマーの抽象化は問題ありません。 Goは多くの抽象化を提供します。 たとえば、次のコードはコンパイルされません。

package main

import "fmt"

const NULL = 0x0

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

しかし、 Cでは、このスタイルのプログラムがコンパイルされます。 これは、 Goが強く型付けされており、 Cは型付けされていないためです。

列挙型のインデックスは内部に格納されている場合がありますが、変数のアドレスと同様に、抽象化としてユーザーには非表示になっています。

@zerkmsはい、それは1つの可能性ですが、 dの型を考えると、型推論が可能であるはずです。 ただし、列挙型の修飾された使用法(例のように)は少し読みやすくなっています。

@ianlancetaylorは、あなたが話している列挙型の非常にCバージョンです。 きっとたくさんの人がいると思いますが、それを望んでいますが、imo:

列挙値には数値プロパティを含めないでください。 各列挙型の値は、コンパイル時に適用され、数値や他の列挙型とは関係のない、個別のラベルの独自の有限ユニバースである必要があります。 このような値のペアで実行できるのは、 ==または!=だけです。 その他の操作は、メソッドとして、または関数を使用して定義できます。

実装はこれらの値を整数にコンパイルしますが、それは安全でないか反省する場合を除いて、プログラマーに直接公開される正当な理由がある基本的なことではありません。 bool(0)を実行してfalse $を取得できないのと同じ理由で。

列挙型を数値またはその他のタイプとの間で変換する場合は、すべてのケースを書き出し、状況に適したエラー処理を含めます。 それが面倒な場合は、stringerなどのコードジェネレーターまたは少なくとも何かを使用して、switchステートメントのケースに入力します。

値をプロセス外に送信する場合、明確に定義された標準に従っている場合、またはソースからコンパイルされたプログラムの別のインスタンスと通信していることがわかっている場合、または何かを作成する必要がある場合は、intが適しています。問題が発生する可能性がある場合でも、可能な限り最小のスペースに収まりますが、通常はこれらのいずれも当てはまらないため、値が型定義のソース順序の影響を受けないように文字列表現を使用することをお勧めします。 プロセスAのグリーンがプロセスBのブルーになることは望ましくありません。定義のアルファベット順を維持するために、他の誰かがグリーンの前にブルーを追加する必要があると判断したためです。 unrecognized color "Blue"が必要です。

これは、多くの状態を抽象的に表すための優れた安全な方法です。 それらの状態が何を意味するかを定義するためにプログラムを離れます。

(もちろん、多くの場合、データをそれらの状態に関連付けたい場合、そのデータのタイプは状態ごとに異なります。。。。)

@ljeabmreosn私のポイントは、Goが整数から列挙型への変換を許可している場合、型なし定数が自動的に列挙型に変換されるのは当然だということでした。 Goは整数からポインタ型への変換を許可していないため、反例は異なります。

@jimmyfrasche整数型と列挙型の間で変換するスイッチを作成する必要がある場合、Goで問題なく機能することに同意しますが、率直に言って、言語自体に追加するのは十分に役に立たないようです。 これは、合計型の特殊なケースになります。これについては、#19412を参照してください。

ここにはたくさんの提案があります。

一般的なコメント:列挙型との間で変換できる基になる値(intなど)を公開しない提案については、ここにいくつかの質問に答えます。

列挙型のゼロ値は何ですか?

ある列挙型から別の列挙型にどのように移動しますか? 多くの人にとって、曜日は列挙型の標準的な例だと思いますが、水曜日から木曜日まで「インクリメント」したいと思うかもしれません。 そのために大きなswitchステートメントを書く必要はありません。

(また、「文字列化」に関しては、曜日の正しい文字列は言語とロケールに依存します。)

@josharian文字列化は通常、列挙値の名前をコンパイラによって自動的に文字列に変換することを意味します。 ローカリゼーションなどはありません。 ローカリゼーションのように、その上に何かを構築したい場合は、他の手段でそれを行い、他の言語はそれを行うための豊富な言語とフレームワークツールを提供します。

たとえば、一部のC#タイプには、カルチャ情報も取得するToStringオーバーライドがあります。 または、 DateTimeオブジェクト自体を使用して、形式とカルチャ情報の両方を受け入れるToStringメソッドを使用することもできます。 ただし、これらのオーバーライドは標準ではなく、誰もが継承するobjectクラスにはToString()しかありません。 Goのストリンガーインターフェースとほとんど同じです。

したがって、ローカリゼーションはこの提案と一般的な列挙型の範囲外である必要があると思います。 それを実装したい場合は、他の方法で実行してください。 たとえば、カスタムストリンガーインターフェイスのように。

@josharian実装に関しては、それでもintであり、ゼロ値はすべてビットゼロであるため、ゼロ値はソース順の最初の値になります。 これは一種の情報漏えいですが、たとえば月曜日と日曜日のどちらで週を開始するかを決定するなど、ゼロ値を選択できるため、実際には非常に便利です。 もちろん、残りの用語の順序がそのような影響を与えないことや、最初の要素を変更した場合に値を並べ替えることが重要な影響を与える可能性があることは、それほど良いことではありません。 ただし、これはconst / iotaと実際には何の違いもありません。

@crekerが言ったことを文字列化してください。 拡大するには、しかし、私は期待します

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

日曜日を0ではなく印刷します。ラベルは値であり、その表現ではありません。

明確にするために、暗黙のStringメソッドが必要だと言っているのではなく、ラベルが型の一部として格納され、リフレクションによってアクセスできるようにするだけです。 (たぶん、Printlnはreflect.ValueでLabel()を呼び出します。列挙型などからですか?fmtがそのブードゥーをどのように実行するかを深く調べていません。)

ある列挙型から別の列挙型にどのように移動しますか? 多くの人にとって、曜日は列挙型の標準的な例だと思いますが、水曜日から木曜日まで「インクリメント」したいと思うかもしれません。 そのために大きなswitchステートメントを書く必要はありません。

リフレクションや大きなスイッチが正しいと思います。 一般的なパターンは、go generateで簡単に入力して、その型またはその型のファクトリ関数でメソッドを作成できます(おそらく、コンパイラーによって認識されて、表現の算術演算になります)。

すべての列挙型に全順序がある、またはそれらが循環的であると仮定することは私には意味がありません。 type failure enum { none; input; file; network }を考えると、無効な入力がファイル障害よりも少ないこと、ファイル障害を増やすとネットワーク障害が発生すること、またはネットワーク障害を増やすと成功することを強制することは本当に意味がありますか?

主な用途が循環順序値であると仮定すると、これを処理する別の方法は、パラメーター化された整数型の新しいクラスを作成することです。 これは悪い構文ですが、議論のために、 I%Nとしましょう。ここで、 Iは整数型で、 Nは整数定数です。 このタイプの値を使用するすべての算術演算は、暗黙的にmod Nです。次に、次のことができます。

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

したがって、土曜日+ 1 ==日曜日と平日(456)==月曜日。 無効な平日を作成することはできません。 ただし、const / iotaの外部でも役立つ可能性があります。

@ianlancetaylorが私が本当に欲しいのは合計型であると指摘したように、それをnumber-yにしたくない場合のために。

任意のモジュラー算術型を導入することは興味深い提案です。 その場合、列挙型は次の形式になる可能性があります。これにより、簡単なStringメソッドが得られます。

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

type Weekday = uint % len(Weekdays)

任意のサイズのintと組み合わせると、int128、int256なども取得できます。

いくつかの組み込みを定義することもできます。

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

コンパイラーは、以前よりも多くの境界を証明できます。 また、APIは型を介してより正確なアサーションを提供できます。たとえば、 math/bitsの関数Len64は$# uint % 64を返すことができるようになりました。

RISC-Vポートで作業するときは、命令エンコーディングコンポーネントが12ビットであるため、 uint12タイプが必要でした。 それはuint % (1<<12)であった可能性があります。 多くのビット操作、特にプロトコルは、これから恩恵を受ける可能性があります。

もちろん、欠点は重大です。 Goは型よりもコードを好む傾向があり、これは型が多いです。 +や-のような操作は、突然%と同じくらい高くなる可能性があります。 ある種の型パラメトリシティがなければ、ほとんどすべてのライブラリ関数と相互運用するために、おそらく正規のuint8 、 uint16などに変換する必要があります。失敗(範囲外のパニック変換を実行する方法がない限り、それ自体が複雑になります)。 そして、HTTPステータスコードにuint % 1000を使用するなど、使いすぎていることがわかります。

それにもかかわらず、興味深いアイデアです。 :)


その他のマイナーな返信:

それは一種の誠実さを漏らしている

これは私に彼らが本当にintであると思わせます。 :)

一般的なパターンは、gogenerateで簡単に埋めることができます

とにかく列挙型を使用してコードを生成する必要がある場合は、文字列関数や境界チェックなどを生成し、言語サポートの重みの代わりにコード生成を使用して列挙型を実行することもできます。

すべての列挙型に全順序がある、またはそれらが循環的であると仮定することは私には意味がありません。

けっこうだ。 これにより、具体的なユースケースをいくつか持つことで、列挙型から必要なものを正確に明確にすることができると思います。 明確な要件のセットはなく、他の言語構造(つまり現状)を使用して列挙型をエミュレートすることが最も理にかなっていると思います。 しかし、それは単なる仮説です。

@crekerが言ったことを文字列化してください。

けっこうだ。 しかし、どれだけの場合が曜日のようになるのだろうか。 確かに、ユーザー向けのものは何でも。 そして、文字列化は列挙型の主要な要求の1つであるようです。

@josharian本当にintである列挙型は、おそらく同様のメカニズムを必要とします。 それ以外の場合、 enum { A; B; C}(42)何ですか?

これはコンパイラエラーであると言えますが、実行時にintとの間で変換できるため、より複雑なコードでは機能しません。

Aまたはランタイムパニックのいずれかです。 いずれの場合も、ドメインが制限された統合型を追加します。 実行時のパニックの場合は、他のユーザーがラップアラウンドしたときにオーバーフロー時にパニックになる整数型を追加しています。 Aの場合、いくつかの式でuint%Nを追加しました。

もう1つのオプションは、A、B、またはCのいずれでもないようにすることですが、これがconst / iotaで今日使用されているものであるため、利益はありません。

int%Nが言語に反映されないと言うすべての理由は、一種のintである列挙型にも同様に当てはまるようです。 (それらのようなものが含まれていれば、私は決して怒っていませんが)。

誠実さを取り除くことは、その難問を取り除きます。 そのint-inessの一部を追加し直したい場合は、コード生成が必要ですが、それを行わないこともできます。これにより、導入するint-inessの量と種類を制御できます。 next "メソッド、循環nextメソッド、またはエッジから外れた場合にエラーを返すnextメソッド。 (また、 Monday*Sunday - Thursdayのようなものが合法になることはありません)。 余分な剛性により、より柔軟な建築材料になります。 識別された共用体は、int-y以外の多様体( pick { A, B, C struct{} }など)をうまくモデル化します。

このような情報を言語で持つことの主な利点は、

  1. 不正な値は不正です。
  2. 情報は反映するために利用可能であり、プログラムが仮定や注釈を付ける必要なしにそれに基づいて行動することを可能にします(現在、反映することはできません)。

このような情報を言語で持つことの主な利点は次のとおりです。違法な値は違法です。

誰もがこれをメリットと見なしているわけではないことを強調することが重要だと思います。 私は確かにそうしません。 多くの場合、値を消費するときに簡単になり、値を生成するときに難しくなります。 あなたがどちらを重くするかは、これまでのところ、個人的な好み次第です。 したがって、それが全体的な純利益であるかどうかの問題もそうです。

また、違法な値を禁止する意味もわかりません。 (上記の私の提案のように)自分で有効性をチェックする手段がすでにある場合、その制限はどのような利点をもたらしますか? 私にとって、それは物事を複雑にするだけです。 私のアプリケーションでは、ほとんどの場合の列挙型に無効/不明な値が含まれている可能性があり、アプリケーションに応じてそれを回避する必要がありました-完全に破棄するか、デフォルトにダウングレードするか、そのまま保存します。

無効な値を許可しない厳密な列挙型は、アプリが外界から隔離されており、無効な入力を受け取る方法がない非常に限られた場合に役立つ可能性があると思います。 あなただけが見て使用できる内部列挙型のように。

const with iotaはコンパイル時に安全ではなく、チェックは実行時に遅延し、安全なチェックは型レベルではありません。 ですから、iotaは文字通り列挙型を置き換えることはできないと思います。列挙型の方が強力だからです。

不正な値は不正です。
誰もがこれをメリットと見なしているわけではないことを強調することが重要だと思います。

私はこの論理を理解していません。 タイプは値のセットです。 値がその型にない変数に型を割り当てることはできません。 私は何かを誤解していますか?

PS:列挙型は合計型の特殊なケースであり、その問題はこれよりも優先されるべきであることに同意します。

言い換えると、より正確に言えば、列挙型を閉じることのメリットとして誰もがそれを認識しているわけではありません。

そのように厳格にしたい場合は、a)「違法な価値観は違法である」はトートロジーであり、b)したがって利益として数えることはできません。 constベースの列挙型では、あなたの解釈では、違法な値も違法です。 このタイプでは、さらに多くの値を使用できます。

列挙型がintであり、intが(型システムの観点から)正当である場合、唯一の利点は、型の名前付き値が反映されていることです。

これは基本的にはconst / iotaですが、fmtパッケージはリフレクションを使用して名前を取得できるため、stringerを実行する必要はありません。 (文字列をソースの名前とは異なるものにしたい場合は、引き続きstringerを実行する必要があります)。

@jimmyfrascheの文字列化は素晴らしいボーナスです。 上記の私の提案で読むことができるように、私にとっての主な機能は、指定された値が実行時に指定された列挙型の有効な値であるかどうかをチェックする機能です。

たとえば、このようなものを考えると

type Foo enum {
    Val1 = 1
    Val2 = 2
}

そして、のような反射方法

func IsValidEnum(v {}interface) bool

私たちはこのようなことをすることができます

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

実際の例として、C#の列挙型を見ることができます。これは、私の意見では、Javaが行ったことを盲目的に追跡するのではなく、この中間点を完全に捉えています。 C#で有効性を確認するには、 Enum.IsDefined静的メソッドを使用します。

@creckerそれとconst / iotaの唯一の違いは、
に保存されている情報は反映します。 それは全体としてはそれほど多くの利益ではありません
新しいタイプのタイプ。

少しクレイジーなアイデア:

同じパッケージで宣言されたすべての定数の名前と値を格納します
リフレクトが到達できる方法で定義されたタイプとして。 それはそのようになります
ただし、その狭いクラスのconstの使用法を特定するのは奇妙です。

上記の私の提案で読むことができるように、私にとっての主な機能

IMOこれは、この議論を引きずっている主なものの1つを示しています。「主な機能」のセットが何であるかが明確になっていないことです。 誰もがそれについて少し異なる考えを持っているようです。
個人的には、私はまだそのセットを発見するための経験レポートの形式が好きです。 リストには1つもあります(ただし、個人的には、「何がうまくいかなかったのか」のセクションでは、実際に何が起こったのかではなく、何がうまくいかなかったのかについてのみ言及しているという事実に注意します)。 おそらく、タイプチェックの欠如が停止/バグにつながる場所や、大規模なリファクタリングの失敗などを示すカップルを追加すると役立つでしょう。

@jimmyfrascheですが、これは多くのアプリケーションの大きな問題、つまり入力データの検証を解決します。 型システムの助けがなければ、手作業で行う必要があります。これは、数行のコードで実行できることではありません。 何らかの形式のタイプ支援検証を行うことで、それを解決できます。 その上に文字列化を追加すると、基になる型の値ではなく名前が適切にフォーマットされるため、ロギングが簡素化されます。

一方、列挙型を厳密にすると、考えられるユースケースが大幅に制限されます。 たとえば、プロトコルでそれらを簡単に使用することはできません。 無効な値を保持するには、列挙型を削除してプレーンな値型を使用する必要があります。必要に応じて、後で列挙型に変換することもできます。 場合によっては、無効な値を削除してエラーをスローする可能性があります。 その他の場合は、デフォルト値にダウングレードできます。 いずれにせよ、エラーを回避するのに役立つのではなく、型システムの制限と戦っています。

Java列挙型を回避するためにJava用のprotobufが生成する必要があるものを見てください。

検証に関しては@Merovius 、私はすでにそれを何度もカバーしたと思います。 他に何を追加できるかわかりません。検証を行わないと、入力を検証するために大量のコピーアンドペーストコードを作成する必要があります。 問題は明らかであり、提案されたソリューションがそれをどのように支援できるかについても同様です。 私は誰もが知っている大規模なアプリケーションには取り組んでいませんが、その検証コードのエラーは、何かが行われるのを見たいのと同じ列挙型の概念を持つ複数の言語で何度も私を噛みました。

一方、無効な値を許可しない列挙型を実装することを支持する議論は見当たりません(何かを見逃した場合はお詫びします)。 理論的には素晴らしくてすっきりしていますが、実際のアプリケーションで役立つとは思えません。

列挙型に必要な機能はそれほど多くありません。 文字列化、検証、無効な値に関する厳密/緩い、列挙-それは私が見ることができるものからほとんどそれです。 誰もが(もちろん私を含めて)この時点でそれらをシャッフルします。 厳格/緩いことは、それらの相反する性質のために、論争の主なポイントであるように思われます。 誰もがどちらかに同意するとは思いません。 おそらく解決策は、両方を何らかの方法で組み込んでプログラマーに選択させることかもしれませんが、現実の世界でどのように機能するかを確認するためにそれを備えた言語はありません。

@crecker上記のエクスポートデータに定数を保存するという私の提案
状況はあなたが求めているようなものを可能にするでしょう
新しい種類のタイプの導入なしで。

これが慣用的な方法であるかどうかはわかりません。また、私もこの言語にまったく慣れていませんが、以下は機能し、簡潔です。

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"
}

利点:

  • type Dayとは異なるものをfunc getTaskに渡すことはできません
    https://play.golang.org/p/4JZOIG5PbRX

  • 値を並べ替えてもコードは壊れません(代わりにiotaは注意が必要です)

  • コードジェネレーターは必要ありません

  • マップで使用できます
    https://play.golang.org/p/KlTNWrJpbDi

  • 繰り返すことができます
    https://play.golang.org/p/ld3TNtenEkD

短所:

  • コンパイラが文句を言わなくても、匿名の構造体をgetTaskに渡すことができます
    https://play.golang.org/p/NaV38og7e2h

  • 定数構造体はサポートされておらず、 varのみがサポートされています
    https://play.golang.org/p/X6KSpACA4N0

本当に列挙型が必要ですか?

誰かがこのようなことをするのを止めるにはどうすればよいですか?

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

そのような変数の消費者は、期待値を適切にチェックすることでそれをキャッチする場合としない場合があります(たとえば、土曜日または日曜日以外のものが平日であるなど、switchステートメントの仮定による不適切なフォールスルーがないと仮定します)が、そうではありません実行時まで。 この種の間違いは、実行時ではなく、コンパイル時にキャッチする方がよいと思います。

@bpkroth
独自のパッケージにDayがあり、選択したフィールドとメソッドのみを公開することにより、 package day $の外部にタイプDayの新しい値を作成できません。
また、この方法では、匿名の構造体をgetTaskに渡すことはできません。

./day/day.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)
    }
}

列挙型、三項演算子、未使用の変数を使用したコンパイル、合計型、ジェネリック、デフォルトパラメータなど、最もシンプルで便利な機能を追加しないことを主張する他の言語は、これまでずっと見たことがありません。

Golangは、開発者がいかに愚かであるかを確認するための社会実験ですか?

@ gh67uyyghj誰かがあなたのコメントをトピック外としてマークしました! そして、誰かが私の返事に同じことをするだろうと思います。 しかし、あなたの質問に対する答えはイエスだと思います。 GoLangで機能がないということは、機能があることを意味するので、GoLangにないものは、実際にはGoLangにある機能であり、他のプログラミング言語にはない機能です。

@ L-orisこれは、型を使用して列挙型を実装するための非常に興味深い方法です。 しかし、それは厄介な感じであり、列挙型キーワード(必然的に言語をもう少し複雑にする)があると、次のことが簡単になります。

  • 書きます
  • 読んだ
  • についての理由

あなたの例(今日はうまくいくので素晴らしいです)では、列挙型(何らかの形で)を持つことは次の必要性を意味します:

  • 構造体タイプを作成する
  • メソッドを作成する
  • 変数を作成します(ライブラリのユーザーはこれらの値を変更できませんが、定数でさえありません)

これは、読み取り、書き込み、および推論(列挙型として使用する必要があることを認識してください)に時間がかかります(ただし、それほど長くはなりません)。

したがって、構文の提案は、言語の単純さと付加価値の点で正しいと思います。

ありがとう@andradei
はい、それは回避策ですが、言語の目的はそれを小さくシンプルに保つことだと思います
クラスが恋しいと主張することもできますが、それではJavaに移りましょう:)

私はむしろGo2の提案、より良いエラー処理に焦点を当てたいと思います。 これらの列挙型よりもはるかに多くの価値を私に提供します

ポイントに戻る:

  • それはそれほど定型的ではありません。 最悪の場合、いくつかのジェネレーターを持つことができます(しかし、それは本当にそれだけのコードですか?)
  • 新しいキーワードを追加することで、どの程度の「シンプルさ」を達成できますか。また、特定の一連の動作全体がおそらく実現するでしょうか。
  • メソッドを少し工夫することで、それらの列挙型に興味深い機能を追加することもできます
  • 読みやすさのために、それはそれに慣れることについてです。 その上にコメントを追加するか、変数のプレフィックスを付けます
package day

// Day Enum
type Day struct {
    value string
}

@ L-orisなるほど。 Go2の提案にも興奮しています。 ジェネリックスは列挙型よりも言語の複雑さを増すと私は主張します。 しかし、あなたのポイントに固執するために:

  • それは確かにそれほど多くの定型文ではありません
  • 列挙型の概念がどれほどよく知られているかを確認する必要があります。ほとんどの人はそれが何であるかを知っていると思います(しかし、それを証明することはできません)。 言語の複雑さは、その利益を支払うのに適した「代償」になります。
  • 確かに、列挙型がないということは、たとえば、generetad protobufコードをチェックするときや、列挙型を模倣するデータベースモデルを作成しようとするときにのみ発生する問題です。
  • それもまた真実です。

私はこの提案について多くのことを考えてきましたが、単純さが生産性に大きな価値をもたらし、明らかに変更が必要でない限り、なぜそれを維持することに傾倒するのかがわかります。 列挙型も言語を大幅に変更する可能性があるため、もはやGoではなく、その長所/短所を評価するには長い時間がかかるようです。 ですから、少なくとも今のところ、コードがまだ読みやすいあなたのような単純な解決策が良い解決策であると私は考えてきました。

みんな、本当に将来のためにこの機能が欲しいです!。 ポインタと_nowadays_で「列挙型」を定義する方法はうまくいきません。 例: https ://play.golang.org/p/A7rjgAMjfCx

列挙型の私の提案は次のとおりです。 これを新しいタイプと見なす必要があります。 たとえば、任意の構造で次の実装を持つ列挙型を使用したいと思います。

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

この構造をどのようにマーシャリングするか、どのように機能するか、どのように文字列に変更するかなどは理解できます。
そしてもちろん、「次へ」機能をオーバーライドできれば素晴らしいと思います。

そのためには、Goは最初に深い不変の構造体をサポートする必要があります。 不変の型がなければ、列挙型でこれを実行して同じことを行うことができると想像できます。

type Status enum {
  Started
  Stopped
}

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

もっとシンプルに見えるべきだと思います

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

Go2の提案

論理的に列挙型は型インターフェースを提供することになっています。
以前、列挙型は分離されているはずだと述べました。
これは、特定の名前空間に関連付けられた明示的な名前の定数です。

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)
  }
}

列挙型は型拡張、「定数コンテナ」です。

タイプ愛好家のために

タイプとして見たい人のための構文の選択肢

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

しかし、これらの明示的なトップレベルの宣言を回避することもできます

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

検証の例は同じままです。

しかし場合に備えて

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
}

Status1.Started == Status2.Startedはどうですか?
マーシャリングについて?

ポジションを変えたら?

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

不変型については@Goodwineに同意します。

マーシャリングは興味深い質問です。
これはすべて、基礎となる価値をどのように扱うかによって異なります。 したがって、実際の値を使用する場合、 Status1.StartedはStatus2.Startedに等しくなります。
シンボリック解釈を使用する場合、それらは異なる値と見なされます。

何かを挿入すると、値が変更されます( iotaの場合とまったく同じ方法です)。
この開発者を回避するには、宣言とともに値を指定する必要があります。

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

これは明らかなことです。
このような問題を回避したい場合は、列挙値の字句解釈に基づいて予測可能なコンパイラ出力を提供する必要があります。 カスタム型キャストが定義されていない限り、ハッシュテーブルを作成するか、シンボリック名(文字列)に固執するという最も簡単な方法を想定しています。

Rustが列挙型を実装する方法が好きです。

タイプが指定されていないデフォルト

enum IpAddr {
    V4,
    V6,
}

カスタムタイプ

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

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

複雑なタイプ

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

確かに、整数型として格納されているC#のような単純な列挙型を持っていても素晴らしいでしょう。

上記はenumを超えており、これらは_discriminated unions_であり、特に_pattern match_を使用すると、より強力になります。これは、 switchのマイナーな拡張である可能性があります。

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"
}

前述のように危険である可能性があるため、列挙型のコンパイル時チェックは必要ありません

何度か必要だったのは、特定のタイプのすべての定数を反復処理することでした。

  • 検証用(これのみを受け入れるか、不明なオプションを単に無視することが確実な場合)

    • または可能な定数のリスト(ドロップダウンを考えてください)。

iotaを使用して検証を行い、リストの最後を指定することができます。 ただし、コード内以外の目的でiotaを使用すると、間違った行に定数を挿入すると問題が発生するため、かなり危険です(プログラミングのどこに配置するかを知っておく必要がありますが、そのようなバグは他のものよりも見つけるのが非常に難しい)。 さらに、定数が数値の場合に実際に何を表すかについての説明はありません。 それは次のポイントにつながります:

良い追加機能は、文字列化された名前を指定することです。

誰かがこのようなことをするのを止めるにはどうすればよいですか?

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

そのような変数の消費者は、期待値を適切にチェックすることでそれをキャッチする場合としない場合があります(たとえば、土曜日または日曜日以外のものが平日であるなど、switchステートメントの仮定による不適切なフォールスルーがないと仮定します)が、そうではありません実行時まで。 この種の間違いは、実行時ではなく、コンパイル時にキャッチする方がよいと思います。

@ L-orisでは、これはどうですか?

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
}

私たちが望んでいるのは、[return "nothing to do"]によるランタイムサイレンスと奇妙なバグではなく、コンパイル時/コーディング時のエラーレポートです!
理解する?

  1. enumは確かに新しいタイプであり、これはtype State stringが行うことであり、新しいキーワードを導入する慣用的な必要はありません。 Goは、ソースコードのスペースを節約することではなく、読みやすさ、目的の明確さです。

  2. 型の安全性の欠如、実際の文字列/ intの新しいstringまたはintベースの型を混乱させることが重要なハードルです。 すべての列挙型句はconstとして宣言されます。これにより、コンパイラがチェックできる既知の値のセットが作成されます。

  3. Stringerインターフェースは、あらゆるタイプを人間が読めるテキストとして表現するためのイディオムです。 カスタマイズしない場合、 type ContextKey string列挙型はこれが文字列値であり、 iota生成された列挙型の場合、JavaScriptのXHR ReadyStateコード(0-未送信、4-完了)と同様に整数です。

    むしろ、問題はカスタムfunc (k ContextKey) String() string実装の誤りにあります。これは通常、すべての既知の列挙型句定数を含まなければならないスイッチを使用して行われます。

  4. Swiftのような言語には、_徹底的なスイッチ_の概念があります。 これは、一連のconstに対する型チェックと、そのチェックを呼び出す慣用的な方法の構築の両方に適したアプローチです。 String()関数は、一般的に必要なものであり、実装に最適なケースです。

提案

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
}

PS Swift列挙型の関連する値は、私のお気に入りのギミックの1つです。 囲碁には彼らのための場所はありません。 列挙型データの横に値を設定する場合は、2つをラップする強い型のstructを使用します。

数か月前、列挙型が適切に処理されていることを確認するリンターの概念実証を作成しました。 https://github.com/loov/enumcheck

現在、コメントを使用して物事を列挙としてマークしています。

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")
    }
}

すべての暗黙的な変換を処理する方法を理解するのに行き詰まりましたが、基本的なケースでは適切に機能します。

現在、まだ作業中であるため、状況が変わる可能性があることに注意してください。 たとえば、コメントの代わりに、タイプに注釈を付けるためにスタブパッケージを使用することもできますが、現時点ではコメントで十分です。

Go1での列挙型の現在の実装は、私が知っているどの言語でも、最も奇妙で最も明白でない列挙型の実装です。 Cでさえそれらをよりうまく実装します。 iotaのことはハックのように見えます。 そして、とにかくイオタは一体何を意味するのでしょうか? そのキーワードをどのように記憶するのですか? 囲碁は習得しやすいはずです。 しかし、それはただ気まぐれです。

@pofl :
Go列挙型はかなり扱いにくいことに同意しますが、 iotaは実際には単なる通常の英語の単語です。

iota
_名詞_

  1. 非常に少量。 ジョット; 聖霊降臨祭。
  2. ギリシャ文字の9番目の文字(I、ι)。
  3. この文字で表される母音。

おそらく、彼らは言語での使用という観点から定義1を目指していたのでしょう。

ここでの古いコメントへの応答としての補足:
Goでも、区別された共用体が欲しいのですが、実際の列挙型とは別にする必要があると思います。 ジェネリックスが現在進んでいる方法では、実際には、インターフェイスの型リストを介して、識別された共用体に非常によく似たものを取得する可能性があります。 #41716を参照してください。

Goでのiotaの使用は、APLでの使用に大まかに基づいています。 引用https://en.wikipedia.org/wiki/Iota :

一部のプログラミング言語(A +、APL、C ++ [6]、Go [7]など)では、iota(小文字の記号⍳または識別子iotaとして)を使用して、連続する整数の配列を表現および生成します。 たとえば、APLでは⍳4は1 2 34になります。

このページは役に立ちましたか?
0 / 5 - 0 評価