Який ідіоматичний спосіб представлення переліків у Go?


522

Я намагаюся представити спрощену хромосому, яка складається з N підстав, кожна з яких може бути лише однією з {A, C, T, G}.

Я хотів би формалізувати обмеження перерахунком, але мені цікаво, який найбільш ідіоматичний спосіб емуляції перерахунку є в Go.


4
У стандартному пакеті вони представлені як константи. Дивіться golang.org/pkg/os/#pkg-constants
Denys Séguret



7
@icza Це питання було задано 3 роки до цього. Це не може бути дублікатом цього, якщо припустити, що стрілка часу працює.
карбокація

Відповіді:


658

Цитування з мовних специфікацій: Iota

У межах постійної декларації попередньо визначений ідентифікатор iota являє собою послідовні нетипізовані цілі константи. Він скидається до 0 кожного разу, коли зарезервоване слово const з'являється у джерелі та з кроком після кожного ConstSpec. Він може бути використаний для побудови набору споріднених констант:

const (  // iota is reset to 0
        c0 = iota  // c0 == 0
        c1 = iota  // c1 == 1
        c2 = iota  // c2 == 2
)

const (
        a = 1 << iota  // a == 1 (iota has been reset)
        b = 1 << iota  // b == 2
        c = 1 << iota  // c == 4
)

const (
        u         = iota * 42  // u == 0     (untyped integer constant)
        v float64 = iota * 42  // v == 42.0  (float64 constant)
        w         = iota * 42  // w == 84    (untyped integer constant)
)

const x = iota  // x == 0 (iota has been reset)
const y = iota  // y == 0 (iota has been reset)

У ExpressionList значення кожної йоти є однаковим, оскільки воно збільшується лише після кожного ConstSpec:

const (
        bit0, mask0 = 1 << iota, 1<<iota - 1  // bit0 == 1, mask0 == 0
        bit1, mask1                           // bit1 == 2, mask1 == 1
        _, _                                  // skips iota == 2
        bit3, mask3                           // bit3 == 8, mask3 == 7
)

Цей останній приклад використовує неявне повторення останнього не порожнього списку виразів.


Тож ваш код може бути таким

const (
        A = iota
        C
        T
        G
)

або

type Base int

const (
        A Base = iota
        C
        T
        G
)

якщо ви хочете, щоб бази були окремим типом від int.


16
чудові приклади (я не згадував точну поведінку йоти - коли вона збільшується - від специфікації). Особисто мені подобається надати тип перерахунку, щоб його можна було перевірити, коли він використовувався як аргумент, поле тощо.
1313

16
Дуже цікаво @jnml. Але я дуже розчарований, що статична перевірка типу здається розпущеною, наприклад, ніщо не заважає мені використовувати Базу № 42, яка ніколи не існувала: play.golang.org/p/oH7eiXBxhR
Deleplace

4
Go не має поняття числових типів піддіапазону, як, наприклад, Pascal's, тому Ord(Base)не обмежується, 0..3але має ті самі обмеження, що і його базовий числовий тип. Це вибір мовного дизайну, компроміс між безпекою та продуктивністю. Розгляньте перевірку обмеженого часу "безпечного" виконання кожного разу, коли торкаєтесь Baseвведеного значення. Або як слід визначити поведінку Baseзначення «переповнення» для арифметики і для ++і --? І т. Д.
zzzz

7
Щоб доповнити jnml, навіть семантично, ніщо в мові не говорить про те, що consts, визначений як Base, представляє весь діапазон дійсних Base, це просто говорить про те, що ці конкретні consts мають тип Base. Більше констант можна визначити і в іншому місці як Base, і це навіть не є взаємовиключним (наприклад, const Z Base = 0 можна було б визначити і було б дійсним).
mna

10
Ви можете використовувати iota + 1не починати з 0.
Марсал Хуан

87

Посилаючись на відповідь jnml, ви можете запобігти появі нових примірників типу Base, взагалі не експортуючи тип Base (тобто запишіть його в малі регістри). Якщо потрібно, ви можете зробити інтерфейс для експорту, який має метод, який повертає базовий тип. Цей інтерфейс можна використовувати у функціях ззовні, які мають справу з Bases, тобто

package a

type base int

const (
    A base = iota
    C
    T
    G
)


type Baser interface {
    Base() base
}

// every base must fulfill the Baser interface
func(b base) Base() base {
    return b
}


func(b base) OtherMethod()  {
}

