Opcjonalne parametry w Go?


464

Czy Go może mieć opcjonalne parametry? Czy mogę po prostu zdefiniować dwie funkcje o tej samej nazwie i innej liczbie argumentów?


Powiązane: w jaki sposób można wymusić obowiązkowe parametry przy użyciu variadic jako parametrów opcjonalnych: Czy możliwe jest wyzwolenie błędu czasu kompilacji z niestandardową biblioteką w Golang?
icza

11
Google podjął straszną decyzję, ponieważ czasami funkcja ma 90% przypadków użycia, a następnie 10% przypadków użycia. Opcjonalny argument jest przeznaczony dla tego 10% użycia. Rozsądne wartości domyślne oznaczają mniej kodu, mniej kodu oznacza większą łatwość konserwacji.
Jonathan

Odpowiedzi:


431

Go nie ma opcjonalnych parametrów ani nie obsługuje przeciążania metod :

Wysyłanie metod jest uproszczone, jeśli nie trzeba również dopasowywać typów. Doświadczenie z innymi językami mówiło nam, że różnorodne metody o tej samej nazwie, ale różne podpisy okazjonalnie były przydatne, ale w praktyce może być także mylące i kruche. Dopasowywanie tylko według nazwy i wymaganie spójności typów było główną decyzją upraszczającą w systemie typów Go.


58
Czy makezatem jest przypadek szczególny? A może nie jest tak naprawdę zaimplementowany jako funkcja…
mk12

65
@ Mk12 makejest konstrukcją językową i powyższe zasady nie mają zastosowania. Zobacz to powiązane pytanie .
nemo

7
rangeto ten sam przypadek, co makew tym sensie
thiagowfx

14
Przeciążenia metod - Świetny pomysł w teorii i doskonały, gdy jest dobrze wdrożony. Jednak byłem świadkiem nieczytelnego przeciążenia śmieci w praktyce i dlatego zgodziłbym się z decyzją
Google'a

118
Mam zamiar wyjść na całość i nie zgodzić się z tym wyborem. Projektanci języków w zasadzie powiedzieli: „Potrzebujemy przeciążenia funkcji, aby zaprojektować język, który chcemy, więc make, zakres i tak dalej są zasadniczo przeciążone, ale jeśli chcesz przeciążenia funkcji, aby zaprojektować API, które chcesz, cóż, to trudne”. Fakt, że niektórzy programiści niewłaściwie używają funkcji języka, nie jest argumentem za pozbyciem się tej funkcji.
Tom

216

Dobrym sposobem na osiągnięcie czegoś takiego jak parametry opcjonalne jest użycie argumentów variadic. Funkcja faktycznie odbiera plasterek dowolnego określonego typu.

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

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

„funkcja faktycznie odbiera wycinek dowolnego typu, który określisz”, w jaki sposób?
Alix Axel

3
w powyższym przykładzie paramsjest kawałek
ints

76
Ale tylko dla tego samego typu parametrów :(
Juan de Parras,

15
@JuandeParras Cóż, nadal możesz używać czegoś takiego jak ... interfejs {} Chyba.
maufl

5
Z typem ... nie przekazujesz znaczenia poszczególnych opcji. Zamiast tego użyj struktury. ... typ jest przydatny w przypadku wartości, które w innym przypadku musiałbyś wstawić do tablicy przed wywołaniem.
user3523091 30.01.16

169

Możesz użyć struktury, która zawiera parametry:

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
Byłoby wspaniale, gdyby struktury mogły mieć tutaj wartości domyślne; wszystko, co użytkownik pomija, ma domyślną wartość zero dla tego typu, co może, ale nie musi być odpowiednim domyślnym argumentem dla funkcji.
jsdw

41
@lytnus, nie lubię dzielić włosów, ale pola, w których wartości są pomijane, domyślnie przyjmowałyby wartość „zero” dla ich typu; zero to inne zwierzę. Gdyby typ pominiętego pola był wskaźnikiem, wartość zero wynosiłaby zero.
burfl

2
@ burfl tak, z wyjątkiem pojęcia „wartość zerowa” jest absolutnie bezużyteczne dla typów int / float / string, ponieważ te wartości są znaczące i dlatego nie można odróżnić, czy wartość została pominięta w strukturze, czy też wartość zero była zdane celowo.
keymone

3
@keymone, nie zgadzam się z tobą. Byłem pedantycznie nastawiony do powyższego stwierdzenia, że ​​wartości pominięte przez użytkownika domyślnie przyjmują wartość „zero dla tego typu”, co jest niepoprawne. Domyślnie przyjmują wartość zerową, która może być zerowa, w zależności od tego, czy typ jest wskaźnikiem.
burfl

123

W przypadku arbitralnej, potencjalnie dużej liczby parametrów opcjonalnych, dobrym idiomem jest użycie opcji funkcjonalnych .

Dla swojego typu Foobarnapisz najpierw tylko jednego konstruktora:

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
}

gdzie każda opcja jest funkcją, która mutuje Foobar. Następnie zapewnij użytkownikowi wygodne sposoby korzystania lub tworzenia standardowych opcji, na przykład:

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

Plac zabaw

Dla zwięzłości możesz nadać nazwę typowi opcji ( Plac zabaw ):

type OptionFoobar func(*Foobar) error

