One place for hosting & domains

      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:

      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 %sn", 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 %sn", 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.



      Source link