Parametri opzionali in Go?


464

Go può avere parametri opzionali? O posso semplicemente definire due funzioni con lo stesso nome e un diverso numero di argomenti?


Correlati: ecco come si può fare per applicare parametri obbligatori quando si utilizza variadic come parametri opzionali: È possibile attivare un errore di tempo di compilazione con la libreria personalizzata in Golang?
Icza,

11
Google ha preso una decisione terribile, perché a volte una funzione ha un caso d'uso del 90% e quindi un caso d'uso del 10%. L'arg opzionale è per quel caso d'uso del 10%. Impostazioni predefinite corrette significano meno codice, meno codice significa più manutenibilità.
Jonathan

Risposte:


431

Go non ha parametri opzionali né supporta il sovraccarico del metodo :

L'invio del metodo è semplificato se non è necessario eseguire anche la corrispondenza del tipo. L'esperienza con altre lingue ci ha detto che avere una varietà di metodi con lo stesso nome ma firme diverse era occasionalmente utile ma che poteva anche essere confuso e fragile nella pratica. La corrispondenza solo per nome e la necessità di coerenza nei tipi è stata una decisione di semplificazione importante nel sistema di tipi di Go.


58
È makeun caso speciale, allora? O non è nemmeno realmente implementato come funzione ...
mk12

65
@ Mk12 makeè un costrutto di linguaggio e le regole sopra menzionate non si applicano. Vedi questa domanda correlata .
nemo,

7
rangeè lo stesso caso make, in tal senso
thiagowfx,

14
Sovraccarichi di metodo - Un'ottima idea in teoria ed eccellente se implementata bene. Tuttavia, in pratica ho assistito a un sovraccarico indecifrabile della spazzatura e sarei quindi d'accordo con la decisione di Google
trevorgk,

118
Sto andando fuori di testa e non sono d'accordo con questa scelta. I progettisti del linguaggio hanno sostanzialmente affermato: "Abbiamo bisogno di un sovraccarico delle funzioni per progettare il linguaggio che vogliamo, quindi marca, gamma e così via sono essenzialmente sovraccarichi, ma se vuoi un sovraccarico delle funzioni per progettare l'API che desideri, beh, è ​​difficile". Il fatto che alcuni programmatori utilizzino in modo improprio una funzione del linguaggio non è un argomento per sbarazzarsi della funzione.
Tom,

216

Un buon modo per ottenere qualcosa come parametri opzionali è usare args variadici. La funzione riceve effettivamente una porzione di qualunque tipo specificato.

func foo(params ...int) {
    fmt.Println(len(params))
}

func main() {
    foo()
    foo(1)
    foo(1,2,3)
}

"La funzione riceve effettivamente una porzione di qualsiasi tipo specificato" come mai?
Alix Axel,

3
nell'esempio sopra, paramsè una fetta di
ints

