Tutorial

Cómo usar funciones variádicas en Go

Published on February 7, 2020
Español
Cómo usar funciones variádicas en Go

Introducción

Una función variádica acepta cero, uno o más valores como único argumento. Si bien las funciones variádicas son atípicas, pueden utilizarse para que su código sea más limpio y legible.

Las funciones variádicas son más comunes de lo que parecen. La más común es la función Println del paquete fmt.

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

Una función con un parámetro precedido por una serie de elipses (...) se considera como una función variádica. La ellipsis implica que el parámetro que se proporciona puede ser cero, uno o más valores. En el paquete fmt.Println, se indica que el parámetro a es variádico.

Crearemos un programa que utilice la función fmt.Println y que pase cero, uno o más valores:

print.go
package main

import "fmt"

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

La primera vez que invocamos fmt.Println, no pasamos argumentos. La segunda vez que invocamos fmt.Println solo se ejecuta un único argumento con el valor de one. Luego pasaremos one y two, y por último one, two y three.

Ejecutaremos el programa con el siguiente comando:

  1. go run print.go

Veremos el siguiente resultado:

Output
one one two one two three

La primera línea del resultado está vacía. Esto se debe a que no aprobamos argumentos la primera vez que se invocó a fmt.Println. La segunda vez se imprime el valor one. Luego se imprime one y two, y por último one, two y three.

Ahora que vimos la forma de invocar una función variádica, veremos la forma de definir una propia.

Definir una función variádica

Podemos definir una función variádica usando puntos suspensivos (...) en frente del argumento. Crearemos un programa que salude a la gente cuando se envíen sus nombres a la función:

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

Creamos la función sayHello que solo toma un único parámetro llamado names. El parámetro es variádico, ya que disponemos puntos suspensivos (...) antes del tipo de datos: ...string. Esto indica a Go que la función puede aceptar cero, uno o muchos argumentos.

La función sayHello recibe el parámetro names como un slice. Debido a que el tipo de datos es un string, el parámetro names puede tratarse como un segmento de cadenas (​​​​​​[]string​​​​) dentro del cuerpo de la función. Podemos crear un bucle con el operador range e iterar el segmento de cadenas.

Si ejecutamos el programa, obtendremos el siguiente resultado:

Output
Hello Sammy Hello Sammy Hello Jessica Hello Drew Hello Jamie

Observe que no hubo impresiones la primera vez que invocamos a sayHello. Esto se debe a que el parámetro variádico fue un slice vacío de string. Debido a que repetimos el segmento, no hay elementos para iterar y nunca se invoca a fmt.Printf.

Modificaremos el programa para detectar que no se enviaron valores:

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

Ahora, usando una instrucción if, si no se pasan valores la extensión de names será 0 e imprimiremos nobody to greet:

Output
nobody to greet Hello Sammy Hello Sammy Hello Jessica Hello Drew Hello Jamie

El uso de un parámetro de variable puede hacer que su código sea más legible. Crearemos una función que una las palabras con un delimitador especificado. Primero, crearemos este programa sin una función de variable para mostrar cómo se leería:

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
}

En este programa, pasaremos una coma (,) como delimitador a la función join. Luego, pasaremos un segmento de valores que se unirán. Este es el resultado:

Output
Sammy,Jessica,Drew,Jamie Sammy,Jessica Sammy

Debido a que la función toma un segmento de cadena como parámetro values, debimos ajustar todas nuestras palabras en un segmento cuando invocamos la función join. Esto puede dificultar la lectura del código.

Ahora, escribiremos la misma función, pero usaremos una función variádica:

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 ejecutamos el programa, podemos ver que obtenemos el mismo resultado que el programa anterior:

Output
Sammy,Jessica,Drew,Jamie Sammy,Jessica Sammy

Si bien ambas versiones de la función join hacen exactamente lo mismo mediante programación, la versión variádica de la función es mucho más fácil de leer cuando se invoca.

Orden de argumentos variádicos

Solo puede disponer de un parámetro variádico en una función y debe ser el último parámetro definido en esta. La definición de parámetros en una función variádica en cualquier orden que no sea el último parámetro dará como resultado un error de compilación:

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
}

Esta vez, disponemos el parámetro values primero en la función join. Esto provocará el siguiente error de compilación:

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

Cuando se define cualquier función variádica, solo el último parámetro puede ser variádico.

Expandir argumentos

Hasta ahora, vimos que podemos pasar cero, uno o más valores a una función variádica. Sin embargo, habrá ocasiones en que dispongamos de un segmento de valores y queramos enviarlos a una función variádica.

Veamos nuestra función join de la última sección para ver qué sucede:

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 ejecutamos este programa, veremos un error de compilación:

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

Aunque la función variable convertirá el parámetro values ...string en un segmento de cadenas strings[], no podemos pasar un segmento de cadenas como argumento. Esto se debe a que el compilador espera argumentos discretos de cadenas.

Para hallar una solución a esto, podemos explandir un factor logístico agregándole como sufijo un conjunto de puntos suspensivos (...) y convertirlo en argumentos discretos que se pasarán a una función variádica:

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
}

Esta vez, cuando llamamos a la función join, expandimos el segmento names agregando puntos suspensivos (...).

Esto permite que el programa ahore funcione como se espera:

Output
Sammy,Jessica,Drew,Jamie

Es importante tener en cuenta que de todas formas podemos pasar cero, uno o más argumentos y un segmento que expandamos. A continuación, se ofrece el código que pasa todas las variaciones que hemos visto hasta ahora:

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
}
Output
Sammy,Jessica,Drew,Jamie Sammy,Jessica,Drew,Jamie Sammy,Jessica Sammy

De esta manera, sabrá pasar cero, uno o muchos argumentos, y un segmento que expanda, a una función variádica.

Conclusión

En este tutorial, vio cómo las funciones variádicas pueden limpiar su código. Si bien no siempre necesitará usarlas, pueden resultarle útiles:

  • si determina que creará un segmento temporal solo para pasar a una función;
  • cuando el número de parámetros de entrada se desconoce o variará cuando se invoque;
  • para que su código sea más legible.

Para obtener más información sobre la creación e invocación de funciones, puede leer Cómo definir e invocar funciones en Go.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel