Необов’язкові параметри в роботі?


464

Чи можуть мати додаткові параметри Go? Або я можу просто визначити дві функції з однаковим іменем та різною кількістю аргументів?


Пов’язано: це, як це можна зробити для примусового використання обов'язкових параметрів при використанні variadic в якості необов'язкових параметрів: Чи можливо запустити помилку часу компіляції за допомогою спеціальної бібліотеки в голонг?
icza

11
Google прийняв жахливе рішення, оскільки іноді функція має 90% випадків використання, а потім 10% випадків використання. Необов’язковий аргумент призначений для 10% випадків використання. Звичайні параметри за замовчуванням означають менше коду, менший код означає більше ремонтопридатності.
Джонатан

Відповіді:


431

Go не має додаткових параметрів, а також не підтримує перевантаження методу :

Відправлення методу спрощується, якщо також не потрібно робити відповідність типу. Досвід інших мов свідчить про те, що використання різноманітних методів з однаковою назвою, але різними підписами іноді було корисно, але це також може бути заплутаним та крихким на практиці. Поєднання лише за назвою та вимагало послідовності типів було головним спрощенням рішення в системі типів Go.


58
Є чи makeце особливий випадок, тоді? Або він навіть реально не реалізований як функція…
mk12

65
@ Mk12 make- це мовна конструкція, і правила, згадані вище, не застосовуються. Дивіться це пов'язане питання .
nemo

7
rangeце той самий випадок make, що і в цьому сенсі
thiagowfx

14
Перевантаження методів - відмінна ідея в теорії та відмінна, коли вона добре реалізована. Однак я був свідком нерозбірливого перевантаження сміття на практиці, і тому погодився би з рішенням Google
trevorgk

118
Я збираюся вийти на кінцівку і не погоджуюся з цим вибором. В основному мовні дизайнери сказали: "Нам потрібна функція перевантаження для розробки потрібної мови, тому make, range та інше по суті перевантажені, але якщо ви хочете перевантажити функцію, щоб створити потрібний API, ну це важко". Те, що деякі програмісти зловживають мовною функцією, не є аргументом для позбавлення від функції.
Том

216

Хороший спосіб домогтися чогось на зразок необов’язкових параметрів - це використовувати різні аргументи. Функція фактично отримує фрагмент будь-якого типу, який ви вказали.

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

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

"функція фактично отримує фрагмент будь-якого типу, який ви вказали" як так?
Алікс Аксель

3
у наведеному вище прикладі params- фрагмент
інтів

76
Але лише для однотипних парам :(
Хуан де Паррас

15
@JuandeParras Ну, ви все одно можете використовувати щось на зразок ... інтерфейсу {} Я думаю.
maufl

5
С ... введіть, що ви не передаєте значення окремих варіантів. Використовуйте натомість структуру. ... type є зручним для значень, які б інакше вам довелося б помістити в масив перед викликом.
користувач3523091

169

Ви можете використовувати структуру, яка включає параметри:

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
Було б чудово, якби структури могли мати тут значення за замовчуванням; все, що ухиляється користувачем, за замовчуванням встановлено значення нуля для цього типу, що може бути, а може і не бути відповідним аргументом за замовчуванням для функції.
jsdw

41
@lytnus, я ненавиджу розщеплювати волоски, але поля, для яких значення опущені, за замовчуванням до "нульового значення" для їх типу; нуль - інша тварина. Якщо тип опущеного поля стане вказівником, значення нуля буде нульовим.
burfl

2
@burfl Так, за винятком поняття "нульове значення" абсолютно непридатне для типів int / float / string, тому що ці значення мають сенс, і тому ви не можете визначити різницю, якщо значення було опущено з структури або якщо нульове значення було пройшов навмисно.
keymone

3
@keymone, я не згоден з тобою. Я просто мав педантичність щодо висловлювання вище про те, що значення, пропущені користувачем за замовчуванням, до "нульового значення для цього типу", яке є невірним. Вони за замовчуванням становлять нульове значення, яке може бути, а не може бути нульовим, залежно від того, тип є вказівником.
burfl

124

Для довільної, потенційно великої кількості необов'язкових параметрів, приємною ідіомою є використання функціональних опцій .

Для свого типу Foobarспочатку напишіть лише один конструктор:

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
}

де кожен варіант є функцією, яка мутує Foobar. Потім надайте зручні способи користувачеві використовувати або створювати стандартні параметри, наприклад:

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

Ігровий майданчик

Для стислість ви можете вказати назву типу параметрів ( дитячий майданчик ):

type OptionFoobar func(*Foobar) error

Якщо вам потрібні обов'язкові параметри, додайте їх як перші аргументи конструктора перед варіантом options.

