Необязательные параметры в Go?

464

Может ли Go иметь дополнительные параметры? Или я могу просто определить две функции с одинаковым именем и разным количеством аргументов?

Devyn
источник
Связанный: это то, как это можно сделать для обеспечения обязательных параметров при использовании variadic в качестве необязательных параметров: возможно ли вызвать ошибку времени компиляции с пользовательской библиотекой в ​​golang?
icza
11
Google принял ужасное решение, потому что иногда функция имеет 90% сценария использования, а затем 10% варианта использования. Необязательный аргумент arg предназначен для этого 10% варианта использования. Разумные значения по умолчанию означают меньше кода, меньше кода означает больше удобства обслуживания.
Джонатан

Ответы:

431

Go не имеет необязательных параметров и не поддерживает перегрузку методов :

Диспетчеризация методов упрощается, если не требуется выполнять сопоставление типов. Опыт работы с другими языками показал нам, что наличие различных методов с одинаковыми именами, но разными сигнатурами иногда полезно, но на практике это также может быть запутанным и хрупким. Сопоставление только по имени и требование согласованности типов было основным упрощающим решением в системе типов Go.

Эндрю Хэйр
источник
58
Это makeособый случай? Или это даже не реализовано как функция ...
mk12
65
@ Mk12 makeявляется языковой конструкцией, и упомянутые выше правила не применяются. Смотрите этот связанный вопрос .
Немо
7
rangeэто тот же случай make, что и в этом смысле
thiagowfx
14
Перегрузки метода - отличная идея в теории и отличная реализация. Однако на практике я был свидетелем неразборчивой перегрузки мусора и поэтому согласился бы с решением Google
trevorgk
119
Я собираюсь выйти на конечности и не согласиться с этим выбором. Разработчики языка в основном говорили: «Нам нужна перегрузка функций для разработки необходимого языка, поэтому make, range и так далее существенно перегружены, но если вы хотите, чтобы перегрузка функций создавала нужный API, это сложно». Тот факт, что некоторые программисты неправильно используют языковую функцию, не является аргументом для избавления от этой функции.
Том
217

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

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

func main() {
    foo()
    foo(1)
    foo(1,2,3)
}
Ferguzz
источник
"функция на самом деле получает фрагмент любого типа, который вы укажете" как это так?
Аликс Аксель
3
в приведенном выше примере paramsэто кусочек целых
Ferguzz
76
Но только для однотипных параметров :(
Хуан де Паррас
15
@JuandeParras Ну, вы все еще можете использовать что-то вроде ... interface {} Я думаю.
maufl
5
С ... типа вы не передаете значение отдельных опций. Вместо этого используйте структуру. ... type удобен для значений, которые в противном случае пришлось бы помещать в массив перед вызовом.
user3523091 30.01.16
170

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

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})
Deamon
источник
12
Было бы здорово, если бы структуры могли иметь значения по умолчанию здесь; все, что пропускает пользователь, по умолчанию принимает значение nil для этого типа, которое может или не может быть подходящим аргументом по умолчанию для функции.
JSDW
41
@lytnus, я ненавижу разделять волосы, но поля, для которых значения опущены, по умолчанию устанавливаются в «нулевое значение» для их типа; ноль это другое животное. Если тип пропущенного поля окажется указателем, нулевое значение будет равно нулю.
burfl
2
@burfl да, за исключением того, что понятие «нулевое значение» абсолютно бесполезно для типов int / float / string, потому что эти значения имеют смысл, и поэтому вы не можете определить разницу, если значение было опущено в структуре или если нулевое значение было прошло намеренно.
тональный сигнал
3
@ ключик, я не согласен с тобой. Я просто педантично относился к приведенному выше утверждению, что значения, опущенные пользователем по умолчанию, равны «nil value for this type», что неверно. По умолчанию они имеют нулевое значение, которое может быть или не быть nil, в зависимости от того, является ли тип указателем.
Burfl
125

Для произвольного, потенциально большого количества необязательных параметров, хорошая идиома - использовать функциональные опции .

Для вашего типа 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
  }
}

Игровая площадка

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

type OptionFoobar func(*Foobar) error

Если вам нужны обязательные параметры, добавьте их в качестве первых аргументов конструктора перед переменным options.

Основными преимуществами функциональных опций являются:

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

Эта техника была придумана Робом Пайком, а также продемонстрирована Дейвом Чейни .

Deleplace
источник
15
Умно, но слишком сложно. Философия Go заключается в написании кода простым способом. Просто передайте структуру и проверьте значения по умолчанию.
user3523091 30.01.16
9
Просто FYI, первоначальный автор этой идиомы, по крайней мере, первый издатель, на которого ссылаются, это коммандер Роб Пайк, которого я считаю достаточно авторитетным для философии Го. Ссылка - commandcenter.blogspot.bg/2014/01/… . Также ищите «Простое сложное».
Петр Дончев
2
#JMTCW, но я считаю, что этот подход очень сложно рассуждать. Я бы предпочел передать структуру ценностей, свойства которых можно было бы использовать в func()случае необходимости, чем это искажает мой разум вокруг этого подхода. Всякий раз, когда мне приходится использовать этот подход, например, с библиотекой Echo, я обнаруживаю, что мой мозг попадает в кроличью нору абстракций. #fwiw
MikeSchinkel
спасибо, искал эту идиому. Может быть так же, как принятый ответ.
r --------- k
6

Нет - ни Для Go для программистов C ++ документы,

Go не поддерживает перегрузку функций и не поддерживает определяемые пользователем операторы.

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

Алекс Мартелли
источник
8
«Нет текущего плана для этого [необязательные параметры].» Йен Лэнс Тейлор, языковая команда Go. groups.google.com/group/golang-nuts/msg/030e63e7e681fd3e
peterSO
Пользовательские операторы не являются ужасным решением, поскольку они лежат в основе любой удобной математической библиотеки, такой как точечные или перекрестные продукты для линейной алгебры, часто используемой в трехмерной графике.
Джонатан
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

В итоге я использовал комбинацию структуры params и variadic args. Таким образом, мне не пришлось менять существующий интерфейс, который использовался несколькими сервисами, и мой сервис мог передавать дополнительные параметры по мере необходимости. Пример кода на игровой площадке Golang: https://play.golang.org/p/G668FA97Nu

Adriana
источник
3

Язык Go не поддерживает перегрузку методов, но вы можете использовать переменные аргументы так же, как необязательные параметры, также вы можете использовать интерфейс {} в качестве параметра, но это не лучший выбор.

BaSO4
источник
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переменную и обнуляющую ее.

Однако, есть небольшое преимущество: вызовы функций такого типа должны быть проще для встроенного компилятора, но я действительно не специалист.

VinGarcia
источник
это шаблон строителя
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})
}
nobar
источник
Вот некоторые комментарии к этому подходу: 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
}
user2133814
источник