前書き
variadic functionは、1つの引数として0、1、または複数の値を受け入れる関数です。 可変引数関数は一般的なケースではありませんが、コードをよりクリーンで読みやすくするために使用できます。
可変長関数は、見かけよりも一般的です。 最も一般的なものは、fmt
パッケージのPrintln
関数です。
func Println(a ...interface{}) (n int, err error)
楕円のセット(...
)が前に付いたパラメーターを持つfunctionは、可変個引数関数と見なされます。 省略記号は、指定されたパラメーターが0、1、またはそれ以上の値であることを意味します。 fmt.Println
パッケージの場合、パラメーターa
が可変個引数であると述べています。
fmt.Println
関数を使用して、0、1、または複数の値を渡すプログラムを作成しましょう。
print.go
package main
import "fmt"
func main() {
fmt.Println()
fmt.Println("one")
fmt.Println("one", "two")
fmt.Println("one", "two", "three")
}
初めてfmt.Println
を呼び出すときは、引数を渡しません。 2回目にfmt.Println
を呼び出すときは、one
の値を使用して1つの引数のみを渡します。 次に、one
とtwo
を渡し、最後にone
、two
、およびthree
を渡します。
次のコマンドでプログラムを実行しましょう:
go run print.go
次の出力が表示されます。
Output
one
one two
one two three
出力の最初の行は空白です。 これは、fmt.Println
が最初に呼び出されたときに引数を渡さなかったためです。 2回目はone
の値が出力されました。 次に、one
とtwo
、最後にone
、two
、three
です。
可変個の関数を呼び出す方法がわかったので、独自の可変個の関数を定義する方法を見てみましょう。
可変長関数の定義
引数の前に省略記号(...
)を使用すると、可変個引数関数を定義できます。 関数に名前が送信されたときに挨拶するプログラムを作成してみましょう。
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)
}
}
names
と呼ばれる単一のパラメーターのみを受け取るsayHello
関数を作成しました。 データ型...string
の前に省略記号(...
)を付けるため、パラメーターは可変個引数です。 これは、関数が0、1、または多くの引数を受け入れることができることをGoに伝えます。
sayHello
関数は、names
パラメーターをslice
として受け取ります。 データ型はstring
であるため、names
パラメーターは、関数本体内の文字列のスライス([]string
)のように扱うことができます。 range
演算子を使用してループを作成し、文字列のスライスを反復処理できます。
プログラムを実行すると、次の出力が得られます。
OutputHello Sammy
Hello Sammy
Hello Jessica
Hello Drew
Hello Jamie
sayHello
を初めて呼び出したときに何も出力されなかったことに注意してください。 これは、可変個引数パラメーターがstring
の空のslice
であったためです。 スライスをループしているので、繰り返すものはなく、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
可変引数を使用すると、コードが読みやすくなります。 指定された区切り文字で単語を結合する関数を作成しましょう。 最初に可変機能を持たないこのプログラムを作成して、その読み方を示します。
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
関数の両方のバージョンはプログラムでまったく同じことを行いますが、関数の可変個引数バージョンは、呼び出されたときにはるかに読みやすくなります。
可変引数の順序
関数には1つの変数パラメータのみを含めることができ、それは関数で定義された最後のパラメータでなければなりません。 最後のパラメーター以外の任意の順序で変数関数のパラメーターを定義すると、コンパイルエラーが発生します。
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
}
今回は、join
関数の最初にvalues
パラメータを配置します。 これにより、次のコンパイルエラーが発生します。
Output./join_error.go:18:11: syntax error: cannot use ... with non-final parameter values
可変引数関数を定義する場合、最後のパラメーターのみが可変引数になります。
爆発的な議論
これまでに、0、1、またはそれ以上の値を可変引数関数に渡すことができることを見てきました。 ただし、値のスライスがあり、変数の関数に送信したい場合があります。
前のセクションの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
ゼロ、1つ、またはそれ以上の引数と爆発したスライスを渡すことができることに注意することが重要です。 これまで見てきたすべてのバリエーションを渡すコードを次に示します。
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
これで、0、1、または多くの引数と、爆発するスライスを可変引数関数に渡す方法がわかりました。
結論
このチュートリアルでは、可変引数関数がコードをきれいにする方法を説明しました。 必ずしもそれらを使用する必要はありませんが、便利な場合があります。
-
関数に渡すためだけに一時スライスを作成していることがわかった場合。
-
入力パラメーターの数が不明な場合、または呼び出されたときに変化する場合。
-
コードを読みやすくするため。
関数の作成と呼び出しの詳細については、How to Define and Call Functions in Goを参照してください。