Optionale Parameter in Go?


464

Kann Go optionale Parameter haben? Oder kann ich einfach zwei Funktionen mit demselben Namen und einer unterschiedlichen Anzahl von Argumenten definieren?


Verwandte Themen: Auf diese Weise können obligatorische Parameter erzwungen werden, wenn Variadic als optionale Parameter verwendet wird: Ist es möglich, einen Kompilierungszeitfehler mit einer benutzerdefinierten Bibliothek in Golang auszulösen?
icza

11
Google hat eine schreckliche Entscheidung getroffen, da eine Funktion manchmal einen Anwendungsfall von 90% und dann einen Anwendungsfall von 10% hat. Das optionale Argument ist für diesen Anwendungsfall von 10%. Vernünftige Standardeinstellungen bedeuten weniger Code, weniger Code bedeutet mehr Wartbarkeit.
Jonathan

Antworten:


431

Go verfügt weder über optionale Parameter noch unterstützt es das Überladen von Methoden :

Der Methodenversand wird vereinfacht, wenn kein Typabgleich durchgeführt werden muss. Die Erfahrung mit anderen Sprachen hat gezeigt, dass es gelegentlich nützlich ist, verschiedene Methoden mit demselben Namen, aber unterschiedlichen Signaturen zu verwenden, aber dass dies in der Praxis auch verwirrend und fragil sein kann. Nur nach Namen zu suchen und Konsistenz in den Typen zu erfordern, war eine wichtige vereinfachende Entscheidung im Typensystem von Go.


58
Ist also makeein Sonderfall? Oder ist es nicht einmal wirklich als Funktion implementiert ...
mk12

65
@ Mk12 makeist ein Sprachkonstrukt und die oben genannten Regeln gelten nicht. Siehe diese verwandte Frage .
Nemo

7
rangeist der gleiche Fall wie makein diesem Sinne
thiagowfx

14
Methodenüberladungen - Eine großartige Idee in der Theorie und ausgezeichnet, wenn sie gut implementiert sind. Ich habe jedoch in der Praxis eine nicht
entzifferbare

118
Ich werde mich auf die Beine stellen und dieser Wahl nicht zustimmen. Die Sprachdesigner haben im Grunde gesagt: "Wir brauchen eine Funktionsüberladung, um die gewünschte Sprache zu entwerfen. Daher sind make, range usw. im Wesentlichen überladen. Wenn Sie jedoch eine Funktionsüberladung wünschen, um die gewünschte API zu entwerfen, ist das schwierig." Die Tatsache, dass einige Programmierer eine Sprachfunktion missbrauchen, ist kein Argument dafür, die Funktion zu entfernen.
Tom

216

Ein guter Weg, um so etwas wie optionale Parameter zu erreichen, ist die Verwendung verschiedener Argumente. Die Funktion empfängt tatsächlich ein Slice des von Ihnen angegebenen Typs.

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

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

"function empfängt tatsächlich ein Slice des von Ihnen angegebenen Typs" wie so?
Alix Axel

3
im obigen Beispiel paramsist ein Stück Ints
Ferguzz

76
Aber nur für die gleiche Art von Params :(
Juan de Parras

15
@JuandeParras Nun, du kannst immer noch so etwas wie ... interface {} verwenden, denke ich.
Maufl

5
Mit ... geben Sie nicht die Bedeutung der einzelnen Optionen an. Verwenden Sie stattdessen eine Struktur. ... type ist praktisch für Werte, die Sie sonst vor dem Aufruf in ein Array einfügen müssten.
user3523091

170

Sie können eine Struktur verwenden, die die folgenden Parameter enthält:

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
Es wäre großartig, wenn Strukturen hier Standardwerte haben könnten; Alles, was der Benutzer weglässt, wird standardmäßig auf den Wert Null für diesen Typ gesetzt, der ein geeignetes Standardargument für die Funktion sein kann oder nicht.
jsdw

41
@lytnus, ich hasse es, Haare zu teilen, aber Felder, für die Werte weggelassen werden, würden standardmäßig den Wert 'Null' für ihren Typ verwenden. Null ist ein anderes Tier. Sollte der Typ des ausgelassenen Feldes ein Zeiger sein, wäre der Nullwert Null.
burfl

2
@burfl ja, außer dass der Begriff "Nullwert" für int / float / string-Typen absolut nutzlos ist, da diese Werte aussagekräftig sind und Sie den Unterschied nicht erkennen können, ob der Wert in der Struktur weggelassen wurde oder ob der Wert Null war absichtlich bestanden.
Keymone

3
@keymone, ich bin nicht anderer Meinung als du. Ich war nur pedantisch in Bezug auf die obige Aussage, dass vom Benutzer ausgelassene Werte standardmäßig den "Nullwert für diesen Typ" verwenden, was falsch ist. Sie verwenden standardmäßig den Wert Null, der null sein kann oder nicht, je nachdem, ob der Typ ein Zeiger ist.
Burfl

124

Für eine beliebige, möglicherweise große Anzahl optionaler Parameter ist es eine gute Redewendung, funktionale Optionen zu verwenden .

FoobarSchreiben Sie für Ihren Typ zunächst nur einen Konstruktor:

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
}

Dabei ist jede Option eine Funktion, die die Foobar mutiert. Bieten Sie Ihrem Benutzer dann bequeme Möglichkeiten, Standardoptionen zu verwenden oder zu erstellen, zum Beispiel:

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
  }
}

Spielplatz

Aus Gründen der Übersichtlichkeit können Sie dem Typ der Optionen ( Spielplatz ) einen Namen geben :

type OptionFoobar func(*Foobar) error

Wenn Sie obligatorische Parameter benötigen, fügen Sie diese als erste Argumente des Konstruktors vor der Variadik hinzu options.

