Table of Contents
URL: https://www.progressiverobot.com/how-to-use-variadic-functions-in-go-pt/
Introdução
Uma _função variadic_ é uma função que aceita zero, um, ou mais valores como um único argumento. Embora as funções variadic não sejam o caso comum, elas podem ser usadas para tornar seu código mais limpo e mais legível.
As funções variadic são mais comuns do que parecem. A função mais comum é a Println do pacote fmt.
func Println(a ...interface{}) (n int, err error)
Uma função com um parâmetro antecedido por um sinal de reticências (...) é considerado como uma função variadic. As reticências significam que o parâmetro fornecido pode ser zero, um, ou mais valores. Para o pacote fmt.Println, elas declaram que o parâmetro a é uma função variadic.
Vamos criar um programa que usa a função fmt.Println e envia valores zero, um ou mais:
[label print.go]
package main
import "fmt"
func main() {
fmt.Println()
fmt.Println("one")
fmt.Println("one", "two")
fmt.Println("one", "two", "three")
}
A primeira vez que chamamos fmt.Println, não enviamos nenhum argumento. A segunda vez que chamamos fmt.Println, enviamos um único argumento apenas, com o valor de one. Então, enviamos one e two e, finalmente, one, two e three.
Vamos executar o programa com o seguinte comando:
go run print.go
Veremos o seguinte resultado:
[secondary_label Output]
one
one two
one two three
A primeira linha do resultado está em branco. Isso se deve ao fato de que não enviamos nenhum argumento na primeira vez que o fmt.Println foi chamado. Na segunda vez que chamamos a função, o valor one foi impresso. Em seguida, one e two e, finalmente, one, two e three.
Agora que vimos como chamar uma função variadic, vamos examinar como podemos definir nossa própria função variadic.
Definindo uma função variadic
Podemos definir uma função variadic usando reticências (...) na frente do argumento. Vamos criar um programa que cumprimenta as pessoas quando seus nomes são enviados para a função:
[label 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)
}
}
Criamos uma função sayHello que usa um único parâmetro chamado names. O parâmetro é variadic, uma vez que colocamos reticências (...) antes do tipo de dados: ...string. Isso diz ao Go que a função pode aceitar zero, um, ou muitos argumentos.
Se executarmos o programa, vamos receber o seguinte resultado:
[secondary_label Output]
Hello Sammy
Hello Sammy
Hello Jessica
Hello Drew
Hello Jamie
Note que nada foi impresso na primeira vez que chamamos o sayHello. Isso acontece porque o parâmetro variadic era uma slice vazia da string. Como estamos formando loops pela fatia, não há nada para iterar até o fim e, dessa forma a função fmt.Printf nunca é chamada.
Vamos modificar o programa para detectar se nenhum valor foi enviado:
[label 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)
}
}
Agora, usando uma instrução if, se nenhum valor for enviado, o tamanho de names será 0 e vamos imprimir nobody to greet:
[secondary_label Output]
nobody to greet
Hello Sammy
Hello Sammy
Hello Jessica
Hello Drew
Hello Jamie
O uso de um parâmetro variadic pode tornar seu código mais legível. Vamos criar uma função que junta as palavras com um delimitador específico. Criaremos esse programa sem uma função variadic primeiro para mostrar como ele seria lido:
[label 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
}
Neste programa, estamos enviando uma vírgula (,) como o delimitador para a função join. Depois, enviamos uma fatia de valores para unir. Aqui está o resultado:
[secondary_label Output]
Sammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy
Como a função aceita uma fatia de string como o parâmetro values, tivemos que empacotar todas nossas palavras em uma fatia quando chamamos a função join. Isso pode tornar o código difícil de ler.
Agora, vamos escrever a mesma função, mas usaremos uma função variadic:
[label 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
}
Se executarmos o programa, podemos verificar que obtemos o mesmo resultado do programa anterior:
[secondary_label Output]
Sammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy
Embora ambas as versões da função join façam a mesma coisa em termos de programação, a versão variadic da função é muito mais fácil de ler quando ela está sendo chamada.
Ordem do argumento variadic
Você só pode ter um parâmetro variadic em uma função e ele deverá ser o último parâmetro definido na função. Definir parâmetros em uma função variadic em qualquer ordem que não seja o último parâmetro resultará em um erro de compilação:
[label 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
}
Desta vez, colocamos primeiro o parâmetro values na função join. Isso causará o seguinte erro de compilação:
[secondary_label Output]
./join_error.go:18:11: syntax error: cannot use ... with non-final parameter values
Ao definir qualquer função variadic, apenas o último parâmetro pode ser variadic.
Destacando argumentos
Até agora, vimos que podemos enviar zero, um, ou mais valores para uma função variadic. No entanto, haverá ocasiões em que teremos uma fatia de valores, os quais vamos querer enviar para uma função variadic.
Vamos examinar nossa função join da última seção para ver o que acontece:
[label 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
}
Se executarmos este programa, vamos receber um erro de compilação:
[secondary_label Output]
./join-error.go:10:14: cannot use names (type []string) as type string in argument to join
Embora a função variadic converta o parâmetro de values ...string em uma fatia de strings []string, não podemos enviar uma fatia de strings como argumento. Isso acontece porque o compilador espera argumentos discretos de strings.
Para contornar esse problema, podemos _destacar_ uma fatia, adicionando a ela um sufixo na forma de reticências (...) e transformando-a em argumentos discretos que serão enviados para uma função variadic:
[label 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
}
Desta vez, quando chamamos a função join, destacamos a fatia names anexando reticências (...).
Isso permite que o programa agora seja executado conforme esperado:
[secondary_label Output]
Sammy,Jessica,Drew,Jamie
É importante notar que ainda poderemos enviar zero, um, ou mais argumentos, assim como uma fatia que destacarmos. Aqui está o código que envia todas as variações que vimos até agora:
[label 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
}
[secondary_label Output]
Sammy,Jessica,Drew,Jamie
Sammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy
Agora, sabemos como enviar zero, um, ou muitos argumentos, além de uma fatia que destacarmos para uma função variadic.
Conclusão
Neste tutorial, vimos como as funções variadic podem tornar seu código mais limpo. Embora nem sempre será necessário usá-las, você pode achá-las uteis:
- Se achar que vai criar uma fatia temporária apenas para enviar para uma função.
- Quando o número de parâmetros de entrada são desconhecidos ou irão variar quando chamados.
- Para tornar seu código mais legível.
Para aprender mais sobre como criar e chamar funções, você pode ler o artigo sobre Como definir e chamar funções em Go.