Как использовать функции Variadic в Go

Вступление

variadic function - это функция, которая принимает ноль, одно или несколько значений в качестве одного аргумента. Хотя функции с переменными числами не являются распространенным случаем, они могут использоваться, чтобы сделать ваш код чище и более читабельным.

Вариадические функции встречаются чаще, чем кажутся. Наиболее распространенной является функцияPrintln из пакетаfmt.

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

function с параметром, которому предшествует набор эллипсов (...), считается вариативной функцией. Многоточие означает, что предоставленный параметр может быть нулевым, одним или несколькими значениями. Для пакетаfmt.Println указывается, что параметрa является переменным.

Давайте создадим программу, которая использует функциюfmt.Println и передает ноль, одно или несколько значений:

print.go

package main

import "fmt"

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

В первый раз, когда мы вызываемfmt.Println, мы не передаем никаких аргументов. Во второй раз, когда мы вызываемfmt.Println, мы передаем только один аргумент со значениемone. Затем мы передаемone иtwo и, наконец,one,two иthree.

Запустим программу с помощью следующей команды:

go run print.go

Мы увидим следующий вывод:

Output
one
one two
one two three

Первая строка вывода пуста. Это потому, что мы не передавали никаких аргументов при первом вызовеfmt.Println. Второй раз было напечатано значениеone. Затемone иtwo, и, наконец,one,two иthree.

Теперь, когда мы увидели, как вызывать переменную функцию, давайте посмотрим, как мы можем определить нашу собственную переменную функцию.

Определение функции Variadic

Мы можем определить вариативную функцию, используя многоточие (...) перед аргументом. Давайте создадим программу, которая приветствует людей, когда их имена отправляются в функцию:

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

Мы создали функциюsayHello, которая принимает только один параметр с именемnames. Параметр является переменным, поскольку мы ставим многоточие (...) перед типом данных:...string. Это говорит Go, что функция может принимать ноль, один или много аргументов.

ФункцияsayHello получает параметрnames какslice. Поскольку тип данных -string, параметрnames можно рассматривать так же, как фрагмент строк ([]string) внутри тела функции. Мы можем создать цикл с операторомrange и выполнять итерацию по фрагменту строк.

Если мы запустим программу, мы получим следующий вывод:

OutputHello Sammy
Hello Sammy
Hello Jessica
Hello Drew
Hello Jamie

Обратите внимание, что ничего не печатается впервые, мы вызвалиsayHello. Это связано с тем, что вариативный параметр был пустымslice изstring. Поскольку мы просматриваем срез в цикле, перебирать нечего, иfmt.Printf никогда не вызывается.

Давайте изменим программу, чтобы обнаружить, что никакие значения не были отправлены в:

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

Теперь, используяif statement, если никакие значения не переданы, длинаnames будет0, и мы распечатаемnobody to greet:

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

Использование параметра variadic может сделать ваш код более читабельным. Давайте создадим функцию, которая объединяет слова вместе с указанным разделителем. Сначала мы создадим эту программу без функции с переменным числом, чтобы показать, как она будет выглядеть:

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
}

В этой программе мы передаем запятую (,) в качестве разделителя функцииjoin. Затем мы передаем часть значений для присоединения. Вот вывод:

OutputSammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy

Поскольку функция принимает фрагмент строки в качестве параметраvalues, нам пришлось заключить все наши слова в фрагмент, когда мы вызывали функциюjoin. Это может затруднить чтение кода.

Теперь давайте напишем ту же функцию, но мы будем использовать функцию с переменным числом аргументов:

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
}

Если мы запустим программу, мы увидим, что мы получаем тот же результат, что и предыдущая программа:

OutputSammy,Jessica,Drew,Jamie
Sammy,Jessica
Sammy

Хотя обе версии функцииjoin программно выполняют одно и то же, вариативную версию функции намного легче читать при ее вызове.

Variadic Argument Order

В функции может быть только один переменный параметр, и он должен быть последним параметром, определенным в функции. Определение параметров в переменной функции в любом порядке, кроме последнего параметра, приведет к ошибке компиляции:

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
}

На этот раз мы помещаем параметрvalues первым в функциюjoin. Это приведет к следующей ошибке компиляции:

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

При определении любой переменной функции, только последний параметр может быть переменной.

Взрывающиеся аргументы

До сих пор мы видели, что мы можем передавать ноль, одно или несколько значений в функцию с переменными числами. Однако будут случаи, когда у нас будет фрагмент значений, и мы хотим отправить их в функцию с переменным числом аргументов.

Давайте посмотрим на нашу функциюjoin из последнего раздела, чтобы увидеть, что происходит:

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
}

Если мы запустим эту программу, мы получим ошибку компиляции:

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

Несмотря на то, что вариативная функция преобразует параметрvalues ...string в фрагмент строк[]string, мы не можем передать фрагмент строк в качестве аргумента. Это потому, что компилятор ожидает дискретных аргументов строк.

Чтобы обойти это, мы можемexplode срез, добавив к нему суффикс набора эллипсов (...) и превратив его в дискретные аргументы, которые будут переданы в вариативную функцию:

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
}

На этот раз, когда мы вызвали функциюjoin, мы расширили срезnames, добавив эллипсы (...).

Это позволяет программе теперь работать как положено:

OutputSammy,Jessica,Drew,Jamie

Важно отметить, что мы все равно можем передать ноль, один или несколько аргументов, а также фрагмент, который мы взрываем. Вот код, передающий все варианты, которые мы видели до сих пор:

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

Теперь мы знаем, как передать ноль, один или несколько аргументов, а также разрезанный фрагмент, в функцию с переменным числом аргументов.

Заключение

В этом уроке мы увидели, как различные функции могут сделать ваш код чище. Хотя вам не всегда нужно их использовать, вы можете найти их полезными:

  • Если вы обнаружите, что создаете временный фрагмент просто для передачи функции.

  • Когда количество входных параметров неизвестно или будет меняться при вызове.

  • Чтобы сделать ваш код более читабельным.

Чтобы узнать больше о создании и вызове функций, вы можете прочитатьHow to Define and Call Functions in Go.