2015-05-08 18 views
15

Mi chiedevo perché questo è valido codice Go:golang restituire più valori problema

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

ma questo non

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

è c'è un modo per evitare le variabili temporanee?

+2

Non sono esperto in come Go funziona nel back-end, ma il modello "virgola ok" viene applicato alle operazioni incorporate (non alle funzioni "reali"). Dal momento che è opzionale, sembrerebbe che il compilatore verifichi l'assegnazione * a più valori *. Forse qualcuno [più familiare con il compilatore] (https://github.com/golang/go/blob/254964074f34dc1cb39693f3e23a95938092044f/src/go/types/call.go#L118-L134) può elaborare – Lander

+0

ti dispiacerebbe elaborare sul " virgola ok "modello? una risposta sarebbe apprezzata (e svalutata) dal momento che non sto solo cercando di salvare i tasti ma comprendendo ** perché ** questo accade. –

+1

Ho un forte sospetto sul motivo per cui ciò non è consentito, ma dovrò fare riferimento alle specifiche. Penso che ci sia un motivo logico valido per questo. Abbastanza sicuro che se lo permetteste potrei inventare alcuni casi limite che interromperanno il compilatore o comportano un comportamento indefinito/incoerente. – evanmcdonnal

risposta

14

Si può risparmiare un paio di colpi di chiave utilizzando i rendimenti denominati:

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

Ma a parte questo, non credo che ciò che si vuole è possibile.

2

Non sono esperto, ma ritengo che si stia verificando un errore in fase di compilazione quando si tenta di restituire l'array, ad esempio return all[id]. Il motivo potrebbe essere dovuto al fatto che il tipo restituito da funzioni è indicato in particolare come (Info, bool) e quando si sta eseguendo return all[id] non è possibile associare il tipo di reso di all[id] a (Info, bool).

Tuttavia la soluzione di cui sopra, le variabili da restituire i e ok sono gli stessi che vengono menzionati nel tipo di ritorno della funzione (i Info, ok bool) e quindi il compilatore sa cosa sta tornando piuttosto che semplicemente facendo (i Info, ok bool).

+0

'return all [id]' non sta restituendo un array ma un elemento nella ** mappa **. se il tipo di ritorno è solo un elemento che va bene, ma in realtà restituisce 2 valori, ecco perché puoi fare 'a, b: = foo [id]' –

11

Per elaborare il mio comment, il Effective Go indica che l'assegnazione a più valori dall'accesso a una chiave di mappa è denominata "virgola ok".

A volte è necessario distinguere una voce mancante da un valore zero. C'è una voce per "UTC" o è la stringa vuota perché non è nella mappa? Puoi discriminare con una forma di assegnazione multipla.

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

Per ovvie ragioni questo è chiamato il “comma ok” idioma. In questo esempio, se tz è presente, i secondi saranno impostati in modo appropriato e ok sarà vero; in caso contrario, i secondi saranno impostati su zero e ok sarà falso.

Playground demonstrating this

Possiamo vedere che questo si differenzia da chiamare una funzione regolare in cui il compilatore vi direbbe che qualcosa non va:

package main 

import "fmt" 

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

func main() { 
    fmt.Println(multiValueReturn) 

    asgn1, _ := multiValueReturn() 

    asgn2 := multiValueReturn() 
} 

Sulla playground questa uscita volontà

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

Questo ci dà un suggerimento che potrebbe essere qualcosa la compilazione sta facendo.Searching the source code per "commaOk" ci dà un paio di posti per cercare, tra cui types.unpack

Al momento della stesura di questo GODOC questo il metodo legge:

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

I bit chiave di questo è che questo metodo sembra determinare se qualcosa è effettivamente un caso "virgola ok".

