Parâmetros opcionais no Go?


464

O Go pode ter parâmetros opcionais? Ou posso apenas definir duas funções com o mesmo nome e um número diferente de argumentos?


Relacionado: é assim que pode ser feito para impor parâmetros obrigatórios ao usar variadic como parâmetros opcionais: É possível disparar um erro de tempo de compilação com a biblioteca personalizada em golang?
icza 29/08/19

11
O Google tomou uma decisão terrível, porque às vezes uma função tem um caso de uso de 90% e, em seguida, um caso de uso de 10%. O argumento opcional é para esse caso de uso de 10%. Padrões normais significa menos código, menos código significa mais facilidade de manutenção.
Jonathan

Respostas:


431

O Go não possui parâmetros opcionais nem suporta sobrecarga de método :

O envio de métodos é simplificado se não for necessário fazer a correspondência de tipos também. A experiência com outros idiomas nos disse que ter uma variedade de métodos com o mesmo nome, mas com assinaturas diferentes, era ocasionalmente útil, mas também poderia ser confuso e frágil na prática. Combinar apenas pelo nome e exigir consistência nos tipos foi uma decisão importante e simplificadora no sistema de tipos da Go.


58
É makeum caso especial, então? Ou não é mesmo realmente implementado como uma função ...
MK12

65
@ Mk12 makeé uma construção de linguagem e as regras mencionadas acima não se aplicam. Veja esta pergunta relacionada .
nemo

7
rangeé o mesmo caso make, nesse sentido
thiagowfx 30/07

14
Sobrecargas de método - uma ótima idéia em teoria e excelente quando bem implementada. No entanto tenho testemunhado sobrecarga indecifrável lixo na prática e, portanto, concordar com o Google decisão
trevorgk

118
Eu vou sair em um membro e discordar desta escolha. Os designers de linguagem disseram basicamente: "Precisamos de sobrecarga de funções para projetar a linguagem que queremos, portanto, make, range e assim por diante são essencialmente sobrecarregados, mas se você deseja sobrecarga de funções para projetar a API que deseja, bem, isso é difícil". O fato de alguns programadores usarem incorretamente um recurso de linguagem não é um argumento para se livrar dele.
Tom

216

Uma boa maneira de obter algo como parâmetros opcionais é usar argumentos variados. A função realmente recebe uma fatia do tipo que você especificar.

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

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

A função "na verdade recebe uma fatia do tipo que você especificar", como assim?
Alix Axel

3
No exemplo acima, paramsé uma fatia de ints
Ferguzz

76
Mas apenas para o mesmo tipo de parâmetro :(
Juan de Parras

15
@JuandeParras Bem, você ainda pode usar algo como ... interface {} eu acho.
maufl

5
Com ... tipo, você não está transmitindo o significado das opções individuais. Use uma estrutura em seu lugar. ... type é útil para valores que você teria que colocar em uma matriz antes da chamada.
precisa saber é o seguinte

170

Você pode usar uma estrutura que inclui os parâmetros:

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
Seria ótimo se as estruturas pudessem ter valores padrão aqui; tudo o que o usuário omite é padronizado com o valor nulo para esse tipo, que pode ou não ser um argumento padrão adequado para a função.
jsdw

41
@lytnus, detesto dividir os cabelos, mas os campos cujos valores são omitidos serão padronizados como o 'valor zero' para o seu tipo; nada é um animal diferente. Se o tipo do campo omitido for um ponteiro, o valor zero será nulo.
burfl

2
@burfl sim, exceto a noção de "valor zero" é absolutamente inútil para os tipos int / float / string, porque esses valores são significativos e, portanto, você não pode dizer a diferença se o valor foi omitido da estrutura ou se o valor zero foi passou intencionalmente.
keymone

3
@ keymone, eu não discordo de você. Eu estava apenas sendo pedante sobre a afirmação acima de que os valores omitidos pelo usuário padrão para o "valor nulo para esse tipo", o que está incorreto. Eles assumem o valor zero, que pode ou não ser nulo, dependendo de o tipo ser um ponteiro.
burfl

125

Para um número arbitrário e potencialmente grande de parâmetros opcionais, um bom idioma é usar as opções funcionais .

Para seu tipo Foobar, primeiro escreva apenas um construtor:

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
}

onde cada opção é uma função que modifica o Foobar. Em seguida, forneça maneiras convenientes para o usuário usar ou criar opções padrão, por exemplo:

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

Parque infantil

Por uma questão de concisão, você pode dar um nome ao tipo de opções ( Playground ):

type OptionFoobar func(*Foobar) error

Se você precisar de parâmetros obrigatórios, inclua-os como primeiros argumentos do construtor antes da variável options.

