Parámetros opcionales en Go?

464

¿Puede ir tener parámetros opcionales? ¿O puedo simplemente definir dos funciones con el mismo nombre y un número diferente de argumentos?

devyn
fuente
Relacionado: así es como se puede hacer cumplir los parámetros obligatorios cuando se usan variables como parámetros opcionales: ¿Es posible activar un error de tiempo de compilación con una biblioteca personalizada en golang?
icza
11
Google tomó una decisión terrible, porque a veces una función tiene un caso de uso del 90% y luego un caso de uso del 10%. El argumento opcional es para ese caso de uso del 10%. Los valores predeterminados correctos significan menos código, menos código significa más mantenibilidad.
Jonathan

Respuestas:

431

Go no tiene parámetros opcionales ni admite la sobrecarga de métodos :

El envío de métodos se simplifica si no es necesario que también coincida con el tipo. La experiencia con otros idiomas nos dijo que tener una variedad de métodos con el mismo nombre pero con firmas diferentes ocasionalmente era útil, pero que también podría ser confuso y frágil en la práctica. Coincidir solo por nombre y requerir consistencia en los tipos fue una decisión simplificadora importante en el sistema de tipos de Go.

Andrew Hare
fuente
58
¿Es makeun caso especial, entonces? ¿O ni siquiera se implementa realmente como una función ...
Mk12
65
@ Mk12 makees una construcción de lenguaje y las reglas mencionadas anteriormente no se aplican. Ver esta pregunta relacionada .
nemo
77
rangees el mismo caso que make, en ese sentido
thiagowfx
14
Sobrecarga de métodos: una gran idea en teoría y excelente cuando se implementa bien. Sin embargo, he sido testigo de sobrecarga indescifrable basura en la práctica y por lo tanto de acuerdo con la decisión de Google
trevorgk
118
Voy a arriesgarme y no estoy de acuerdo con esta elección. Los diseñadores de lenguaje básicamente han dicho: "Necesitamos una sobrecarga de funciones para diseñar el lenguaje que queremos, por lo que la fabricación, el rango, etc. están esencialmente sobrecargados, pero si desea que la sobrecarga de funciones diseñe la API que desea, bueno, eso es difícil". El hecho de que algunos programadores hagan mal uso de una función del lenguaje no es un argumento para deshacerse de la función.
Tom
217

Una buena manera de lograr algo como parámetros opcionales es usar argumentos variadic. La función en realidad recibe una porción del tipo que especifique.

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

func main() {
    foo()
    foo(1)
    foo(1,2,3)
}
Ferguzz
fuente
"la función realmente recibe una porción del tipo que especifique" ¿cómo?
Alix Axel
3
en el ejemplo anterior, paramses una porción de
entradas
76
Pero solo para el mismo tipo de parámetros :(
Juan de Parras
15
@JuandeParras Bueno, todavía puedes usar algo como ... interfaz {} supongo.
maufl
55
Con ... escriba no está transmitiendo el significado de las opciones individuales. Use una estructura en su lugar. ... type es útil para valores que de lo contrario tendría que poner en una matriz antes de la llamada.
user3523091
170

Puede usar una estructura que incluya los 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})
deamon
fuente
12
Sería genial si las estructuras pudieran tener valores predeterminados aquí; todo lo que omita el usuario está predeterminado en el valor nulo para ese tipo, que puede o no ser un argumento predeterminado adecuado para la función.
jsdw
41
@lytnus, odio dividir los pelos, pero los campos para los que se omiten los valores pasarían por defecto al 'valor cero' para su tipo; nil es un animal diferente. Si el tipo del campo omitido es un puntero, el valor cero sería nulo.
burfl
2
@burfl sí, excepto que la noción de "valor cero" es absolutamente inútil para los tipos int / float / string, porque esos valores son significativos y, por lo tanto, no puede distinguir si el valor se omitió de la estructura o si el valor cero fue aprobado intencionalmente.
Keymone
3
@keymone, no estoy en desacuerdo contigo. Simplemente estaba siendo pedante sobre la declaración anterior de que los valores omitidos por el usuario por defecto son el "valor nulo para ese tipo", que es incorrecto. El valor predeterminado es cero, que puede ser nulo o no, dependiendo de si el tipo es un puntero.
burfl
125

Para un número arbitrario y potencialmente grande de parámetros opcionales, un buen modismo es usar opciones funcionales .

Para su tipo Foobar, primero escriba solo un constructor:

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
}

donde cada opción es una función que muta el Foobar. Luego, proporcione formas convenientes para que su usuario use o cree opciones estándar, por ejemplo:

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

Patio de recreo

Para ser concisos, puede dar un nombre al tipo de opciones ( Zona de juegos ):

type OptionFoobar func(*Foobar) error

Si necesita parámetros obligatorios, agréguelos como primeros argumentos del constructor antes de la variable options .

