6

Какой идиоматичный эквивалент тернарного оператора C в Go?

15

В языках C/C++ (и во многих других языках, принадлежащих к этой семье) распространенным приемом при объявлении и инициализации переменной в зависимости от условия является использование тернарного оператора:

int index = val > 0 ? val : -val

В языке Go нет тернарного оператора. Как наиболее идиоматично реализовать тот же кусок кода, что и выше? Я пришел к следующему решению, но оно кажется слишком многословным:

var index int

if val > 0 {
    index = val
} else {
    index = -val
}

Есть ли что-то лучше?

5 ответ(ов)

0

В языке C тернарный оператор позволяет компактно записывать условные выражения. Например, следующий код:

int a = test ? 1 : 2;

в Go не имеет аналогичного тернарного оператора, поэтому вместо него часто используют if блок:

var a int

if test {
  a = 1
} else {
  a = 2
}

Однако в некоторых случаях такая запись может оказаться неудобной, например, если необходимо использовать инлайн-выражение в шаблоне генерации кода. В этом случае я нашел решение, использовав немедленно вызываемую анонимную функцию:

a := func() int { if test { return 1 } return 2 }()

Такой способ гарантирует, что ни одна из веток не будет выполнена, что может быть важно для оптимизации или специфики вашего кода.

0

В вашем примере кода используется карта, и с помощью тернарного оператора вы выбираете значение в зависимости от условия.

Код выглядит следующим образом:

c := map[bool]int{true: 1, false: 0}[5 > 4]

Здесь 5 > 4 возвращает true, поэтому в переменную c будет записано значение 1. Такой подход действительно удобен для чтения, так как позволяет избежать использования дополнительных скобок.

Вместо стандартной конструкции if, этот метод делает ваш код более лаконичным и выразительным. Если у вас есть дополнительные вопросы по этому коду или его применению, не стесняйтесь спрашивать!

0

Ваш код демонстрирует использование тернарной функции для вычисления абсолютного значения числа. Хотя это решение действительно работает, оно имеет несколько недостатков.

Во-первых, тернарная функция Ternary возвращает интерфейс, что требует явного приведения типа (.(int)) в функции Abs. Это добавляет накладные расходы времени выполнения и усложняет код.

Во-вторых, как показали бенчмарки, использование конструкции if/else значительно более производительно. В вашем примере, производительность Abs с использованием тернарной функции составляет 18.8 нс на операцию, тогда как обычное условие if/else достигает 0.27 нс на операцию. Это означает, что вариант с if/else выполнится примерно в 70 раз быстрее.

Если требуется максимальная производительность, рекомендуется использовать стандартную конструкцию if/else:

func Abs(n int) int {
    if n >= 0 {
        return n
    }
    return -n
}

Таким образом, можно улучшить как читаемость, так и производительность кода.

0

Как было отмечено другими участниками, язык Go не имеет тернарного оператора или его эквивалента. Это осознанное решение, направленное на улучшение читаемости кода.

На днях я столкнулся с ситуацией, когда создание битовой маски стало сложным для восприятия при написании в идиоматичном стиле, а также очень неэффективным при инкапсуляции в функции - или тем и другим, так как код генерировал ветвления:

package lib

func maskIfTrue(mask uint64, predicate bool) uint64 {
  if predicate {
    return mask
  }
  return 0
}

При компиляции это приводит к:

        text    "".maskIfTrue(SB), NOSPLIT|ABIInternal, $0-24
        funcdata        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        funcdata        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        movblzx "".predicate+16(SP), AX
        testb   AL, AL
        jeq     maskIfTrue_pc20
        movq    "".mask+8(SP), AX
        movq    AX, "".~r2+24(SP)
        ret
maskIfTrue_pc20:
        movq    $0, "".~r2+24(SP)
        ret

Из этого я извлек для себя урок использовать немного больше возможностей Go. Использование именованного результата в функции (result int) позволяет сэкономить строку на его объявление внутри функции (то же самое можно сделать с захватами), но компилятор также распознает этот идиом (использовать присвоение только при условии IF) и, если возможно, заменяет его на условную инструкцию.

func zeroOrOne(predicate bool) (result int) {
  if predicate {
    result = 1
  }
  return
}

Код без ветвлений выглядит так:

    movblzx "".predicate+8(SP), AX
    movq    AX, "".result+16(SP)
    ret

Что в свою очередь позволяет Go свободно инлайнить эту функцию.

package lib

func zeroOrOne(predicate bool) (result int) {
  if predicate {
    result = 1
  }
  return
}

type Vendor1 struct {
    Property1 int
    Property2 float32
    Property3 bool
}

// Позиции битов Vendor2.
const (
    Property1Bit = 2
    Property2Bit = 3
    Property3Bit = 5
)

func Convert1To2(v1 Vendor1) (result int) {
    result |= zeroOrOne(v1.Property1 == 1) << Property1Bit
    result |= zeroOrOne(v1.Property2 < 0.0) << Property2Bit
    result |= zeroOrOne(v1.Property3) << Property3Bit
    return
}

Код генерирует https://go.godbolt.org/z/eKbK17:

    movq    "".v1+8(SP), AX
    cmpq    AX, $1
    seteq   AL
    xorps   X0, X0
    movss   "".v1+16(SP), X1
    ucomiss X1, X0
    sethi   CL
    movblzx AL, AX
    shlq    $2, AX
    movblzx CL, CX
    shlq    $3, CX
    orq     CX, AX
    movblzx "".v1+20(SP), CX
    shlq    $5, CX
    orq     AX, CX
    movq    CX, "".result+24(SP)
    ret

Таким образом, использование именованных результатов помогает делать код более чистым и при этом позволяет компилятору оптимизировать выходные данные.

0

Ответ eold интересен и креативен, возможно, даже умный.

Тем не менее, рекомендуется сделать следующее:

var index int
if val > 0 {
    index = printPositiveAndReturn(val)
} else {
    index = slowlyReturn(-val)  // или slowlyNegate(val)
}

Да, оба варианта в конечном счете компилируются в примерно одинаковый ассемблерный код, однако этот код гораздо более понятен, чем вызов анонимной функции просто для возвращения значения, которое могло бы быть записано в переменную изначально.

В общем, простой и ясный код лучше, чем креативный.

Кроме того, использование литералов для карт (maps) не является хорошей идеей, так как карты в Go совсем не легковесны. С версии Go 1.3 гарантируется случайный порядок итерации для небольших карт, и для обеспечения этого их память стала менее эффективной для маленьких карт.

В результате создание и удаление множества небольших карт занимает как место, так и время. У меня был фрагмент кода, который использовал небольшую карту (две или три ключа, вероятно, но общий случай - это всего одна запись). Однако код работал очень медленно. Мы говорим как минимум о трех порядках величины медленнее, чем тот же код, переписанный с использованием двустороннего среза key[index] => data[index]. И, вероятно, даже больше. Некоторые операции, которые раньше занимали несколько минут, теперь выполнялись за миллисекунды.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь