はじめに
Go 言語基礎文法最速マスターにて出題されている演習課題「Exercise: Stringers(ストリンガー)」の答えに辿り着くまでの手順と解答例の解説を記事にしてみました。暗黙のインターフェース、型にメソッドを定義する、レシーバーなどについても簡単に説明しています。
課題
課題の目的は IPAddr
型の IP アドレス {1, 2, 3, 4}
をドット 10 進表記 の "1.2.3.4"
で出力する fmt.Stringer
インターフェースを実装することです。
package main
import "fmt"
type IPAddr [4]byte
// TODO: Add a "String() string" method to IPAddr.
func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}
解答例
package main
import "fmt"
type IPAddr [4]byte
func (ip IPAddr) String() string {
return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
}
func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}
解説
初めに、課題のソースコードを読み解きます。
5 行目、type
で [4]byte
型を IPAddr
という名前で 型宣言 していますね。
type IPAddr [4]byte
type
とは、既存の型と同じ基になる型と操作を持つ新しい別個の型を作成して、それに識別子をバインドすることができる予約語です。
type 識別子 型
わかりやすく言うと、既存の型と同じの新しい名前の型を作っちゃおうぜウェーイwwwってことです。
7 行目、TODO コメントに IPAddr
型に String() string
メソッドを追加してねと書いてあります。
TODO: Add a "String() string" method to IPAddr.
このコメントの後に処理を実装することがわかります。
9 行目、main
関数で map[string]IPAddr
型の変数を定義して、for range
文で各要素を fmt.Printf
関数で出力しています。
func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}
変数 ip
にドット 10 進記法で出力する IP アドレスが格納されていることがわかります。
さて、実装を進める前に interface
(インターフェース)型の理解が必要になります。
interface
型とは method(メソッド)の signature(シグネチャ)の集まりを定義したものです。
シグネチャとはメソッドの名前、引数の数と型、戻り値の型のことです。
interface
型は type
で下記のように宣言することができます、Java のように明示的に implements
と書いて宣言する必要はありません。
type 識別子 interface
では type
で fmt.Stringer
インターフェースを実装すれば良いかというと、違います。
Stringers でも説明されているように、fmt.Stringer
インターフェースは既に fmt
パッケージで下記のように宣言されています。
type Stringer interface {
String() string
}
つまり、やることは TODO コメントにあった通り IPAddr
型に String() string
メソッドを追加するだけです。
メソッドの追加ってどうやりゃいいんじゃって話なんですけど、ここで method(メソッド)の理解が必要になります。
Go は型にメソッドを定義できます。
メソッドとは、receiver(レシーバー)引数を伴う関数のことです。
レシーバーとは、下記のメソッドの定義では ip
の部分になります。
func (ip IPAddr) String() string {}
Go はインターフェースに定義された関数をメソッドとして定義すると、暗黙的にインターフェースを実装したことになります。
前述のコードは String
関数をメソッドとして定義することで fmt.Stringer
インターフェースを暗黙的に実装しています。もちろん、fmf
パッケージを import
している前提の話です。
つまり、前述のコードは課題である fmt.Stringer
インターフェースを実装するということを実現することができます。
しかし、まだ IP アドレスをドット 10 進表記にするという課題が残っていますね。
main
関数では、変数 ip
に IP アドレスを格納して fmt.Printf
関数で出力していました。
ということは、下記のコードが書いてあると fmt.Printf
関数は変数 ip
を出力する際に (ip IPAddr) String()
を呼び出します。
func (ip IPAddr) String() string {}
なんで fmt.Printf
関数から String
関数が呼び出されるんじゃ?ってなりますよね。
Stringer
インターフェースの ソースコード にあるコメントに答えがあります。Stringers でも説明されているように、多くのパッケージでは変数を文字列で出力する為に Stringer
インターフェースが使われています。
// Stringer is implemented by any value that has a String method,
// which defines the ``native'' format for that value.
// The String method is used to print values passed as an operand
// to any format that accepts a string or to an unformatted printer
// such as Print.
type Stringer interface {
String() string
}
また、fmt
パッケージドキュメントの Printing にて、print
関数について下記のような説明があります。
オペランドが String メソッドを実装している場合は、オブジェクトを文字列に変換する為にそのメソッドが呼び出され、その後(もしあれば)動詞の要求に応じてフォーマットされます。
If an operand implements method String() string, that method will be invoked to convert the object to a string, which will then be formatted as required by the verb (if any).
https://pkg.go.dev/fmt@go1.17.2
この辺りの理解が大事です。
では、どうやってレシーバー引数の IP アドレスをドット 10 進表記に変換するのかというと fmt.Sprintf
関数を使います。
func (ip IPAddr) String() string {
return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
}
fmt.Sprintf
関数は、書式指定子に従ってフォーマットした結果を文字列で返します。%d
という書式指定子を指定すると 10 進数で出力してくれます。
あとは、配列の添字を指定してドッド 10 進表記にして返すように実装するだけです。
package main
import "fmt"
type IPAddr [4]byte
func (ip IPAddr) String() string {
return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
}
func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}
実行すると下記のように出力されます。
loopback: 127.0.0.1
googleDNS: 8.8.8.8
大丈夫だ、問題ない。
以上です。
おわりに
仮面ライダーストリンガーっていそうじゃない。え、カブトムシの改造電気人間がいるの?