Los principales beneficios de la idioma de opciones funcionales son:

  • su API puede crecer con el tiempo sin romper el código existente, porque la firma del constructor permanece igual cuando se necesitan nuevas opciones.
  • permite que el caso de uso predeterminado sea el más simple: ¡sin argumentos!
  • Proporciona un control preciso sobre la inicialización de valores complejos.

Esta técnica fue acuñada por Rob Pike y también demostrada por Dave Cheney .

Deleplace
fuente
15
Inteligente, pero demasiado complicado. La filosofía de Go es escribir código de una manera directa. Simplemente pase una estructura y pruebe los valores predeterminados.
user3523091
99
Solo para su información, el autor original de este idioma, al menos el primer editor al que se hace referencia, es el comandante Rob Pike, a quien considero lo suficientemente autoritario para la filosofía Go. Enlace: commandcenter.blogspot.bg/2014/01/… . También busque "Simple es complicado".
Petar Donchev
2
#JMTCW, pero este enfoque me parece muy difícil de razonar. Preferiría transmitir una estructura de valores, cuyas propiedades podrían ser func()s si fuera necesario, que doblar mi cerebro en torno a este enfoque. Cada vez que tengo que usar este enfoque, como en la biblioteca Echo, encuentro que mi cerebro queda atrapado en la madriguera de las abstracciones. #fwiw
MikeSchinkel
gracias, estaba buscando este idioma. Bien podría estar allí como la respuesta aceptada.
r --------- k
6

No, tampoco Según los documentos de Go para programadores de C ++ ,

Go no admite la sobrecarga de funciones y no admite operadores definidos por el usuario.

No puedo encontrar una declaración igualmente clara de que los parámetros opcionales no son compatibles, pero tampoco son compatibles.

Alex Martelli
fuente
8
"No hay un plan actual para esto [parámetros opcionales]". Ian Lance Taylor, equipo de idiomas de Go. groups.google.com/group/golang-nuts/msg/030e63e7e681fd3e
peterSO el
Ningún operador definido por el usuario es una decisión terrible, ya que es el núcleo detrás de cualquier biblioteca matemática ingeniosa, como productos de punto o productos cruzados para álgebra lineal, a menudo utilizados en gráficos 3D.
Jonathan
4

Puede encapsular esto bastante bien en una función similar a la que se muestra a continuación.

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
}

En este ejemplo, la solicitud por defecto tiene dos puntos y un espacio delante. . .

: 

. . . sin embargo, puede anular eso proporcionando un parámetro a la función de solicitud.

prompt("Input here -> ")

Esto dará como resultado un mensaje como el siguiente.

Input here ->
Jam Risser
fuente
3

Terminé usando una combinación de una estructura de parámetros y argumentos variados. De esta manera, no tuve que cambiar la interfaz existente que fue consumida por varios servicios y mi servicio pudo pasar parámetros adicionales según fue necesario. Código de muestra en el patio de juegos de Golang: https://play.golang.org/p/G668FA97Nu

Adriana
fuente
3

Go language no admite la sobrecarga de métodos, pero puede usar argumentos variados al igual que los parámetros opcionales, también puede usar la interfaz {} como parámetro, pero no es una buena opción.

BaSO4
fuente
2

Llego un poco tarde, pero si te gusta la interfaz fluida, puedes diseñar tus setters para llamadas encadenadas 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
}

Y luego llámalo así:

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

Esto es similar al lenguaje de opciones funcionales presentado en la respuesta @Ripounet y disfruta de los mismos beneficios, pero tiene algunos inconvenientes:

  1. Si se produce un error, no se cancelará de inmediato, por lo tanto, sería un poco menos eficiente si espera que su constructor informe errores con frecuencia.
  2. Tendrá que pasar una línea declarando una errvariable y poniéndola a cero.

Sin embargo, existe una pequeña ventaja posible, este tipo de llamadas a funciones debería ser más fácil para el compilador en línea, pero realmente no soy un especialista.

VinGarcia
fuente
este es un patrón de construcción
UmNyobe
2

Puede pasar parámetros nombrados arbitrarios con un 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})
}
sin bar
fuente
Aquí hay algunos comentarios sobre este enfoque: reddit.com/r/golang/comments/546g4z/…
nobar el
0

Otra posibilidad sería utilizar una estructura que con un campo para indicar si es válida. Los tipos nulos de sql como NullString son convenientes. Es bueno no tener que definir su propio tipo, pero en caso de que necesite un tipo de datos personalizado, siempre puede seguir el mismo patrón. Creo que la opcionalidad está clara en la definición de la función y hay un mínimo de código o esfuerzo adicional.

Como ejemplo:

func Foo(bar string, baz sql.NullString){
  if !baz.Valid {
        baz.String = "defaultValue"
  }
  // the rest of the implementation
}
usuario2133814
fuente