|
| 1 | +--- |
| 2 | +title: "Go 1.24リリース連載 templateの新文法(イテレータ)" |
| 3 | +date: 2025/01/30 00:00:00 |
| 4 | +postid: a |
| 5 | +tag: |
| 6 | + - Go |
| 7 | + - Go1.24 |
| 8 | +category: |
| 9 | + - Programming |
| 10 | +thumbnail: /images/20250130a/thumbnail.png |
| 11 | +author: 大江聖太郎, |
| 12 | +lede: "text/template`でのrange over func、range over intのサポート を取り上げます" |
| 13 | +--- |
| 14 | +<img src="/images/20250130a/go1.24.png" alt="" width="800" height="484"> |
| 15 | + |
| 16 | + |
| 17 | +# はじめに |
| 18 | + |
| 19 | +TIG所属の大江です。[Go1.24リリース連載](/articles/20250127a/)の4本目です。 |
| 20 | + |
| 21 | +本記事では以下の内容を取り上げます。 |
| 22 | + |
| 23 | +- `text/template`でのrange over func、range over intのサポート |
| 24 | + |
| 25 | +## アップデート内容 |
| 26 | + |
| 27 | +Go1.22で導入された`range over int`, Go1.23で導入された`range over func`が共に`text/template`パッケージで使えるようになりました。 |
| 28 | + |
| 29 | +以降では、これらの機能がどのように使えるかを見ていきます。 |
| 30 | + |
| 31 | +### range over int |
| 32 | + |
| 33 | +指定された回数、同じ文字を繰り返すテンプレートを書く |
| 34 | + |
| 35 | +#### Go1.23までの場合 |
| 36 | + |
| 37 | +テンプレートを使って同じ文字を10回繰り返す場合、以下のようにスライスや配列を生成してからテンプレートに渡す必要がありました。 |
| 38 | + |
| 39 | +```go |
| 40 | +package main |
| 41 | + |
| 42 | +import ( |
| 43 | + "log" |
| 44 | + "os" |
| 45 | + "text/template" |
| 46 | +) |
| 47 | + |
| 48 | +func main() { |
| 49 | + numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} |
| 50 | + |
| 51 | + // このピリオドは上で定義したnumbersのスライス |
| 52 | + tmpl := `{{range .}}Bonjour |
| 53 | +{{end}}` |
| 54 | + |
| 55 | + t := template.Must(template.New("").Parse(tmpl)) |
| 56 | + err := t.Execute(os.Stdout, numbers) |
| 57 | + if err != nil { |
| 58 | + log.Fatal(err) |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | +/// 処理結果 |
| 63 | +/// |
| 64 | +/// Bonjour |
| 65 | +/// Bonjour |
| 66 | +/// Bonjour |
| 67 | +/// Bonjour |
| 68 | +/// Bonjour |
| 69 | +/// Bonjour |
| 70 | +/// Bonjour |
| 71 | +/// Bonjour |
| 72 | +/// Bonjour |
| 73 | +/// Bonjour |
| 74 | + |
| 75 | +``` |
| 76 | + |
| 77 | +### Go1.24の場合 |
| 78 | + |
| 79 | +Go1.24では、range over intにより、以下のようにスライスや配列を用意せず直接テンプレート内で繰り返し回数を指定することが出来るようになりました。 |
| 80 | + |
| 81 | +``` go |
| 82 | +package main |
| 83 | + |
| 84 | +import ( |
| 85 | + "log" |
| 86 | + "os" |
| 87 | + "text/template" |
| 88 | +) |
| 89 | + |
| 90 | +func main() { |
| 91 | + // 直接数字で繰り返し回数を指定 |
| 92 | + tmpl := `{{range 10}}Bonjour |
| 93 | +{{end}}` |
| 94 | + |
| 95 | + t := template.Must(template.New("").Parse(tmpl)) |
| 96 | + err := t.Execute(os.Stdout, nil) |
| 97 | + if err != nil { |
| 98 | + log.Fatal(err) |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +/// 処理結果 |
| 103 | +/// |
| 104 | +/// Bonjour |
| 105 | +/// Bonjour |
| 106 | +/// Bonjour |
| 107 | +/// Bonjour |
| 108 | +/// Bonjour |
| 109 | +/// Bonjour |
| 110 | +/// Bonjour |
| 111 | +/// Bonjour |
| 112 | +/// Bonjour |
| 113 | +/// Bonjour |
| 114 | + |
| 115 | +``` |
| 116 | + |
| 117 | +## range over func |
| 118 | + |
| 119 | +マップの中の一部を抜き出してテンプレートを使って表示する |
| 120 | + |
| 121 | +### Go1.23までの場合 |
| 122 | + |
| 123 | +以下では数字と文字の組み合わせの中から、偶数、3の倍数をそれぞれ抜き出し、テンプレートを使って表示します。 |
| 124 | +Go1.23までは以下のように、それぞれの関数の戻り値として別のマップに代入するなどしてからテンプレートに渡す必要がありました。 |
| 125 | + |
| 126 | +``` go |
| 127 | +package main |
| 128 | + |
| 129 | +import ( |
| 130 | + "fmt" |
| 131 | + "log" |
| 132 | + "os" |
| 133 | + "text/template" |
| 134 | +) |
| 135 | + |
| 136 | +func main() { |
| 137 | + numbers := map[int]string{ |
| 138 | + 1: "un", |
| 139 | + 2: "deux", |
| 140 | + 3: "trois", |
| 141 | + 4: "quatre", |
| 142 | + 5: "cinq", |
| 143 | + 6: "six", |
| 144 | + 7: "sept", |
| 145 | + 8: "huit", |
| 146 | + 9: "neuf", |
| 147 | + 10: "dix", |
| 148 | + } |
| 149 | + |
| 150 | + // このピリオドは下で定義したgetMultiples()の戻り値 |
| 151 | + tmpl := `{{range $i, $c := .}}{{$i}}:{{$c}} |
| 152 | +{{end}}` |
| 153 | + |
| 154 | + t := template.Must(template.New("").Parse(tmpl)) |
| 155 | + fmt.Println("偶数") |
| 156 | + evenNumbers := getMultiples(numbers, 2) |
| 157 | + err := t.Execute(os.Stdout, evenNumbers) |
| 158 | + if err != nil { |
| 159 | + log.Fatal(err) |
| 160 | + } |
| 161 | + fmt.Println("\n3の倍数") |
| 162 | + threeMultiples := getMultiples(numbers, 3) |
| 163 | + err = t.Execute(os.Stdout, threeMultiples) |
| 164 | + if err != nil { |
| 165 | + log.Fatal(err) |
| 166 | + } |
| 167 | +} |
| 168 | + |
| 169 | +func getMultiples(numbers map[int]string, divisor int) map[int]string { |
| 170 | + multiples := make(map[int]string, 10) |
| 171 | + for k, v := range numbers { |
| 172 | + if k%divisor == 0 { |
| 173 | + multiples[k] = v |
| 174 | + } |
| 175 | + } |
| 176 | + return multiples |
| 177 | +} |
| 178 | + |
| 179 | +/// 処理結果 |
| 180 | +/// ※Goのマップでは、反復処理の操作を行う際の順序は保証されていないので順序はランダム |
| 181 | +/// |
| 182 | +/// 偶数 |
| 183 | +/// 10:dix |
| 184 | +/// 2:deux |
| 185 | +/// 4:quatre |
| 186 | +/// 6:six |
| 187 | +/// 8:huit |
| 188 | + |
| 189 | +/// 3の倍数 |
| 190 | +/// 3:trois |
| 191 | +/// 6:six |
| 192 | +/// 9:neuf |
| 193 | + |
| 194 | +``` |
| 195 | + |
| 196 | +### Go1.24の場合 |
| 197 | + |
| 198 | +新たに導入されたrange over funcを使えば、直接テンプレート内で関数を呼び出し、戻り値を表示することができます。 |
| 199 | +なおrange over funcについては、[Go1.23連載の棚井さんの記事](https://future-architect.github.io/articles/20240718a/)を参考にさせていただきました。 |
| 200 | + |
| 201 | +```go |
| 202 | +package main |
| 203 | + |
| 204 | +import ( |
| 205 | + "fmt" |
| 206 | + "iter" |
| 207 | + "log" |
| 208 | + "os" |
| 209 | + "text/template" |
| 210 | +) |
| 211 | + |
| 212 | +func main() { |
| 213 | + numbers := map[int]string{ |
| 214 | + 1: "un", |
| 215 | + 2: "deux", |
| 216 | + 3: "trois", |
| 217 | + 4: "quatre", |
| 218 | + 5: "cinq", |
| 219 | + 6: "six", |
| 220 | + 7: "sept", |
| 221 | + 8: "huit", |
| 222 | + 9: "neuf", |
| 223 | + 10: "dix", |
| 224 | + } |
| 225 | + |
| 226 | + // このピリオドは下で定義したgetMultiples()の戻り値 |
| 227 | + tmpl := `{{range $i, $c := .}}{{$i}}:{{$c}} |
| 228 | +{{end}}` |
| 229 | + |
| 230 | + t := template.Must(template.New("").Parse(tmpl)) |
| 231 | + fmt.Println("偶数") |
| 232 | + err := t.Execute(os.Stdout, getMultiples(numbers, 2)) |
| 233 | + if err != nil { |
| 234 | + log.Fatal(err) |
| 235 | + } |
| 236 | + fmt.Println("\n3の倍数") |
| 237 | + err = t.Execute(os.Stdout, getMultiples(numbers, 3)) |
| 238 | + if err != nil { |
| 239 | + log.Fatal(err) |
| 240 | + } |
| 241 | +} |
| 242 | + |
| 243 | +func getMultiples(numbers map[int]string, divisor int) iter.Seq2[int, string] { |
| 244 | + return func(yield func(int, string) bool) { |
| 245 | + for k, v := range numbers { |
| 246 | + if k%divisor == 0 { |
| 247 | + if !yield(k, v) { |
| 248 | + return |
| 249 | + } |
| 250 | + } |
| 251 | + } |
| 252 | + } |
| 253 | +} |
| 254 | + |
| 255 | +/// 処理結果 |
| 256 | +/// ※Goのマップでは、反復処理の操作を行う際の順序は保証されていないので数字の順序はランダム |
| 257 | +/// |
| 258 | +/// 偶数 |
| 259 | +/// 2:deux |
| 260 | +/// 4:quatre |
| 261 | +/// 10:dix |
| 262 | +/// 6:six |
| 263 | +/// 8:huit |
| 264 | + |
| 265 | +/// 3の倍数 |
| 266 | +/// 3:trois |
| 267 | +/// 6:six |
| 268 | +/// 9:neuf |
| 269 | + |
| 270 | +``` |
| 271 | + |
| 272 | +# おわりに |
| 273 | + |
| 274 | +以上、text/templateパッケージでのrange over int、range over funcについて紹介しました。近年のGoの反復処理のアップデートがさらに加速し、痒い所に手が届くようになった印象です。 |
0 commit comments