package main

import "a"

// func from the outside that handles a.base via a.Baser
// since a.base is not exported, only exported bases that are created within package a may be used, like a.A, a.C, a.T. and a.G
func HandleBasers(b a.Baser) {
    base := b.Base()
    base.OtherMethod()
}


// func from the outside that returns a.A or a.C, depending of condition
func AorC(condition bool) a.Baser {
    if condition {
       return a.A
    }
    return a.C
}

Всередині основного пакету a.Baserефективно, як перерахунок зараз. Лише всередині пакета ви можете визначати нові екземпляри.


10
Ваш метод здається ідеальним для тих випадків, коли baseвикористовується лише як приймач методу. Якщо у вашому aпакеті було викрито функцію, яка приймає параметр типу base, це стане небезпечним. Дійсно, користувач міг би просто назвати його з буквальним значенням 42, яке функція сприйняла б так, baseоскільки може бути передана в Int. Щоб запобігти цьому, зробити : . Проблема: ви більше не можете оголошувати бази константами, лише змінні модуля. Але 42 ніколи не буде передано до цього типу. basestructtype base struct{value:int}base
Нірієль

6
@metakeule Я намагаюся зрозуміти ваш приклад, але ваш вибір змінних імен зробив це надзвичайно важко.
anon58192932

1
Це один із моїх помилок у прикладах. FGS, я розумію, що це заманливо, але не називайте змінну такою ж, як тип!
Грем Ніколлс

26

Ви можете зробити так:

type MessageType int32

const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)

За допомогою цього компілятора коду слід перевірити тип enum


5
Константи, як правило, записуються у звичайній верблюді, а не у всіх великих літерах. Початкова велика літера означає, що експортується змінна, яка може бути або не бути такою, яку ви хочете.
425nesp

1
Я помітив, що у вихідному коді Go є суміш, де інколи постійні літери мають великі літери, а іноді вони верблюди. У вас є посилання на специфікацію?
Джеремі Гейлор

@JeremyGailor Я думаю, що 425nesp просто відзначає, що звичайна перевага розробникам використовувати їх як неекспортовані константи, тому використовують верблюд. Якщо розробник визначить, що його слід експортувати, сміливо використовуйте всі великі регістри чи великі регістри, оскільки немає встановлених переваг. Див. Рекомендації з перегляду кодексу Голанг та Ефективний розділ про константи
waynethec

Є перевага. Подібно до змінних, функцій, типів та інших, імена константи повинні бути змішанимиCaps або MixedCaps, а не ALLCAPS. Джерело: коментарі до перегляду коду Go .
Родольфо Карвальо,

23

Це правда, що наведені вище приклади використання constта iotaє найбільш ідіоматичними способами представлення примітивних перерахунків у Go. Але що робити, якщо ви шукаєте спосіб створити більш повнофункціональний перелік, схожий на тип, який ви бачите на іншій мові, як Java або Python?

Дуже простий спосіб створити об’єкт, який починає виглядати і відчувати себе струнним перерахунком в Python, це:

package main

import (
    "fmt"
)

var Colors = newColorRegistry()

func newColorRegistry() *colorRegistry {
    return &colorRegistry{
        Red:   "red",
        Green: "green",
        Blue:  "blue",
    }
}

type colorRegistry struct {
    Red   string
    Green string
    Blue  string
}

func main() {
    fmt.Println(Colors.Red)
}

Припустимо, ви також хотіли деякі корисні методи, як-от Colors.List()і Colors.Parse("red"). А ваші кольори були складнішими і потребували структури. Тоді ви можете зробити щось подібне:

package main

import (
    "errors"
    "fmt"
)

var Colors = newColorRegistry()

type Color struct {
    StringRepresentation string
    Hex                  string
}

func (c *Color) String() string {
    return c.StringRepresentation
}

func newColorRegistry() *colorRegistry {

    red := &Color{"red", "F00"}
    green := &Color{"green", "0F0"}
    blue := &Color{"blue", "00F"}

    return &colorRegistry{
        Red:    red,
        Green:  green,
        Blue:   blue,
        colors: []*Color{red, green, blue},
    }
}

type colorRegistry struct {
    Red   *Color
    Green *Color
    Blue  *Color

    colors []*Color
}

func (c *colorRegistry) List() []*Color {
    return c.colors
}

func (c *colorRegistry) Parse(s string) (*Color, error) {
    for _, color := range c.List() {
        if color.String() == s {
            return color, nil
        }
    }
    return nil, errors.New("couldn't find it")
}

