Paramètres facultatifs dans Go?


464

Go peut-il avoir des paramètres facultatifs? Ou puis-je simplement définir deux fonctions avec le même nom et un nombre d'arguments différent?


Connexes: voici
icza

11
Google a pris une décision terrible, car parfois une fonction a un cas d'utilisation à 90% puis un cas d'utilisation à 10%. L'argument facultatif concerne ce cas d'utilisation de 10%. Par défaut sain signifie moins de code, moins de code signifie plus de maintenabilité.
Jonathan

Réponses:


431

Go n'a pas de paramètres facultatifs et ne prend pas en charge la surcharge de méthode :

L'envoi de méthode est simplifié s'il n'a pas besoin de faire de correspondance de type également. L'expérience d'autres langues nous a montré que le fait d'avoir une variété de méthodes portant le même nom mais des signatures différentes était parfois utile, mais cela pouvait aussi être déroutant et fragile dans la pratique. Faire correspondre uniquement par nom et nécessiter la cohérence des types était une décision de simplification majeure dans le système de types de Go.


58
Est-ce donc makeun cas particulier? Ou n'est-il même pas vraiment implémenté en fonction…
mk12

65
@ Mk12 makeest une construction de langage et les règles mentionnées ci-dessus ne s'appliquent pas. Voir cette question connexe .
nemo

7
rangeest le même cas que make, en ce sens
thiagowfx

14
Surcharges de méthode - Une excellente idée en théorie et excellente lorsqu'elle est bien mise en œuvre. Cependant, j'ai été témoin d'une surcharge indéchiffrable des ordures dans la pratique et je suis donc d'accord avec la décision de Google
trevorgk

118
Je vais sortir sur une branche et être en désaccord avec ce choix. Les concepteurs de langage ont essentiellement dit: "Nous avons besoin d'une surcharge de fonctions pour concevoir le langage que nous voulons, alors la marque, la plage et ainsi de suite sont essentiellement surchargées, mais si vous voulez une surcharge de fonctions pour concevoir l'API que vous voulez, eh bien, c'est difficile." Le fait que certains programmeurs utilisent mal une fonctionnalité de langue n'est pas un argument pour se débarrasser de la fonctionnalité.
Tom

217

Une bonne façon de réaliser quelque chose comme des paramètres facultatifs est d'utiliser des arguments variadiques. La fonction reçoit en fait une tranche du type que vous spécifiez.

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

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

"la fonction reçoit en fait une tranche du type que vous spécifiez" comment?
Alix Axel

3
dans l'exemple ci-dessus, paramsest une tranche d'ints
Ferguzz

76
Mais seulement pour le même type de paramètres :(
Juan de Parras

15
@JuandeParras Eh bien, vous pouvez toujours utiliser quelque chose comme ... interface {} je suppose.
maufl

5
Avec ... type, vous ne transmettez pas la signification des options individuelles. Utilisez une structure à la place. ... type est pratique pour les valeurs que vous auriez autrement dû mettre dans un tableau avant l'appel.
user3523091

170

Vous pouvez utiliser une structure qui inclut les paramètres:

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
Ce serait formidable si les structures pouvaient avoir des valeurs par défaut ici; tout ce que l'utilisateur omet est défini par défaut sur la valeur nil pour ce type, qui peut ou non être un argument par défaut approprié pour la fonction.
jsdw

41
@lytnus, je déteste diviser les cheveux, mais les champs pour lesquels des valeurs sont omises auraient par défaut la «valeur zéro» pour leur type; nul est un animal différent. Si le type du champ omis devait être un pointeur, la valeur zéro serait nulle.
burfl

2
@burfl oui, sauf que la notion de "valeur zéro" est absolument inutile pour les types int / float / string, car ces valeurs sont significatives et vous ne pouvez donc pas faire la différence si la valeur a été omise de la structure ou si la valeur zéro était passé intentionnellement.
keymone

3
@keymone, je ne suis pas en désaccord avec vous. J'étais simplement pédant sur la déclaration ci-dessus selon laquelle les valeurs omises par l'utilisateur sont par défaut la "valeur nulle pour ce type", ce qui est incorrect. Ils prennent par défaut la valeur zéro, qui peut être ou non nulle, selon que le type est un pointeur.
burfl

125

Pour un nombre arbitraire et potentiellement important de paramètres facultatifs, un idiome agréable consiste à utiliser les options fonctionnelles .

Pour votre type Foobar, écrivez d'abord un seul constructeur:

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
}

où chaque option est une fonction qui mute le Foobar. Ensuite, fournissez des moyens pratiques à votre utilisateur d'utiliser ou de créer des options standard, par exemple:

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

Terrain de jeux

Par souci de concision, vous pouvez donner un nom au type des options ( Playground ):

type OptionFoobar func(*Foobar) error

Si vous avez besoin de paramètres obligatoires, ajoutez-les comme premiers arguments du constructeur avant le variadic options.

