Comment utiliser les fonctions variadiques dans Go

introduction

Unvariadic function est une fonction qui accepte zéro, une ou plusieurs valeurs comme argument unique. Bien que les fonctions variadiques ne soient pas le cas habituel, elles peuvent être utilisées pour rendre votre code plus propre et plus lisible.

Les fonctions variadiques sont plus courantes qu'il n'y paraît. La plus courante est la fonctionPrintln du packagefmt.

func Println(a ...interface{}) (n int, err error)

Unfunction avec un paramètre précédé d'un ensemble d'ellipses (...) est considéré comme une fonction variadique. Les points de suspension signifient que le paramètre fourni peut être zéro, une ou plusieurs valeurs. Pour le packagefmt.Println, il indique que le paramètrea est variadique.

Créons un programme qui utilise la fonctionfmt.Println et transmet zéro, une ou plusieurs valeurs:

print.go

package main

import "fmt"

func main() {
    fmt.Println()
    fmt.Println("one")
    fmt.Println("one", "two")
    fmt.Println("one", "two", "three")
}

La première fois que nous appelonsfmt.Println, nous ne transmettons aucun argument. La deuxième fois que nous appelonsfmt.Println, nous ne transmettons qu'un seul argument, avec la valeur deone. Ensuite, nous passonsone ettwo, et enfinone,two etthree.

Lançons le programme avec la commande suivante:

go run print.go

Nous verrons le résultat suivant:

Output
one
one two
one two three

La première ligne de la sortie est vide. En effet, nous n’avons transmis aucun argument la première fois quefmt.Println a été appelé. La deuxième fois que la valeur deone a été imprimée. Puisone ettwo, et enfinone,two etthree.

Maintenant que nous avons vu comment appeler une fonction variadique, voyons comment définir notre propre fonction variadique.

Définir une fonction variadique

Nous pouvons définir une fonction variadique en utilisant une ellipse (...) devant l'argument. Créons un programme qui accueille les personnes lorsque leurs noms sont envoyés à la fonction:

hello.go

package main

import "fmt"

func main() {
    sayHello()
    sayHello("Sammy")
    sayHello("Sammy", "Jessica", "Drew", "Jamie")
}

func sayHello(names ...string) {
    for _, n := range names {
        fmt.Printf("Hello %s\n", n)
    }
}

Nous avons créé une fonctionsayHello qui ne prend qu'un seul paramètre appelénames. Le paramètre est variadique, car nous mettons une ellipse (...) avant le type de données:...string. Cela indique à Go que la fonction peut accepter zéro, un ou plusieurs arguments.

La fonctionsayHello reçoit le paramètrenames en tant queslice. Puisque le type de données est unstring, le paramètrenames peut être traité comme une tranche de chaînes ([]string) à l'intérieur du corps de la fonction. Nous pouvons créer une boucle avec l'opérateurrange et parcourir la tranche de chaînes.

Si nous exécutons le programme, nous obtiendrons le résultat suivant:

OutputHello Sammy
Hello Sammy
Hello Jessica
Hello Drew
Hello Jamie

Notez que rien n'est imprimé pour la première fois, nous avons appelésayHello. Ceci est dû au fait que le paramètre variadique était unslice vide destring. Puisque nous parcourons la tranche, il n'y a rien à parcourir etfmt.Printf n'est jamais appelé.

Modifions le programme pour détecter qu’aucune valeur n’a été envoyée:

hello.go

package main

import "fmt"

func main() {
    sayHello()
    sayHello("Sammy")
    sayHello("Sammy", "Jessica", "Drew", "Jamie")
}

func sayHello(names ...string) {
    if len(names) == 0 {
        fmt.Println("nobody to greet")
        return
    }
    for _, n := range names {
        fmt.Printf("Hello %s\n", n)
    }
}

Maintenant, en utilisant unif statement, si aucune valeur n'est passée, la longueur denames sera0, et nous afficheronsnobody to greet:

Outputnobody to greet
Hello Sammy
Hello Sammy
Hello Jessica
Hello Drew
Hello Jamie

L'utilisation d'un paramètre variadique peut rendre votre code plus lisible. Créons une fonction qui associe des mots à un délimiteur spécifié. Nous allons d'abord créer ce programme sans fonction variadique pour montrer comment il se lirait:

join.go

package main

import "fmt"

func main() {
    var line string

    line = join(",", []string{"Sammy", "Jessica", "Drew", "Jamie"})
    fmt.Println(line)

    line = join(",", []string{"Sammy", "Jessica"})
    fmt.Println(line)

    line = join(",", []string{"Sammy"})
    fmt.Println(line)
}

func join(del string, values []string) string {
    var line string
    for i, v := range values {
        line = line + v
        if i != len(values)-1 {
            line = line + del
        }
    }
    return line
}

Dans ce programme, nous passons une virgule (,) comme délimiteur à la fonctionjoin. Ensuite, nous transmettons une tranche de valeurs à rejoindre. Voici la sortie:

OutputSammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy

Comme la fonction prend une tranche de chaîne comme paramètrevalues, nous avons dû envelopper tous nos mots dans une tranche lorsque nous avons appelé la fonctionjoin. Cela peut rendre le code difficile à lire.

Écrivons maintenant la même fonction, mais nous allons utiliser une fonction variadique:

join.go

package main

import "fmt"

