golang возвращает проблему с несколькими значениями

Мне было интересно, почему это действительный код перехода:

func FindUserInfo(id string) (Info, bool) {
    it, present := all[id]
    return it, present
}

но это не

func FindUserInfo(id string) (Info, bool) {
    return all[id]
}

есть ли способ избежать временных переменных?


person Pablo Fernandez    schedule 08.05.2015    source источник
comment
Я не эксперт в том, как Go работает в бэкенде, но шаблон запятой ok применяется к встроенным операциям (не к реальным функциям). Поскольку это необязательно, может показаться, что компилятор проверяет многозначное назначение. Может быть, кто-то более знаком с компилятором можно уточнить   -  person Lander    schedule 08.05.2015
comment
не могли бы вы уточнить шаблон запятой? ответ будет оценен (и одобрен), поскольку я не только пытаюсь сохранить нажатия клавиш, но и понимаю, почему это происходит.   -  person Pablo Fernandez    schedule 09.05.2015
comment
У меня есть сильное подозрение, почему это не разрешено, но мне нужно сослаться на спецификацию. Я думаю, что для этого есть веская логическая причина. Почти уверен, что если бы вы разрешили это, я мог бы придумать некоторые крайние случаи, которые сломают компилятор или будут иметь неопределенное/несогласованное поведение.   -  person evanmcdonnal    schedule 09.05.2015
comment
@PabloFernandez Надеюсь, мой ответ сможет ответить на вопрос, почему!   -  person Lander    schedule 09.05.2015


Ответы (5)


Чтобы уточнить мой комментарий, Effective Go упоминает, что присваивание нескольких значений при доступе к ключу карты называется шаблоном "запятая ок".

Иногда вам нужно отличить отсутствующую запись от нулевого значения. Есть ли запись для «UTC» или это пустая строка, потому что ее вообще нет на карте? Вы можете различать форму множественного присвоения.

var seconds int
var ok bool
seconds, ok = timeZone[tz]

По понятным причинам это называется идиомой «запятая ок». В этом примере, если присутствует tz, секунды будут установлены соответствующим образом, а ok будет истинным; если нет, секунды будут установлены на ноль, а ok будет ложным.

Игровая площадка, демонстрирующая это

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

package main

import "fmt"

func multiValueReturn() (int, int) {
    return 0, 0
}

func main() {
    fmt.Println(multiValueReturn)

    asgn1, _ := multiValueReturn()

    asgn2 := multiValueReturn()
}

На игровой площадке это выведет

# command-line-arguments
/tmp/sandbox592492597/main.go:14: multiple-value multiValueReturn() in single-value context

Это дает нам подсказку, что это может быть что-то, что делает компилятор. Поиск "commaOk" в исходном коде дает нам несколько мест для поиска, в том числе types.unpack< /а>

На момент написания этой статьи в godoc этого метода читается:

// unpack takes a getter get and a number of operands n. If n == 1, unpack
// calls the incoming getter for the first operand. If that operand is
// invalid, unpack returns (nil, 0, false). Otherwise, if that operand is a
// function call, or a comma-ok expression and allowCommaOk is set, the result
// is a new getter and operand count providing access to the function results,
// or comma-ok values, respectively. The third result value reports if it
// is indeed the comma-ok case. In all other cases, the incoming getter and
// operand count are returned unchanged, and the third result value is false.
//
// In other words, if there's exactly one operand that - after type-checking
// by calling get - stands for multiple operands, the resulting getter provides
// access to those operands instead.
//
// If the returned getter is called at most once for a given operand index i
// (including i == 0), that operand is guaranteed to cause only one call of
// the incoming getter with that i.
//

Ключевым моментом этого является то, что этот метод, по-видимому, определяет, является ли что-то фактически случаем "запятая ок".

Изучение этого метода говорит нам, что он проверит, является ли режим операндов индексированием карты или установлен ли режим commaok (где это определен дает нам много подсказок о том, когда он используется, но поиск источника для присваивания commaok мы видим, что он используется, когда получает значение из канала и утверждения типа ). Запомните жирный бит на потом!

if x0.mode == mapindex || x0.mode == commaok {
    // comma-ok value
    if allowCommaOk {
        a := [2]Type{x0.typ, Typ[UntypedBool]}
        return func(x *operand, i int) {
            x.mode = value
            x.expr = x0.expr
            x.typ = a[i]
        }, 2, true
    }
    x0.mode = value
}