Les principaux avantages de l' idiome des options fonctionnelles sont les suivants:

  • votre API peut évoluer au fil du temps sans casser le code existant, car la signature du constructeur reste la même lorsque de nouvelles options sont nécessaires.
  • il permet au cas d'utilisation par défaut d'être le plus simple: aucun argument du tout!
  • il permet un contrôle précis de l'initialisation des valeurs complexes.

Cette technique a été inventée par Rob Pike et également démontrée par Dave Cheney .



15
Intelligent, mais trop compliqué. La philosophie de Go est d'écrire du code de manière simple. Passez juste une structure et testez les valeurs par défaut.
user3523091

9
Juste FYI, l'auteur original de cet idiome, au moins le premier éditeur référencé, est le commandant Rob Pike, que je considère suffisamment autoritaire pour la philosophie de Go. Lien - commandcenter.blogspot.bg/2014/01/… . Recherchez également "Simple is complex".
Petar Donchev

2
#JMTCW, mais je trouve cette approche très difficile à raisonner. Je préférerais de loin passer dans une structure de valeurs, dont les propriétés pourraient être func()s si besoin est, que de plier mon cerveau autour de cette approche. Chaque fois que je dois utiliser cette approche, comme avec la bibliothèque Echo, je trouve mon cerveau pris dans le trou du lapin des abstractions. #fwiw
MikeSchinkel

merci, cherchait cet idiome. Pourrait aussi bien être là-haut que la réponse acceptée.
r --------- k


6

Non - non plus. Selon les documents des programmeurs Go for C ++ ,

Go ne prend pas en charge la surcharge de fonctions et ne prend pas en charge les opérateurs définis par l'utilisateur.

Je ne trouve pas de déclaration aussi claire que les paramètres facultatifs ne sont pas pris en charge, mais ils ne sont pas non plus pris en charge.


8
"Il n'y a pas de plan actuel pour ce [paramètres facultatifs]." Ian Lance Taylor, équipe linguistique Go. groups.google.com/group/golang-nuts/msg/030e63e7e681fd3e
peterSO

Aucun opérateur défini par l'utilisateur n'est une décision terrible, car c'est le cœur de toute bibliothèque mathématique lisse, comme les produits scalaires ou les produits croisés pour l'algèbre linéaire, souvent utilisés dans les graphiques 3D.
Jonathan

4

Vous pouvez l'encapsuler assez bien dans une fonction similaire à celle ci-dessous.

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
}

Dans cet exemple, l'invite par défaut a deux points et un espace devant elle. . .

: 

. . . cependant, vous pouvez remplacer cela en fournissant un paramètre à la fonction d'invite.

prompt("Input here -> ")

Cela se traduira par une invite comme ci-dessous.

Input here ->

3

J'ai fini par utiliser une combinaison d'une structure de paramètres et d'arguments variadiques. De cette façon, je n'ai pas eu à changer l'interface existante qui était consommée par plusieurs services et mon service a pu passer des paramètres supplémentaires selon les besoins. Exemple de code dans la cour de récréation de Golang: https://play.golang.org/p/G668FA97Nu


3

Le langage Go ne prend pas en charge la surcharge de méthode, mais vous pouvez utiliser des arguments variadiques tout comme les paramètres facultatifs, vous pouvez également utiliser l'interface {} comme paramètre, mais ce n'est pas un bon choix.


2

Je suis un peu en retard, mais si vous aimez une interface fluide, vous pouvez concevoir vos setters pour des appels en chaîne comme celui-ci:

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
}

Et puis appelez-le comme ceci:

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

Ceci est similaire à l' idiome des options fonctionnelles présenté sur la réponse @Ripounet et bénéficie des mêmes avantages mais présente certains inconvénients:

  1. Si une erreur se produit, elle n'interrompra pas immédiatement, ainsi, elle serait légèrement moins efficace si vous attendez de votre constructeur qu'il signale souvent des erreurs.
  2. Vous devrez passer une ligne à déclarer une errvariable et à la mettre à zéro.

Il y a cependant un petit avantage possible, ce type d'appels de fonction devrait être plus facile pour le compilateur à s'aligner mais je ne suis vraiment pas un spécialiste.


ceci est un modèle de constructeur
UmNyobe

2

Vous pouvez passer des paramètres nommés arbitraires avec une carte.

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

Voici quelques commentaires sur cette approche: reddit.com/r/golang/comments/546g4z/…
nobar


0

Une autre possibilité serait d'utiliser une structure qui avec un champ pour indiquer si elle est valide. Les types nuls de sql tels que NullString sont pratiques. C'est bien de ne pas avoir à définir votre propre type, mais si vous avez besoin d'un type de données personnalisé, vous pouvez toujours suivre le même modèle. Je pense que l'option-ness est claire à partir de la définition de la fonction et il y a un minimum de code ou d'effort supplémentaire.

Par exemple:

func Foo(bar string, baz sql.NullString){
  if !baz.Valid {
        baz.String = "defaultValue"
  }
  // the rest of the implementation
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.