Основними перевагами ідіоми функціональних опцій є:

  • ваш API може зростати з часом, не порушуючи існуючий код, оскільки підпис кондуктора залишається таким же, коли потрібні нові параметри.
  • це дає можливість випадку використання за замовчуванням бути найпростішим: аргументів взагалі немає!
  • він забезпечує тонкий контроль над ініціалізацією складних значень.

Цей прийом був винайдений Роб Пайком, а також його продемонстрував Дейв Чейні .



15
Розумна, але занадто складна. Філософія Go полягає в тому, щоб писати код прямо. Просто пройдіть структуру та протестуйте значення за замовчуванням.
користувач3523091

9
Тільки FYI, оригінальний автор цієї ідіоми, принаймні першого посилання на видавця, - це командир Роб Пайк, якого я вважаю достатньо авторитетним для філософії Go. Посилання - commandcenter.blogspot.bg/2014/01/… . Також шукайте "Простий складний".
Петро Дончев

2
#JMTCW, але мені здається, що цей підхід дуже важко міркувати. Я набагато вважаю за краще передати структуру значень, властивості яких можуть бути func()необхідними, ніж те, що нав'язує мій мозок навколо цього підходу. Щоразу, коли мені доводиться використовувати такий підхід, наприклад, з бібліотекою Echo, я виявляю, що мій мозок потрапляє в кролячу нору абстракцій. #fwiw
MikeSchinkel

дякую, шукав цю ідіому. Можливо, так само, як прийнята відповідь.
r --------- k


6

Ні - ні. Per від Go для програмістів C ++ документації,

Go не підтримує функцію перевантаження та не підтримує визначені користувачем оператори.

Я не можу знайти однаково чітке твердження, що необов'язкові параметри не підтримуються, але вони також не підтримуються.


8
"Немає поточного плану для цього [необов'язкові параметри]." Ian Lance Taylor, команда з мови Go. groups.google.com/group/golang-nuts/msg/030e63e7e681fd3e
peterSO

Немає визначених користувачем операторів - це жахливе рішення, оскільки воно є ядром будь-якої гладкої математичної бібліотеки, наприклад, крапкових продуктів або крос-продуктів для лінійної алгебри, часто використовуваних у 3D-графіці.
Джонатан

4

Ви можете досить добре інкапсулювати це у функцію, подібну до наведеної нижче.

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
}

У цьому прикладі підказки за замовчуванням є двокрапка та пробіл перед нею. . .

: 

. . . однак ви можете це змінити, надавши параметр функції підказки.

prompt("Input here -> ")

Це призведе до підказки, як показано нижче.

Input here ->

3

Я в кінцевому підсумку використовував комбінацію структури парам і варіативних арг. Таким чином, мені не довелося змінювати існуючий інтерфейс, який споживав декілька сервісів, і моя служба змогла передавати додаткові параметри за потребою. Зразок коду на майданчику голанг: https://play.golang.org/p/G668FA97Nu


3

Мова Go не підтримує перевантаження методу, але ви можете використовувати різноманітні аргументи так само, як і необов'язкові параметри, також ви можете використовувати інтерфейс {} як параметр, але це не гарний вибір.


2

Я трохи запізнююся, але якщо вам подобається вільний інтерфейс, ви можете спроектувати свої сетери для ланцюгових дзвінків так:

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
}

А потім назвіть це так:

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

Це схоже на ідіому функціональних параметрів, представлену на відповіді @Ripounet, і має ті ж переваги, але має деякі недоліки:

  1. Якщо виникла помилка, вона не перестане негайно, таким чином, це буде дещо менш ефективно, якщо ви очікуєте, що ваш конструктор часто повідомляє про помилки.
  2. Вам доведеться витратити рядок, оголошуючи errзмінну та нулюючи її.

Однак є й невелика перевага: компілятор повинен випробовувати цей тип функціональних викликів, але я справді не фахівець.


це шаблон для будівельників
UmNyobe

2

Ви можете передавати довільні названі параметри за допомогою карти.

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

Ось деякі коментарі до цього підходу: reddit.com/r/golang/comments/546g4z/…
nobar


0

Іншою можливістю буде використання структури, яка з полем, щоб вказати, чи є її дійсною. Нульові типи з sql, такі як NullStringЗручні , зручні. Приємно не визначати власний тип, але якщо вам потрібен користувацький тип даних, ви завжди можете дотримуватися тієї ж схеми. Я думаю, що необов'язковість зрозуміла з визначення функції і є мінімальний додатковий код або зусилля.

Як приклад:

func Foo(bar string, baz sql.NullString){
  if !baz.Valid {
        baz.String = "defaultValue"
  }
  // the rest of the implementation
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.