func main() {
    var line string

    line = join(",", "Sammy", "Jessica", "Drew", "Jamie")
    fmt.Println(line)

    line = join(",", "Sammy", "Jessica")
    fmt.Println(line)

    line = join(",", "Sammy")
    fmt.Println(line)
}

func join(del string, values ...string) string {
    var line string
    for i, v := range values {
        line = line + v
        if i != len(values)-1 {
            line = line + del
        }
    }
    return line
}

Si nous exécutons le programme, nous pouvons voir que nous obtenons le même résultat que le programme précédent:

OutputSammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy

Alors que les deux versions de la fonctionjoin font exactement la même chose par programme, la version variadique de la fonction est beaucoup plus facile à lire lorsqu'elle est appelée.

Ordre d'argument variadique

Vous ne pouvez avoir qu'un seul paramètre variadique dans une fonction, et il doit s'agir du dernier paramètre défini dans la fonction. La définition de paramètres dans une fonction variadic dans un ordre autre que le dernier paramètre entraînera une erreur de compilation:

join.go

package main

import "fmt"

func main() {
    var line string

    line = join(",", "Sammy", "Jessica", "Drew", "Jamie")
    fmt.Println(line)

    line = join(",", "Sammy", "Jessica")
    fmt.Println(line)

    line = join(",", "Sammy")
    fmt.Println(line)
}

func join(values ...string, del string) string {
    var line string
    for i, v := range values {
        line = line + v
        if i != len(values)-1 {
            line = line + del
        }
    }
    return line
}

Cette fois, nous mettons le paramètrevalues en premier dans la fonctionjoin. Cela provoquera l'erreur de compilation suivante:

Output./join_error.go:18:11: syntax error: cannot use ... with non-final parameter values

Lors de la définition d'une fonction variadique, seul le dernier paramètre peut être variadique.

Arguments explosifs

Jusqu'ici, nous avons vu que nous pouvons passer zéro, une ou plusieurs valeurs à une fonction variadique. Cependant, il y aura des occasions où nous aurons une tranche de valeurs et que nous voudrons les envoyer à une fonction variadique.

Regardons notre fonctionjoin de la dernière section pour voir ce qui se passe:

join.go

package main

import "fmt"

func main() {
    var line string

    names := []string{"Sammy", "Jessica", "Drew", "Jamie"}

    line = join(",", names)
    fmt.Println(line)
}

func join(del string, values ...string) string {
    var line string
    for i, v := range values {
        line = line + v
        if i != len(values)-1 {
            line = line + del
        }
    }
    return line
}

Si nous exécutons ce programme, nous recevrons une erreur de compilation:

Output./join-error.go:10:14: cannot use names (type []string) as type string in argument to join

Même si la fonction variadique convertira le paramètre devalues ...string en une tranche de chaînes[]string, nous ne pouvons pas passer une tranche de chaînes comme argument. En effet, le compilateur attend des arguments discrets de chaînes.

Pour contourner ce problème, nous pouvonsexplode une tranche en la suffixant avec un ensemble d'ellipses (...) et en la transformant en arguments discrets qui seront passés à une fonction variadique:

join.go

package main

import "fmt"

func main() {
    var line string

    names := []string{"Sammy", "Jessica", "Drew", "Jamie"}

    line = join(",", names...)
    fmt.Println(line)
}

func join(del string, values ...string) string {
    var line string
    for i, v := range values {
        line = line + v
        if i != len(values)-1 {
            line = line + del
        }
    }
    return line
}

Cette fois, lorsque nous avons appelé la fonctionjoin, nous avons explosé la tranchenames en ajoutant des ellipses (...).

Cela permet au programme de s'exécuter comme prévu:

OutputSammy,Jessica,Drew,Jamie

Il est important de noter que nous pouvons toujours transmettre un zéro, un ou plusieurs arguments, ainsi qu'une tranche que nous explosons. Voici le code passant toutes les variations que nous avons vu jusqu'à présent:

join.go

package main

import "fmt"

func main() {
    var line string

    line = join(",", []string{"Sammy", "Jessica", "Drew", "Jamie"}...)
    fmt.Println(line)

    line = join(",", "Sammy", "Jessica", "Drew", "Jamie")
    fmt.Println(line)

    line = join(",", "Sammy", "Jessica")
    fmt.Println(line)

    line = join(",", "Sammy")
    fmt.Println(line)

}

func join(del string, values ...string) string {
    var line string
    for i, v := range values {
        line = line + v
        if i != len(values)-1 {
            line = line + del
        }
    }
    return line
}
OutputSammy,Jessica,Drew,Jamie
Sammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy

Nous savons maintenant comment passer zéro, un ou plusieurs arguments, ainsi qu'une tranche que nous explosons, à une fonction variadique.

Conclusion

Dans ce tutoriel, nous avons vu comment des fonctions variadiques peuvent rendre votre code plus propre. Bien que vous n’ayez pas toujours besoin de les utiliser, vous pouvez les trouver utiles:

  • Si vous constatez que vous créez une tranche temporaire juste pour passer à une fonction.

  • Lorsque le nombre de paramètres d’entrée est inconnu ou varie lorsqu’il est appelé.

  • Pour rendre votre code plus lisible.

Pour en savoir plus sur la création et l'appel de fonctions, vous pouvez lireHow to Define and Call Functions in Go.