Scavando in quel metodo ci dice che controllerà per vedere se la modalità degli operandi è indicizzazione una mappa o se la modalità è impostata su commaok (dove questa is defined ci dà molti suggerimenti su quando viene utilizzato, ma cercando la fonte per i compiti a commaok possiamo vedere che è usato quando getting a value from a channel e type assertions). Ricorda il pezzo audace per dopo!

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 è un parametro alla funzione. Controllando dove viene chiamato unpack in quel file possiamo vedere che tutti i chiamanti passano come argomento false. La ricerca nel resto del repository ci porta a assignments.go nello Checker.initVars() method.

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

Poiché sembra che siamo in grado di utilizzare solo il modello "comma ok" per ottenere due valori di ritorno quando si fa un'assegnazione più valori questo mi sembra il posto giusto per cercare! Nel codice sopra riportato viene verificata la lunghezza della parte sinistra e, quando viene chiamato il parametro unpack, il parametro allowCommaOk è il risultato di l == 2 && !returnPos.IsValid(). Il !returnPos.IsValid() è un po 'di confusione in quanto ciò significherebbe che la posizione has no file or line information associated con esso, ma lo ignoreremo.

Più in basso in questo metodo abbiamo:

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 
} 

Che cosa significa tutto questo ci dice?

  • Poiché il metodo unpack accetta un parametro allowCommaOk che è codificato a false ovunque tranne in Checker.initVars() metodo s' assignment.go, probabilmente possiamo supporre che si sempre e solo ottenere due valori quando si fa un compito e hanno due variabili sul lato sinistro.
  • Il metodo unpack sarà determinare se effettivamente fai ottenere un valore ok in cambio controllando se si è l'indicizzazione di un fetta, afferrando un valore da un canale, o fare un tipo di affermazione
  • Dal momento che si può solo ottenere il valore ok quando facendo un incarico sembra che nel tuo caso specifico si avrà sempre bisogno di usare le variabili
6

in poche parole: il motivo per cui il secondo esempio non è valido il codice Go è perché la specifica del linguaggio dice così . ;)

Indexing a map restituisce solo un valore secondario in un'assegnazione a due variabili. La dichiarazione di reso non è un compito.

Un'espressione indice su una mappa una di tipo map [K] V utilizzato in un'assegnazione o inizializzazione di forma speciale

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

restituisce un valore booleano aggiuntivo non tipizzato. Il valore di ok è vero se la chiave x è presente nella mappa e false altrimenti.

Inoltre, indicizzare una mappa non è una "single call to a multi-valued function", che è uno dei tre modi per restituire valori da una funzione (il secondo, gli altri due non essendo rilevante qui):

ci sono tre modi per restituire valori da una funzione con un tipo di risultato:

  1. il valore oi valori di ritorno possono essere esplicitamente elencati nella dichiarazione "ritorno". Ogni espressione deve essere a valore singolo e assegnabile all'elemento corrispondente del tipo di risultato della funzione.

  2. L'elenco di espressioni nell'istruzione "return" può essere una singola chiamata a una funzione multivalore. L'effetto è come se ogni valore restituito da quella funzione fosse assegnato a una variabile temporanea con il tipo del rispettivo valore, seguito da un'istruzione "return" che elenca queste variabili, a quel punto si applicano le regole del caso precedente.

  3. L'elenco di espressioni può essere vuoto se il tipo di risultato della funzione specifica i nomi per i relativi parametri di risultato. I parametri del risultato agiscono come variabili locali ordinarie e la funzione può assegnare loro i valori necessari. L'istruzione "return" restituisce i valori di queste variabili.

Per quanto riguarda la tua domanda effettiva: l'unico modo per evitare le variabili temporanee sarebbe utilizzando le variabili non temporanea, ma di solito che sarebbe molto saggio - e probabilmente non molto di un'ottimizzazione, anche quando al sicuro.

Quindi, perché le specifiche del linguaggio non consentono questo tipo di uso speciale dell'indicizzazione della mappa (o l'asserzione del tipo o la ricezione del canale, che possono entrambi utilizzare anche l'idioma "virgola ok") nelle dichiarazioni di reso? Questa è una bella domanda. La mia ipotesi: mantenere le specifiche della lingua semplice.