allowCommaOk — это параметр функции. Проверяя, где в этом файле вызывается unpack, мы видим, что все вызывающие программы передают false в качестве аргумента. Поиск в остальной части репозитория приводит нас к assignments.go в Checker.initVars()метод.

l := len(lhs)
get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2 && !returnPos.IsValid())

Поскольку кажется, что мы можем использовать только шаблон «запятая ok», чтобы получить два возвращаемых значения при выполнении присваивания нескольких значений, это кажется правильным местом для поиска! В приведенном выше коде проверяется длина левой стороны, и когда вызывается unpack, параметр allowCommaOk является результатом l == 2 && !returnPos.IsValid(). !returnPos.IsValid() здесь несколько сбивает с толку, так как это означает, что позиция не имеет связанной информации о файле или строке, но мы просто проигнорируем это.

Далее в этом методе у нас есть:

var x operand
if commaOk {
    var a [2]Type
    for i := range a {
        get(&x, i)
        a[i] = check.initVar(lhs[i], &x, returnPos.IsValid())
    }
    check.recordCommaOkTypes(rhs[0], a)
    return
}

Итак, что все это нам говорит?

  • Поскольку метод unpack принимает параметр allowCommaOk, для которого жестко задано значение false везде, кроме метода Checker.initVars() assignment.go, мы, вероятно, можем предположить, что вы когда-либо получите только два значения при выполнении присваивания и две переменные слева. сторона руки.
  • Метод unpack определяет, действительно ли вы действительно получаете взамен значение ok, проверяя, индексируете ли вы срез, извлекаете ли значение из канала или выполняете утверждение типа.
  • Поскольку вы можете получить значение ok только при выполнении задания, похоже, что в вашем конкретном случае вам всегда нужно будет использовать переменные.
person Lander    schedule 09.05.2015

Вы можете сэкономить пару нажатий клавиш, используя именованные возвраты:

func FindUserInfo(id string) (i Info, ok bool) {
    i, ok = all[id]
    return
}

Но помимо этого, я не думаю, что то, что вы хотите, возможно.

person Ainar-G    schedule 08.05.2015

Проще говоря: причина, по которой ваш второй пример не является допустимым кодом Go, заключается в том, что так сказано в спецификации языка. ;)

Индексирование карты дает только вторичное значение при присвоении значения двум переменным. Оператор return не является присваиванием.

Индексное выражение на карте a типа map[K]V, используемое в присваивании или инициализации специальной формы.

v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]

дает дополнительное нетипизированное логическое значение. Значение ok равно true, если ключ x присутствует в карте, и false в противном случае.

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

Есть три способа вернуть значения из функции с типом результата:

  1. Возвращаемое значение или значения могут быть явно указаны в операторе return. Каждое выражение должно быть однозначным и присваиваться соответствующему элементу типа результата функции.

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

  3. Список выражений может быть пустым, если тип результата функции указывает имена для ее параметров результата. Параметры результата действуют как обычные локальные переменные, и функция может присваивать им значения по мере необходимости. Оператор return возвращает значения этих переменных.

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

Итак, почему спецификация языка не разрешает такого рода специальное использование индексации карты (или утверждения типа или получения канала, оба из которых также могут использовать идиому запятая ok) в операторах возврата? Это хороший вопрос. Мое предположение: сделать спецификацию языка простой.

person LemurFromTheId    schedule 09.05.2015

Я не эксперт по Go, но я считаю, что вы получаете ошибку времени компиляции, когда пытаетесь вернуть массив, то есть return all[id]. Причина может заключаться в том, что тип возвращаемого значения функции специально упоминается как (Info, bool), и когда вы выполняете return all[id], он не может сопоставить возвращаемый тип all[id] с (Info, bool).

Однако в решении, упомянутом выше, возвращаемые переменные i и ok совпадают с указанными в возвращаемом типе функции (i Info, ok bool), и, следовательно, компилятор знает, что он возвращает, а не просто делает (i Info, ok bool).

person srock    schedule 08.05.2015
comment
return all[id] возвращает не массив, а элемент на карте. если возвращаемый тип - это просто элемент, это нормально, но на самом деле он возвращает 2 значения, поэтому вы можете сделать a,b := foo[id] - person Pablo Fernandez; 09.05.2015

По умолчанию карты в golang возвращают одно значение при доступе к ключу

https://blog.golang.org/go-maps-in-action

Следовательно, return all[id] не будет компилироваться для функции, которая ожидает 2 возвращаемых значения.

person Manan    schedule 08.05.2015