func main() {
    fmt.Printf("%s\n", Colors.List())
}

У той момент впевнений, що він працює, але вам може не сподобатися, як вам потрібно повторювати кольори. Якщо в цей момент ви хочете усунути це, ви можете використати теги на вашій структурі і зробити деякі фантазії, що відображають її настройку, але, сподіваємось, цього достатньо, щоб охопити більшість людей.


19

Станом на Go 1.4, go generateінструмент був представлений разом із stringerкомандою, яка дозволяє вашій перерахунку легко налагоджувати та друкувати.


Ви знаєте, це опозиційне рішення. Я маю на увазі рядок -> MyType. Оскільки один спосіб рішення далеко не ідеальний. Ось sb gist, який робить те, що я хочу - але писати вручну легко помилитися.
SR

11

Я впевнений, що у нас тут багато хороших відповідей. Але я просто думав додати те, як я використав перелічені типи

package main

import "fmt"

type Enum interface {
    name() string
    ordinal() int
    values() *[]string
}

type GenderType uint

const (
    MALE = iota
    FEMALE
)

var genderTypeStrings = []string{
    "MALE",
    "FEMALE",
}

func (gt GenderType) name() string {
    return genderTypeStrings[gt]
}

func (gt GenderType) ordinal() int {
    return int(gt)
}

func (gt GenderType) values() *[]string {
    return &genderTypeStrings
}

func main() {
    var ds GenderType = MALE
    fmt.Printf("The Gender is %s\n", ds.name())
}

Це, безумовно, один із ідіоматичних способів, яким ми могли б створити перелічені типи та використовувати в Go.

Редагувати:

Додавання іншого способу використання констант для перерахунку

package main

import (
    "fmt"
)

const (
    // UNSPECIFIED logs nothing
    UNSPECIFIED Level = iota // 0 :
    // TRACE logs everything
    TRACE // 1
    // INFO logs Info, Warnings and Errors
    INFO // 2
    // WARNING logs Warning and Errors
    WARNING // 3
    // ERROR just logs Errors
    ERROR // 4
)

// Level holds the log level.
type Level int

func SetLogLevel(level Level) {
    switch level {
    case TRACE:
        fmt.Println("trace")
        return

    case INFO:
        fmt.Println("info")
        return

    case WARNING:
        fmt.Println("warning")
        return
    case ERROR:
        fmt.Println("error")
        return

    default:
        fmt.Println("default")
        return

    }
}

func main() {

    SetLogLevel(INFO)

}

2
Ви можете оголосити константи зі строковими значеннями. ІМО простіше зробити це, якщо ви маєте намір відобразити їх і насправді не потрібно числового значення.
cbednarski

4

Ось приклад, який виявиться корисним, коли перелічень багато. Він використовує структури в Голангу та спирається на об'єктно-орієнтовані принципи, щоб зв'язати їх усі разом в маленький акуратний пучок. Жоден базовий код не зміниться, коли додане чи видалено нове перерахування. Процес:

  • Визначте структуру перерахування для enumeration items: EnumItem . Він має цілий і рядковий тип.
  • Визначте enumerationяк список enumeration items: Enum
  • Побудуйте методи перерахування. До них було включено декілька:
    • enum.Name(index int): повертає ім'я для даного індексу.
    • enum.Index(name string): повертає ім'я для даного індексу.
    • enum.Last(): повертає індекс та назву останнього перерахунку
  • Додайте свої визначення перерахунку.

Ось код:

type EnumItem struct {
    index int
    name  string
}

type Enum struct {
    items []EnumItem
}

func (enum Enum) Name(findIndex int) string {
    for _, item := range enum.items {
        if item.index == findIndex {
            return item.name
        }
    }
    return "ID not found"
}

func (enum Enum) Index(findName string) int {
    for idx, item := range enum.items {
        if findName == item.name {
            return idx
        }
    }
    return -1
}

func (enum Enum) Last() (int, string) {
    n := len(enum.items)
    return n - 1, enum.items[n-1].name
}

var AgentTypes = Enum{[]EnumItem{{0, "StaffMember"}, {1, "Organization"}, {1, "Automated"}}}
var AccountTypes = Enum{[]EnumItem{{0, "Basic"}, {1, "Advanced"}}}
var FlagTypes = Enum{[]EnumItem{{0, "Custom"}, {1, "System"}}}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.