Die Hauptvorteile der Funktionsoptionssprache sind:

  • Ihre API kann im Laufe der Zeit wachsen, ohne vorhandenen Code zu beschädigen, da die Konstruktorsignatur gleich bleibt, wenn neue Optionen benötigt werden.
  • Dadurch wird der Standardanwendungsfall am einfachsten: überhaupt keine Argumente!
  • Es bietet eine genaue Kontrolle über die Initialisierung komplexer Werte.

Diese Technik wurde von Rob Pike geprägt und auch von Dave Cheney demonstriert .



15
Clever, aber zu kompliziert. Die Philosophie von Go ist es, Code auf einfache Weise zu schreiben. Übergeben Sie einfach eine Struktur und testen Sie sie auf Standardwerte.
user3523091

9
Nur zu Ihrer Information, der ursprüngliche Autor dieser Redewendung, zumindest der erste Verlag, auf den verwiesen wird, ist Commander Rob Pike, den ich für maßgeblich genug für die Go-Philosophie halte. Link - commandcenter.blogspot.bg/2014/01/… . Suchen Sie auch nach "Einfach ist kompliziert".
Petar Donchev

2
#JMTCW, aber ich finde es sehr schwierig, über diesen Ansatz nachzudenken. Ich würde es bei weitem vorziehen, eine Struktur von Werten einzugeben, deren Eigenschaften bei Bedarf func()s sein könnten , als dass dies mein Gehirn um diesen Ansatz biegt. Immer wenn ich diesen Ansatz verwenden muss, beispielsweise bei der Echo-Bibliothek, gerät mein Gehirn in das Kaninchenloch der Abstraktionen. #fwiw
MikeSchinkel

danke, suchte nach dieser Redewendung. Könnte genauso gut da oben sein wie die akzeptierte Antwort.
r --------- k


6

Nein auch nicht. Gemäß den Dokumenten für Go for C ++ - Programmierer ,

Go unterstützt keine Funktionsüberladung und keine benutzerdefinierten Operatoren.

Ich kann keine ebenso klare Aussage finden, dass optionale Parameter nicht unterstützt werden, aber sie werden auch nicht unterstützt.


8
"Es gibt keinen aktuellen Plan für diese [optionalen Parameter]." Ian Lance Taylor, Go-Sprachteam. groups.google.com/group/golang-nuts/msg/030e63e7e681fd3e
peterSO

Kein benutzerdefinierter Operator ist eine schreckliche Entscheidung, da er der Kern jeder raffinierten Mathematikbibliothek ist, wie z. B. Punktprodukte oder Kreuzprodukte für die lineare Algebra, die häufig in 3D-Grafiken verwendet werden.
Jonathan

4

Sie können dies ganz gut in einer Funktion zusammenfassen, die der folgenden ähnelt.

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 diesem Beispiel enthält die Eingabeaufforderung standardmäßig einen Doppelpunkt und ein Leerzeichen. . .

: 

. . . Sie können dies jedoch überschreiben, indem Sie der Eingabeaufforderungsfunktion einen Parameter zuweisen.

prompt("Input here -> ")

Dies führt zu einer Eingabeaufforderung wie unten.

Input here ->

3

Am Ende habe ich eine Kombination aus einer Struktur von Parametern und verschiedenen Argumenten verwendet. Auf diese Weise musste ich die vorhandene Schnittstelle, die von mehreren Diensten verwendet wurde, nicht ändern, und mein Dienst konnte bei Bedarf zusätzliche Parameter übergeben. Beispielcode auf dem Golang-Spielplatz: https://play.golang.org/p/G668FA97Nu


3

Die Sprache Go unterstützt keine Methodenüberladung, aber Sie können verschiedene Argumente wie optionale Parameter verwenden. Sie können auch die Schnittstelle {} als Parameter verwenden, dies ist jedoch keine gute Wahl.


2

Ich bin etwas spät dran, aber wenn Sie eine flüssige Benutzeroberfläche mögen, können Sie Ihre Setter für verkettete Anrufe wie folgt entwerfen:

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
}

Und dann nenne es so:

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)
  }
}

Dies ähnelt der in der Antwort von @Ripounet dargestellten Redewendung für funktionale Optionen und bietet dieselben Vorteile, weist jedoch einige Nachteile auf:

  1. Wenn ein Fehler auftritt, wird er nicht sofort abgebrochen. Daher ist es etwas weniger effizient, wenn Sie erwarten, dass Ihr Konstruktor häufig Fehler meldet.
  2. Sie müssen eine Zeile damit verbringen, eine errVariable zu deklarieren und auf Null zu setzen.

Es gibt jedoch einen möglichen kleinen Vorteil: Diese Art von Funktionsaufrufen sollte für den Compiler einfacher zu inline sein, aber ich bin wirklich kein Spezialist.


Dies ist ein Builder-Muster
UmNyobe

2

Sie können beliebige benannte Parameter mit einer Karte übergeben.

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})
}

Hier ist ein Kommentar zu diesem Ansatz: reddit.com/r/golang/comments/546g4z/…
nobar


0

Eine andere Möglichkeit wäre, eine Struktur zu verwenden, die mit einem Feld angibt, ob sie gültig ist. Die Nulltypen von SQL wie NullString sind praktisch. Es ist schön, keinen eigenen Typ definieren zu müssen, aber falls Sie einen benutzerdefinierten Datentyp benötigen, können Sie immer dem gleichen Muster folgen. Ich denke, die Optionalität ergibt sich aus der Funktionsdefinition und es gibt nur minimalen zusätzlichen Code oder Aufwand.

Als Beispiel:

func Foo(bar string, baz sql.NullString){
  if !baz.Valid {
        baz.String = "defaultValue"
  }
  // the rest of the implementation
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.