Jeśli potrzebujesz parametrów obowiązkowych, dodaj je jako pierwsze argumenty konstruktora przed variadic options.

Główne zalety idiomu opcji funkcjonalnych to:

  • Twój interfejs API może z czasem rosnąć bez niszczenia istniejącego kodu, ponieważ podpis konstruktora pozostaje taki sam, gdy potrzebne są nowe opcje.
  • pozwala, aby domyślny przypadek użycia był najprostszy: żadnych argumentów!
  • zapewnia dokładną kontrolę inicjalizacji złożonych wartości.

Ta technika została wymyślona przez Roba Pike'a, a także zademonstrowana przez Dave'a Cheneya .



15
Sprytne, ale zbyt skomplikowane. Filozofią Go jest pisanie kodu w prosty sposób. Wystarczy przekazać strukturę i przetestować wartości domyślne.
user3523091 30.01.16

9
Tylko FYI, oryginalnym autorem tego idiomu, przynajmniej pierwszym wymienionym wydawcą, jest komandor Rob Pike, który uważam za wystarczająco autorytatywny dla filozofii Go. Link - Commandcenter.blogspot.bg/2014/01/… . Szukaj także „Proste jest skomplikowane”.
Petar Donchev

2
#JMTCW, ale uważam to podejście za bardzo trudne. Zdecydowanie wolałbym przekazać strukturę wartości, której właściwości mogą być w func()razie potrzeby s, niż pochylać mój mózg wokół tego podejścia. Ilekroć muszę korzystać z tego podejścia, na przykład w bibliotece Echo, mój mózg zostaje uwięziony w króliczej naturze abstrakcji. #fwiw
MikeSchinkel

dzięki, szukałem tego idiomu. Równie dobrze może być tam jako zaakceptowana odpowiedź.
r --------- k


6

Nie - nie. Dokumenty dla programistów Per the Go for C ++ ,

Go nie obsługuje przeciążania funkcji i nie obsługuje operatorów zdefiniowanych przez użytkownika.

Nie mogę znaleźć równie jasnego stwierdzenia, że ​​parametry opcjonalne nie są obsługiwane, ale też nie są obsługiwane.


8
„Nie ma aktualnego planu dla tego [parametry opcjonalne].” Ian Lance Taylor, zespół językowy Go. groups.google.com/group/golang-nuts/msg/030e63e7e681fd3e
peterSO

Żaden operator zdefiniowany przez użytkownika nie jest straszną decyzją, ponieważ jest podstawą każdej zręcznej biblioteki matematycznej, takiej jak produkty kropkowe lub produkty krzyżowe dla algebry liniowej, często stosowane w grafice 3D.
Jonathan

4

Możesz to całkiem dobrze zamknąć w func podobnym do tego, co poniżej.

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
}

W tym przykładzie monit domyślnie ma dwukropek i spację przed nim. . .

: 

. . . można jednak to zmienić, podając parametr do funkcji pytania.

prompt("Input here -> ")

Spowoduje to wyświetlenie monitu jak poniżej.

Input here ->

3

Skończyło się na tym, że użyłem kombinacji struktury params i różnych argumentów. W ten sposób nie musiałem zmieniać istniejącego interfejsu, który był używany przez kilka usług, a moja usługa mogła przekazać dodatkowe parametry w razie potrzeby. Przykładowy kod na placu zabaw Golang: https://play.golang.org/p/G668FA97Nu


3

Język Go nie obsługuje przeciążania metod, ale możesz używać argumentów variadic tak jak parametrów opcjonalnych, możesz także użyć interfejsu {} jako parametru, ale nie jest to dobry wybór.


2

Jestem trochę spóźniony, ale jeśli lubisz płynny interfejs, możesz zaprojektować setery do połączeń łańcuchowych w następujący sposób:

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
}

A potem nazwij to tak:

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

Jest to podobne do idiomu opcji funkcjonalnych przedstawionego na odpowiedzi @Ripounet i ma te same zalety, ale ma pewne wady:

  1. Jeśli wystąpi błąd, nie zostanie on natychmiast przerwany, dlatego byłoby nieco mniej wydajne, jeśli oczekuje się od konstruktora częstego zgłaszania błędów.
  2. Musisz wydać wiersz deklarujący errzmienną i ją zerujący.

Istnieje jednak pewna niewielka zaleta, tego typu wywołania funkcji powinny być łatwiejsze dla kompilatora do wstawienia, ale tak naprawdę nie jestem specjalistą.


to jest wzorzec konstruktora
UmNyobe,

2

Możesz przekazać dowolne nazwane parametry za pomocą mapy.

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

Oto komentarz na temat tego podejścia: reddit.com/r/golang/comments/546g4z/…
nobar


0

Inną możliwością byłoby użycie struktury, która z polem wskazuje, czy jest poprawna. Wygodne są typy zerowe z SQL, takie jak NullString . Miło jest nie musieć definiować własnego typu, ale jeśli potrzebujesz niestandardowego typu danych, zawsze możesz zastosować ten sam wzorzec. Myślę, że opcjonalność wynika z definicji funkcji i wymaga minimalnego dodatkowego kodu lub wysiłku.

Jako przykład:

func Foo(bar string, baz sql.NullString){
  if !baz.Valid {
        baz.String = "defaultValue"
  }
  // the rest of the implementation
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.