76
Ma solo per lo stesso tipo di parametri :(
Juan de Parras,

15
@JuandeParras Bene, puoi ancora usare qualcosa come ... interfaccia {} credo.
Maufl,

5
Con ... type non stai trasmettendo il significato delle singole opzioni. Utilizzare invece una struttura. ... type è utile per i valori che altrimenti dovresti inserire in un array prima della chiamata.
user3523091

170

È possibile utilizzare una struttura che include i parametri:

type Params struct {
  a, b, c int
}

func doIt(p Params) int {
  return p.a + p.b + p.c 
}

// you can call it without specifying all parameters
doIt(Params{a: 1, c: 9})

12
Sarebbe bello se le strutture potessero avere valori predefiniti qui; tutto ciò che l'utente omette viene automaticamente impostato sul valore zero per quel tipo, che può essere o meno un argomento predefinito adatto alla funzione.
jsdw,

41
@lytnus, odio dividere i capelli, ma i campi per i quali sono omessi i valori predefiniti sarebbero il "valore zero" per il loro tipo; lo zero è un animale diverso. Se il tipo di campo omesso dovesse essere un puntatore, il valore zero sarebbe nullo.
burfl,

2
@burfl sì, tranne la nozione di "valore zero" è assolutamente inutile per i tipi int / float / string, perché quei valori sono significativi e quindi non si può dire la differenza se il valore è stato omesso dalla struttura o se il valore zero era passato intenzionalmente.
Keymone,

3
@keymone, non sono d'accordo con te. Ero semplicemente pedante riguardo all'affermazione sopra che i valori omessi dall'utente impostano il "valore zero per quel tipo", che non è corretto. L'impostazione predefinita è il valore zero, che può essere o meno nulla, a seconda che il tipo sia un puntatore.
burfl

125

Per un numero arbitrario, potenzialmente elevato, di parametri opzionali, un buon linguaggio è usare le opzioni funzionali .

Per il tuo tipo Foobar, prima scrivi un solo costruttore:

func NewFoobar(options ...func(*Foobar) error) (*Foobar, error){
  fb := &Foobar{}
  // ... (write initializations with default values)...
  for _, op := range options{
    err := op(fb)
    if err != nil {
      return nil, err
    }
  }
  return fb, nil
}

dove ogni opzione è una funzione che muta il Foobar. Quindi fornire modi convenienti per l'utente di utilizzare o creare opzioni standard, ad esempio:

func OptionReadonlyFlag(fb *Foobar) error {
  fb.mutable = false
  return nil
}

func OptionTemperature(t Celsius) func(*Foobar) error {
  return func(fb *Foobar) error {
    fb.temperature = t
    return nil
  }
}

Terreno di gioco

Per semplicità, puoi dare un nome al tipo di opzioni ( Playground ):

type OptionFoobar func(*Foobar) error

Se hai bisogno di parametri obbligatori, aggiungili come primi argomenti del costruttore prima del variadic options.

I principali vantaggi del linguaggio delle opzioni funzionali sono:

  • la tua API può crescere nel tempo senza rompere il codice esistente, perché la firma del costatore rimane invariata quando sono necessarie nuove opzioni.
  • consente al caso d'uso predefinito di essere il più semplice: nessun argomento!
  • fornisce un controllo accurato sull'inizializzazione di valori complessi.

Questa tecnica è stata coniata da Rob Pike e dimostrata anche da Dave Cheney .



15
Intelligente, ma troppo complicato. La filosofia di Go è scrivere codice in modo semplice. Basta passare una struttura e testare i valori predefiniti.
user3523091

9
Proprio FYI, l'autore originale di questo idioma, almeno il primo editore a cui viene fatto riferimento, è il comandante Rob Pike, che considero abbastanza autorevole per la filosofia Go. Link - commandcenter.blogspot.bg/2014/01/… . Cerca anche "Semplice è complicato".
Petar Donchev,

2
#JMTCW, ma trovo molto difficile ragionare su questo approccio. Preferirei di gran lunga passare in una struttura di valori, le cui proprietà potrebbero essere func()s, se necessario, piuttosto che piegare il mio cervello attorno a questo approccio. Ogni volta che devo usare questo approccio, ad esempio con la libreria Echo, trovo il mio cervello intrappolato nella tana del coniglio delle astrazioni. #fwiw
MikeSchinkel

grazie, stavo cercando questo idioma. Potrebbe anche essere lassù come la risposta accettata.
r --------- k


6

No - nessuno dei due. Per i documenti dei programmatori Go for C ++ ,

Go non supporta il sovraccarico delle funzioni e non supporta gli operatori definiti dall'utente.

Non riesco a trovare un'affermazione altrettanto chiara del fatto che i parametri opzionali non sono supportati, ma non sono supportati neanche.


8
"Non esiste un piano attuale per questo [parametri opzionali]." Ian Lance Taylor, squadra linguistica Go. groups.google.com/group/golang-nuts/msg/030e63e7e681fd3e
peterSO

Nessun operatore definito dall'utente è una decisione terribile, in quanto è il nucleo dietro qualsiasi libreria matematica slick, come prodotti a punti o prodotti incrociati per l'algebra lineare, spesso utilizzata nella grafica 3D.
Jonathan

4

Puoi incapsularlo abbastanza bene in una funzione simile a quella che segue.

package main

import (
        "bufio"
        "fmt"
        "os"
)

func main() {
        fmt.Println(prompt())
}

func prompt(params ...string) string {
        prompt := ": "
        if len(params) > 0 {
                prompt = params[0]
        }
        reader := bufio.NewReader(os.Stdin)
        fmt.Print(prompt)
        text, _ := reader.ReadString('\n')
        return text
}

In questo esempio, il prompt per impostazione predefinita ha due punti e uno spazio davanti. . .

: 

. . . tuttavia è possibile sovrascriverlo fornendo un parametro alla funzione prompt.

prompt("Input here -> ")

Ciò si tradurrà in un prompt come di seguito.

Input here ->

3

Ho finito per usare una combinazione di una struttura di parametri e argomenti vari. In questo modo, non ho dovuto modificare l'interfaccia esistente utilizzata da numerosi servizi e il mio servizio è stato in grado di passare parametri aggiuntivi in ​​base alle esigenze. Codice di esempio nel parco giochi Golang: https://play.golang.org/p/G668FA97Nu


3

La lingua Go non supporta il sovraccarico del metodo, ma è possibile utilizzare gli argomenti variadic proprio come i parametri opzionali, inoltre è possibile utilizzare l'interfaccia {} come parametro ma non è una buona scelta.


2

Sono un po 'in ritardo, ma se ti piace un'interfaccia fluida potresti progettare i tuoi setter per chiamate concatenate come questa:

type myType struct {
  s string
  a, b int
}

func New(s string, err *error) *myType {
  if s == "" {
    *err = errors.New(
      "Mandatory argument `s` must not be empty!")
  }
  return &myType{s: s}
}

func (this *myType) setA (a int, err *error) *myType {
  if *err == nil {
    if a == 42 {
      *err = errors.New("42 is not the answer!")
    } else {
      this.a = a
    }
  }
  return this
}

func (this *myType) setB (b int, _ *error) *myType {
  this.b = b
  return this
}

E poi chiamalo così:

func main() {
  var err error = nil
  instance :=
    New("hello", &err).
    setA(1, &err).
    setB(2, &err)

  if err != nil {
    fmt.Println("Failed: ", err)
  } else {
    fmt.Println(instance)
  }
}

Questo è simile al linguaggio delle opzioni funzionali presentato sulla risposta di @Ripounet e gode degli stessi vantaggi ma presenta alcuni svantaggi:

  1. Se si verifica un errore, questo non si interromperà immediatamente, pertanto sarebbe leggermente meno efficiente se si prevede che il costruttore riporti spesso errori.
  2. Dovrai spendere una riga per dichiarare una errvariabile e azzerarla.

Vi è, tuttavia, un possibile piccolo vantaggio, questo tipo di chiamate di funzione dovrebbe essere più facile da integrare per il compilatore, ma non sono davvero uno specialista.


questo è uno schema di costruzione
UmNyobe,

2

È possibile passare parametri nominali arbitrari con una mappa.

type varArgs map[string]interface{}

func myFunc(args varArgs) {

    arg1 := "default" // optional default value
    if val, ok := args["arg1"]; ok {
        // value override or other action
        arg1 = val.(string) // runtime panic if wrong type
    }

    arg2 := 123 // optional default value
    if val, ok := args["arg2"]; ok {
        // value override or other action
        arg2 = val.(int) // runtime panic if wrong type
    }

    fmt.Println(arg1, arg2)
}

func Test_test() {
    myFunc(varArgs{"arg1": "value", "arg2": 1234})
}

Ecco alcuni commenti su questo approccio: reddit.com/r/golang/comments/546g4z/…
nobar


0

Un'altra possibilità sarebbe quella di utilizzare una struttura che con un campo per indicare se è valida. I tipi null da sql come NullString sono convenienti. È bello non dover definire il proprio tipo, ma nel caso in cui sia necessario un tipo di dati personalizzato è sempre possibile seguire lo stesso modello. Penso che l'opzione opzionale sia chiara dalla definizione della funzione e ci sia un minimo di codice o sforzo in più.

Come esempio:

func Foo(bar string, baz sql.NullString){
  if !baz.Valid {
        baz.String = "defaultValue"
  }
  // the rest of the implementation
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.