Os principais benefícios do idioma das opções funcionais são:

  • sua API pode crescer com o tempo sem quebrar o código existente, porque a assinatura do construtor permanece a mesma quando novas opções são necessárias.
  • ele permite que o caso de uso padrão seja o mais simples: sem argumentos!
  • fornece controle fino sobre a inicialização de valores complexos.

Essa técnica foi cunhada por Rob Pike e também demonstrada por Dave Cheney .



15
Inteligente, mas muito complicado. A filosofia do Go é escrever código de maneira direta. Apenas passe uma estrutura e teste os valores padrão.
precisa saber é o seguinte

9
Apenas para sua informação, o autor original desse idioma, pelo menos o primeiro editor mencionado, é o comandante Rob Pike, a quem considero autoritário o suficiente para a filosofia Go. Link - commandcenter.blogspot.bg/2014/01/… . Procure também "Simples é complicado".
Petar Donchev

2
#JMTCW, mas acho essa abordagem muito difícil de raciocinar. Eu preferiria passar uma estrutura de valores, cujas propriedades poderiam ser func()s, se necessário, do que dobrar meu cérebro em torno dessa abordagem. Sempre que tenho que usar essa abordagem, como na biblioteca Echo, encontro meu cérebro preso na toca do coelho das abstrações. #wiw #
MikeSchinkel 20/03/19

obrigado, estava procurando por esse idioma. Poderia muito bem estar lá em cima como a resposta aceita.
r --------- k


6

Não - nem. De acordo com os documentos dos programadores do Go for C ++ ,

O Go não suporta sobrecarga de função e não suporta operadores definidos pelo usuário.

Não consigo encontrar uma declaração igualmente clara de que parâmetros opcionais não são suportados, mas eles também não são suportados.


8
"Não existe um plano atual para este [parâmetros opcionais]." Ian Lance Taylor, equipe de idiomas Go. groups.google.com/group/golang-nuts/msg/030e63e7e681fd3e
peterSO

Nenhum operador definido pelo usuário é uma decisão terrível, pois é o núcleo por trás de qualquer biblioteca matemática, como produtos de ponto ou produtos cruzados para álgebra linear, frequentemente usados ​​em gráficos 3D.
Jonathan

4

Você pode encapsular isso muito bem em uma função semelhante ao que está abaixo.

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
}

Neste exemplo, o prompt por padrão possui dois pontos e um espaço à sua frente. . .

: 

. . . no entanto, você pode substituir isso fornecendo um parâmetro para a função prompt.

prompt("Input here -> ")

Isso resultará em um prompt como abaixo.

Input here ->

3

Acabei usando uma combinação de uma estrutura de parâmetros e argumentos variáveis. Dessa forma, não precisei alterar a interface existente consumida por vários serviços e meu serviço conseguiu passar parâmetros adicionais conforme necessário. Código de exemplo no playground de golang: https://play.golang.org/p/G668FA97Nu


3

O idioma Go não suporta sobrecarga de método, mas você pode usar argumentos variados, assim como parâmetros opcionais, também pode usar a interface {} como parâmetro, mas não é uma boa escolha.


2

Estou um pouco atrasado, mas se você gosta de uma interface fluente, pode projetar seus setters para chamadas encadeadas como esta:

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 então chame assim:

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

Isso é semelhante ao idioma das opções funcionais apresentado na resposta do @Ripounet e possui os mesmos benefícios, mas tem algumas desvantagens:

  1. Se ocorrer um erro, ele não será interrompido imediatamente, portanto, seria um pouco menos eficiente se você espera que seu construtor relate erros com frequência.
  2. Você terá que gastar uma linha declarando uma errvariável e zerando-a.

Há, no entanto, uma pequena vantagem possível: esse tipo de chamada de função deve ser mais fácil para o compilador alinhar, mas eu realmente não sou especialista.


este é um padrão do construtor
UmNyobe

2

Você pode passar parâmetros nomeados arbitrários com um mapa.

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

Aqui estão alguns comentários sobre essa abordagem: reddit.com/r/golang/comments/546g4z/…
nobar 01/08/19


0

Outra possibilidade seria usar um struct que com um campo para indicar se é válido. Os tipos nulos do sql, como NullString, são convenientes. É bom não ter que definir seu próprio tipo, mas caso você precise de um tipo de dados personalizado, sempre poderá seguir o mesmo padrão. Acho que a opcionalidade é clara na definição da função e há um mínimo de código ou esforço extra.

Como um exemplo:

func Foo(bar string, baz sql.NullString){
  if !baz.Valid {
        baz.String = "defaultValue"
  }
  // the rest of the implementation
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.