フューチャー技術ブログ

Go 1.24リリース連載 templateの新文法(イテレータ)

はじめに

TIG所属の大江です。Go1.24リリース連載の4本目です。

本記事では以下の内容を取り上げます。

  • text/templateでのrange over func、range over intのサポート

アップデート内容

Go1.22で導入されたrange over int, Go1.23で導入されたrange over funcが共にtext/templateパッケージで使えるようになりました。

以降では、これらの機能がどのように使えるかを見ていきます。

range over int

指定された回数、同じ文字を繰り返すテンプレートを書く

Go1.23までの場合

テンプレートを使って同じ文字を10回繰り返す場合、以下のようにスライスや配列を生成してからテンプレートに渡す必要がありました。

package main

import (
"log"
"os"
"text/template"
)

func main() {
numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

// このピリオドは上で定義したnumbersのスライス
tmpl := `{{range .}}Bonjour
{{end}}`

t := template.Must(template.New("").Parse(tmpl))
err := t.Execute(os.Stdout, numbers)
if err != nil {
log.Fatal(err)
}
}

/// 処理結果
///
/// Bonjour
/// Bonjour
/// Bonjour
/// Bonjour
/// Bonjour
/// Bonjour
/// Bonjour
/// Bonjour
/// Bonjour
/// Bonjour

Go1.24の場合

Go1.24では、range over intにより、以下のようにスライスや配列を用意せず直接テンプレート内で繰り返し回数を指定することが出来るようになりました。

package main

import (
"log"
"os"
"text/template"
)

func main() {
// 直接数字で繰り返し回数を指定
tmpl := `{{range 10}}Bonjour
{{end}}`

t := template.Must(template.New("").Parse(tmpl))
err := t.Execute(os.Stdout, nil)
if err != nil {
log.Fatal(err)
}
}

/// 処理結果
///
/// Bonjour
/// Bonjour
/// Bonjour
/// Bonjour
/// Bonjour
/// Bonjour
/// Bonjour
/// Bonjour
/// Bonjour
/// Bonjour

range over func

マップの中の一部を抜き出してテンプレートを使って表示する

Go1.23までの場合

以下では数字と文字の組み合わせの中から、偶数、3の倍数をそれぞれ抜き出し、テンプレートを使って表示します。
Go1.23までは以下のように、それぞれの関数の戻り値として別のマップに代入するなどしてからテンプレートに渡す必要がありました。

package main

import (
"fmt"
"log"
"os"
"text/template"
)

func main() {
numbers := map[int]string{
1: "un",
2: "deux",
3: "trois",
4: "quatre",
5: "cinq",
6: "six",
7: "sept",
8: "huit",
9: "neuf",
10: "dix",
}

// このピリオドは下で定義したgetMultiples()の戻り値
tmpl := `{{range $i, $c := .}}{{$i}}:{{$c}}
{{end}}`

t := template.Must(template.New("").Parse(tmpl))
fmt.Println("偶数")
evenNumbers := getMultiples(numbers, 2)
err := t.Execute(os.Stdout, evenNumbers)
if err != nil {
log.Fatal(err)
}
fmt.Println("\n3の倍数")
threeMultiples := getMultiples(numbers, 3)
err = t.Execute(os.Stdout, threeMultiples)
if err != nil {
log.Fatal(err)
}
}

func getMultiples(numbers map[int]string, divisor int) map[int]string {
multiples := make(map[int]string, 10)
for k, v := range numbers {
if k%divisor == 0 {
multiples[k] = v
}
}
return multiples
}

/// 処理結果
/// ※Goのマップでは、反復処理の操作を行う際の順序は保証されていないので順序はランダム
///
/// 偶数
/// 10:dix
/// 2:deux
/// 4:quatre
/// 6:six
/// 8:huit

/// 3の倍数
/// 3:trois
/// 6:six
/// 9:neuf

Go1.24の場合

新たに導入されたrange over funcを使えば、直接テンプレート内で関数を呼び出し、戻り値を表示することができます。
なおrange over funcについては、Go1.23連載の棚井さんの記事を参考にさせていただきました。

package main

import (
"fmt"
"iter"
"log"
"os"
"text/template"
)

func main() {
numbers := map[int]string{
1: "un",
2: "deux",
3: "trois",
4: "quatre",
5: "cinq",
6: "six",
7: "sept",
8: "huit",
9: "neuf",
10: "dix",
}

// このピリオドは下で定義したgetMultiples()の戻り値
tmpl := `{{range $i, $c := .}}{{$i}}:{{$c}}
{{end}}`

t := template.Must(template.New("").Parse(tmpl))
fmt.Println("偶数")
err := t.Execute(os.Stdout, getMultiples(numbers, 2))
if err != nil {
log.Fatal(err)
}
fmt.Println("\n3の倍数")
err = t.Execute(os.Stdout, getMultiples(numbers, 3))
if err != nil {
log.Fatal(err)
}
}

func getMultiples(numbers map[int]string, divisor int) iter.Seq2[int, string] {
return func(yield func(int, string) bool) {
for k, v := range numbers {
if k%divisor == 0 {
if !yield(k, v) {
return
}
}
}
}
}

/// 処理結果
/// ※Goのマップでは、反復処理の操作を行う際の順序は保証されていないので数字の順序はランダム
///
/// 偶数
/// 2:deux
/// 4:quatre
/// 10:dix
/// 6:six
/// 8:huit

/// 3の倍数
/// 3:trois
/// 6:six
/// 9:neuf

おわりに

以上、text/templateパッケージでのrange over int、range over funcについて紹介しました。近年のGoの反復処理のアップデートがさらに加速し、痒い所に手が届くようになった印象です。